MCA子系统分析

August 10th, 2015 by JasonLe's Tech Leave a reply »

Update 2015-6-15

machine check exception在Intel manual中是18号异常,主要用来处理硬件产生的异常,包括各种总线错误,内存错误,数据错误等。MCA子系统正是为了处理这个异常而设计的,由于这个子系统与寄存器紧密相关,所以我们需要阅读Intel manual chapter15后,才能理解本文章的一些函数与代码。

本文主要讨论SRAO,SRAR,UCNA类型的错误,其中SRAR错误错误等级最高,如果这个错误发生在kernel中,kernel默认panic。SRAO、UCNA等级不至于panic,但是当系统发生多次,仍有可能触发SRAR错误。

SRAO错误主要发生在Memory scrubbing 时,而Memory scrubbing主要通过定时ECC校验发现内存的单bit错误,但是对于多bit错误,导致内存错误失败,就会发生SRAO错误,发生这个错误意味着,系统检测到了内存错误,但是这个错误并没有被cpu使用,cpu的执行数据流仍然可以继续运行下去而不至于宕机。这就是RAS的目标。

下面函数就是MCA的初始化函数,__mcheck_cpu_ancient_init()初始化早期主板的MCA架构,类似于P4处理器。之后将do_machine_check()设定为MCE的异常处理函数,这里我不会展开。

__mcheck_cpu_init_generic()主要是用来初始化machine_check_poll(),这个函数主要用来处理UCNA/CE类型的错误,这个稍后进行介绍。__mcheck_cpu_init_vendor(c)主要是用来识别是Intel架构还是AMD架构,之后函数初始化了一个timer,用来定时调用machine_check_poll()轮询UCNA/CE错误。

最后又定义了一个工作队列mce_work,用来调用mce_process_work()函数,这个函数主要实现对SRAO类型错误的处理,最后并调用memory_failure()对错误进行恢复。(工作队列是处于进程上下文的的,这个也是memory_failure()要求的) mce_irq_work是一个信号队列,主要用来唤醒/dev/mcelog对于错误进行一个记录。

void mcheck_cpu_init(struct cpuinfo_x86 *c)
{
...
    if (__mcheck_cpu_ancient_init(c))
        return;
...
    machine_check_vector = do_machine_check;

    __mcheck_cpu_init_generic();
    __mcheck_cpu_init_vendor(c);
    __mcheck_cpu_init_timer();
    INIT_WORK(this_cpu_ptr(&mce_work), mce_process_work);
    init_irq_work(this_cpu_ptr(&mce_irq_work), &mce_irq_work_cb);
}

UCNA处理

下面我们来仔细说一下__mcheck_cpu_init_vendor(c);这个函数中完成了对于UCNA与Correctted Error的错误处理的初始化,通过下面初始化代码的函数调用关系,我们可以发现对于这两种类型的错误,handler就是intel_threshold_interrupt(),这个handler包括machine_check_poll()与mce_notify_irq()。这个函数本质上就是触发对于错误的记录,没有任何额外的操作,这就是叫UCNA(Uncorrected Error No Action)。machine_check_poll() 不对kernel产生任何影响,主要就是记录错误。

static void __mcheck_cpu_init_vendor(struct cpuinfo_x86 *c)
{
    switch (c->x86_vendor) {
    case X86_VENDOR_INTEL:
        mce_intel_feature_init(c);
....
}
void mce_intel_feature_init(struct cpuinfo_x86 *c)
{
    intel_init_thermal(c);
    intel_init_cmci();
}
static void intel_init_cmci(void)
{
...
    mce_threshold_vector = intel_threshold_interrupt;
...
}
static void intel_threshold_interrupt(void)
{
    if (cmci_storm_detect())
        return;
    machine_check_poll(MCP_TIMESTAMP, this_cpu_ptr(&mce_banks_owned));
    mce_notify_irq();
}

在最新的4.0.4代码里,machine_check_poll()包括下面的代码,所以对于UCNA类型的错误也只是将他记录在mce_ring中,之后使用memory_failure()进行处理,比如标记为HWPoison页框。

if (severity == MCE_DEFERRED_SEVERITY && memory_error(&m))
{
      if (m.status & MCI_STATUS_ADDRV) {
          mce_ring_add(m.addr >> PAGE_SHIFT);
          mce_schedule_work();
      }
}

SRAR/SRAO错误处理

综上所述do_machine_check()主要处理Fetal与SRAR/SRAO类型的错误,他会通过查表mce_severity()判断错误等级。找到这个SRAR类型错误发生位置,内核空间直接panic,用户空间杀死当前进程(进入machine check exception是一种NMI类型的异常,处于进程上下文),(对于发生在用户空间SRAR错误处理的时机就是把错误记录在mce_info结构体中,给当前进程设置TIF_MCE_NOTIFY标示,在返回用户空间时,调用mce_notify_process()-找出之前记录在struct mce_info的错误信息,进一步调用memory_failure()进行错误恢复处理)在最新的主线内核提交中,Luck, Tony <[email protected]>提交了一个commit。在最新代码中,他删除了mce_info,mce_save_info(),mce_find_info(),mce_clear_info(),mce_notify_process()和位于do_notify_resume()中的mce_notify_process(),也就是说SRAR不在返回用户态前处理。

x86, mce: Get rid of TIF_MCE_NOTIFY and associated mce tricks

We now switch to the kernel stack when a machine check interrupts
during user mode. This means that we can perform recovery actions
in the tail of do_machine_check()

他改变了SRAR发生在用户空间时,通过设置fiag并调度的方式,直接在do_machine_check()最后加入对于这种错误的处理,并在末位加入memory_failure()的错误恢复,这里指出如果恢复失败,那么直接使用force_sig(SIGBUS, current)。

对于SRAO类型的错误主要通过记录在mce_ring中,然后通过工作队列的方式调用mce_process_work()方式调用memory_failure()进行错误处理。下面代码来自于do_machine_check()。

      if (severity == MCE_AO_SEVERITY &amp;&amp; mce_usable_address(&amp;m))
            mce_ring_add(m.addr >> PAGE_SHIFT);

do_machine_check() 在最后会通过调用mce_report_event()->mce_irq_work->wake up /dev/mcelog 记录SRAR/SRAO错误。 所有内存问题,最后都会调用memory_failure()函数,这个函数就是对于问题页框进行标记,然后解除与进程的关系映射等。

 

参考:

Memory scrubbing

中断下半部的两种实现方式

http://www.intel.com/content/www/us/en/processors/architectures-software-developer-manuals.html