do_machine_check() 函数分析

July 20th, 2015 by JasonLe's Tech Leave a reply »

do_machine_check()函数是MCA架构最为核心的函数,在之前的 一篇博文 我们分析了现有MCA 对于现有错误的处理流程,但是没有对于do_machine_check函数进行分析。这里我们会深入分析这个异常处理函数,下面分析的代码是基于Linux-3.14.38,在今天一月份Andi Kleen 提交了最新的补丁,这个会在最后进行说明。

do_machine_check()异常处理函数是由18号异常触发,它执行在NMI上下文,不适用已有的kernel service服务和机制,甚至无法正常打印信息。开头主要是声明一些变量,mca_config 主要用来声明当前系统mca的基本配置,包括是否选择通过mcelog记录CE错误,CMCI中断使能等。

struct mca_config {
        bool dont_log_ce;
        bool cmci_disabled;
        bool ignore_ce;
        bool disabled;
        bool ser;
        bool bios_cmci_threshold;
        u8 banks;
        s8 bootlog;
        int tolerant;
        int monarch_timeout;
        int panic_timeout;
        u32 rip_msr;
};

severity 主要记录错误等级,order主要用于后来进入Monarch’s region的顺序,no_way_out 用来快速检查当前cpu是否需要panic,mce_gather_info(&m, regs);用来收集当前cpu MCA寄存器中的数据,保存到struct mce结构体中。

mces_seen是每个cpu都有存在的struct mce变量,当需要保存该值时,就将其保存在final中,final<=>mces_seen等价。

void do_machine_check(struct pt_regs *regs, long error_code)
{
        struct mca_config *cfg = &mca_cfg;
        struct mce m, *final;
        int i;
        int worst = 0;
        int severity;

        int order;
        int no_way_out = 0; 

        int kill_it = 0;
        DECLARE_BITMAP(toclear, MAX_NR_BANKS);
        DECLARE_BITMAP(valid_banks, MAX_NR_BANKS);
        char *msg = "Unknown";
        atomic_inc(&mce_entry);

        this_cpu_inc(mce_exception_count);

        if (!cfg->banks)
                goto out;

        mce_gather_info(&m, regs);

        final = &__get_cpu_var(mces_seen);
        *final = m;

        memset(valid_banks, 0, sizeof(valid_banks));
        no_way_out = mce_no_way_out(&m, &msg, valid_banks, regs);

        barrier();

检查 mcgstatus 寄存器中的MCG_STATUS_RIPV是否有效,无效,将kill_it置1,后续配合SRAR事件使用。

mce_start()与mce_end()可以使得所有cpu进入Monarch’s region,第一个进入handler的是Monarch,之后的cpu听从Monarch的指挥。之后按照cfg配置信息,扫描当前cpu所有bank,将当前MSR_IA32_MCx_STATUS()信息读取到m.status中,判断这个status是否有效,对于machine_check_poll() 处理的事件跳过处理。

判断当前bank内严重等级,如果severity错误等级是MCE_KEEP_SEVERITY 和 MCE_NO_SEVERITY ,忽略这次扫描,然后mce_read_aux()继续读取ADDR与MISC相关信息到当前struct mce m中。

这时当前severity等级是MCE_AO_SEVERITY 时,kernel会将其保存到ring buffer中,在之后的work queue中进行处理。然后调用mce_log(&m)记录到/dev/mcelog中。如果此次错误等级最高,那么更新 worst ,并struct mce写入到当前cpu。

扫描完毕之后,保存struct mce 信息,清除寄存器内容。再次判断worst 等级,高于MCE_PANIC_SEVERITY的话,稍后会panic。然后mce_end()退出 Monarch。

        if (!(m.mcgstatus & MCG_STATUS_RIPV))
                kill_it = 1;

        order = mce_start(&no_way_out);
        for (i = 0; i < cfg->banks; i++) {
                __clear_bit(i, toclear);
                if (!test_bit(i, valid_banks))
                        continue;
                if (!mce_banks[i].ctl)
                        continue;

                m.misc = 0;
                m.addr = 0;
                m.bank = i;

                m.status = mce_rdmsrl(MSR_IA32_MCx_STATUS(i));
                if ((m.status & MCI_STATUS_VAL) == 0)
                        continue;

                if (!(m.status & (cfg->ser ? MCI_STATUS_S : MCI_STATUS_UC)) &&
                        !no_way_out)
                        continue;

                add_taint(TAINT_MACHINE_CHECK, LOCKDEP_NOW_UNRELIABLE);

                severity = mce_severity(&m, cfg->tolerant, NULL);

                if (severity == MCE_KEEP_SEVERITY && !no_way_out)
                        continue;
                __set_bit(i, toclear);
                if (severity == MCE_NO_SEVERITY) {
                        continue;
                }

                mce_read_aux(&m, i);

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

                mce_log(&m);

                if (severity > worst) {
                        *final = m;
                        worst = severity;
                }
        }
        m = *final;

        if (!no_way_out)
                mce_clear_state(toclear);

        if (mce_end(order) < 0)
                no_way_out = worst >= MCE_PANIC_SEVERITY;

tolerant 在mca机制中可调, 0最严格,3最宽松。

/*
* Tolerant levels:
* 0: always panic on uncorrected errors, log corrected errors
* 1: panic or SIGBUS on uncorrected errors, log corrected errors
* 2: SIGBUS or log uncorrected errors (if possible), log corr. errors
* 3: never panic or SIGBUS, log all errors (for testing only)
*/

只要小于3,如果no_way_out等于1 ,直接panic,如果不是,worst等级是SRAR,那么标记这个进程,在返回用户态时处理,具体处理的函数就是memory_failure();如果不是SRAR错误,而且RIPV无效,那么只能杀死当前进程。

        if (cfg->tolerant < 3) {
                if (no_way_out)
                        mce_panic("Fatal machine check on current CPU", &m, msg);
                if (worst == MCE_AR_SEVERITY) {
                        /* schedule action before return to userland */
                        mce_save_info(m.addr, m.mcgstatus & MCG_STATUS_RIPV);
                        set_thread_flag(TIF_MCE_NOTIFY);
                } else if (kill_it) {
                        force_sig(SIGBUS, current);
                }
        }

        if (worst > 0)
                mce_report_event(regs);
        mce_wrmsrl(MSR_IA32_MCG_STATUS, 0);
out:
        atomic_dec(&mce_entry);
        sync_core();
}

在最新代码 Linux-4.0.4 中,Andi Kleen 删除了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)。

最后指出:do_machine_check()只处理SRAR、SRAO类型的错误,对于UCNA类型错误由machine_check_poll()处理,下篇博文介绍machine_check_poll()。

 

参考:

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