do_machine_check() 函数分析

July 20th, 2015 by JasonLe's Tech 1,693 views

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

git多分支开发

July 14th, 2015 by JasonLe's Tech 1,204 views

虽然网上有很多关于git多分支开发的介绍,但是有时候在项目中使用的多分支还是让我非常混乱。最近我参与了一个“elinux.org 中文版: 嵌入式 Linux 知识库”的翻译计划。

eLinux.org 是 Linux 基金会下属 Consumer Electronics Linux Forum 维护的一个 Embedded Linux Wiki。该 Wiki 全面系统地梳理了嵌入式 Linux 方方面面的知识。

该项目致力于翻译 Embedded Linux Wiki 为中文版。欢迎fork我的分支,一起参与elinux.org的翻译工作!项目:https://github.com/lzz5235/elinux

由于这个项目启动时间不久,经常出现了上游分支大量的修改,此刻我已经开始了一些翻译工作,而我如果从远程拉取修改,会和本地产生严重的冲突,非常麻烦。之后为了解决这个问题,在原有master分支上,我在本地创建了一个development分支,用来开展翻译工作。

$git branch development
$git checkout development

当切换到开发分支,我们就可以开始翻译链接了,当翻译完毕之后,我们需要使用git add、commit提交。然后我们可以切回master,首先我们需要把master分支中的内容更新到最新(可以使用git fetch ,也可以使用git pull)。这里我们可以使用git merge development合并开发分支,这个时候,我们需要一个一个解决冲突,而且产生大量的Merge 的提交,非常不美观与无用。

或者我们在master分子下使用git rebase development,这个时候git会根据开发分支做的修改,这些命令会把”development”分支里的每个提交(commit)取消掉,并且把它们临时 保存为补丁(patch)(这些补丁放到”.git/rebase”目录中),然后把”master”分支更新 为最新的”origin”分支,最后把保存的这些补丁应用到”master”分支上。

这其中如果有冲突会一个一个的去解决具体就是执行git rebase –continue/–skip/–abort

$ git checkout master
$ git rebase project/master
$ git push

最后我们就可以顺利的提交了,master分支没有多余的Merge提交!如果我们继续翻译工作,那么切换到development分支,然后进行git rebase master操作。开发分支更新完毕后,我们继续翻译工作!

当然,我们也可以使用git pull –rebase,这个相当于重建了master主线分支,就是上面操作的集合!

 

啰嗦半天就是我们要尽量使用git rebase 方式,减少大量不必要的Merge提交!

http://blog.csdn.net/hudashi/article/details/7664631
http://git-scm.com/book/zh/ch3-2.html

DRAM页映射到BANK中的两种形式

July 4th, 2015 by JasonLe's Tech 4,758 views

在之前的一篇博文上,我分析了DRAM的物理结构与从bank中存取数据的方式。这里我继续这个话题,并引入一种新式的内存存取数据的方式:Permutation-based Page Interleaving。

当CPU发送物理内存地址给内存的时候需要首先发送给memory controller,然后由memory controller将其翻译成内存地址,也就是以DIMM Rank Chip Bank为单位的地址形式,如下图所示:20150421162433 我们知道CPU对Memory的读写要经过cache,在这里可以认为CPU与cache是一体的,读到了cache也就等于被CPU使用。传统的memory与cache的关系是多路并联、单路并联,也就是说一系列的内存物理地址,被分割成了tag段,每个tag段内的每个内存地址映射到不同cache line中。

cache-related

也就是说不同tag段的相同set index会被映射到同一个cache line中,而最后的block offset正是cache line的大小,也就是说这个物理地址中的内容放到cache line中正是cache line的大小,然后我们将这个位数变化一下,就是传统物理地址对应三个部分,page index,bank index,page offset。而其中bank index的信息是具体的硬件实现(硬件生产商不会对外公布这种参数),一般我们使用软件的方式来测试bank位!

这里的page不是我们常规意义上的物理页,而是第一个图中以bank 与row组成的一行称为page。

下面我们来做一个推定:比如tag值不同,后面set index,block offset两个字段相同的两个物理地址,必定映射同一个cache line。转换到下面的DRAM的页模式,set index相同,那么bank index一定相同,也就是说两个数据处于同一个bank中,又因为tag不同,那么page index不同,意味着不属于同一个row,而每个bank只有一个row buffer,这样必然导致L3(CPU最后一级cache)无法命中,导致 L3 cache conflict miss!

举例:

double x[T], y[T], sum;
for(i = 0; i < T; i++);
     sum += x[i] * y[i];

若x[0] 和y[0] 的距离是L3 cache 大小的倍数,必然会导致读取x[i],y[i]发生L3 cache conflict miss!CPU不得不每次讲L3 cache清空,读取新的数据,这样必然大大增加了数据访问的延迟!

为了避免这种情况工程师提出了一种新的内存page交叉访问方式Permutation-based Page Interleaving Scheme,首先我们看这个算法如何降低了 L3 cache conflict miss。

permutation

 

他在传统物理地址对应的banks存取上,使用tag的一些位与bank index做异或运算,这样就可以大大降低内存页交叉访问带来的L3 conflict miss,不同tag的相同bank index会被映射到不同的bank中!

DRAM

而我们知道每个bank都有一个row buffer,只要我们保证程序局部性存取的数据在不同row buffer中,这样就意味着row buffer的数据可以长时间存在,不同频繁的清空,当需要这些数据,直接从row buffer中读取到memory controller就可以了。而程序的局部性并没有被破坏,仍然存在在一个DRAM page!

DRAM_bank

 

这两种方式长时间存在于DRAM交叉存取bank中,有时候我们在测试Bank位会遇到这两种方式,因此我们必须使用特定工具去检测决定bank位的index,如过检测bank index非常多,就要考虑是否是XOR交叉存取方式了!

 

 

论文:http://ieeexplore.ieee.org/stamp/stamp.jsp?arnumber=898056

进程控制踩过的坑

July 1st, 2015 by JasonLe's Tech 1,300 views

1. fork()与vfork()非常相似,但是使用场景有一些不同,vfork()主要用来创建子进程,然后执行exec()一个新的程序,不会发生COW(fork()出来的子进程exec()会产生COW,所以vfork()更加快速),vfork()可以保证子进程先运行,调用exec()、exit之后才会被调度,如果子进程依赖父进程产生一些动作的话,可能产生死锁

2. vfork()在父进程空间中运行,这个导致子进程可以修改父进程的值!

3. 之前在C/S模型下Server 中fork()的健壮性中说过,fork()产生的子进程退出后,发送SIGCHLD信号,如果不及时使用wait方式处理的话,会产生僵尸进程。反过来,如果父进程先停止,那么子进程退出时,会向init进程发送SIGCHLD信号。

4. wait()与waitpid()都可以接受终止子进程发送的信号,wait()是waitpid()的简化版本,wait()返回任意一个终止子进程的状态,waitpid()可以接受特定子进程的信号。

5. 按照之前第3条所叙述的,我们可以利用这个init领养子进程规则让init管理孤儿进程,这里有一个技巧:fork()两次!

int main(void)
{
        pid_t pid;
        if ((pid = fork()) < 0) {
             err_sys("fork error");
        } else if (pid == 0) { /* first child */
             if ((pid = fork()) < 0)
                  err_sys("fork error");
             else if (pid > 0)
                  exit(0); /* parent from second fork == first child */
//这个exit(0)退出的就是第一次fork()出来的子进程,也是第二次fork()的
//父进程,当这个进程退出后,也就意味着第二次fork()出来的子进程变成
//孤儿进程,直接由init接管!
/*
* We’re the second child; our parent becomes init as soon
* as our real parent calls exit() in the statement above.
* Here’s where we’d continue executing, knowing that when
* we’re done, init will reap our status.
*/
//下面这段是第二次fork()出来子进程执行的代码段
            sleep(2);//必须保证第二次fork()出来的父进程先退出!
            printf("second child, parent pid = %ld\n", (long)getppid());
            exit(0);
        }
        if (waitpid(pid, NULL, 0) != pid) /* wait for first child */
            err_sys("waitpid error");
/*
* We’re the parent (the original process); we continue executing,
* knowing that we’re not the parent of the second child.
*/
        exit(0);
}

这个代码设计的很精巧,开始我没有看懂,仔细分析才可以。

6. 对于某些父子进程拥有竞争条件的代码,必须要使用信号机制或者管道机制实现父子进程同步,其中TELL_WAIT(),TELL_PARENT(),WAIT_PARENT(),TELL_CHILD(pid),WAIT_CHILD()可以使用不同的机制定义,从而实现父子进程的有序执行!

     TELL_WAIT(); /* set things up for TELL_xxx & WAIT_xxx */
     if ((pid = fork()) < 0) {
      err_sys("fork error");
     } else if (pid == 0) { /* child */
     /* child does whatever is necessary ... */
     TELL_PARENT(getppid()); /* tell parent we’re done */
     WAIT_PARENT(); /* and wait for parent */
     /* and the child continues on its way ... */
     exit(0);
     }
    /* parent does whatever is necessary ... */
    TELL_CHILD(pid); /* tell child we’re done */
    WAIT_CHILD(); /* and wait for child */
    /* and the parent continues on its way ... */
    exit(0);

7. 使用信号机制来实现父子进程同步的话,可以自定义SIGUSR1,SIGUSR2的方式,在main()开始部位,设置中断处理函数,函数修改一个全局volatile sig_atomic类型的变量sigflag,然后在等待函数中,轮训挂起等待信号,直至进程处理信号,跳出这个循环:

while (sigflag == 0)
       sigsuspend(&zeromask); /* and wait for parent */
sigflag = 0;

8.使用pipe,可以在等待函数中读管道,在通知函数中写管道,达到父子进程的同步!

void TELL_PARENT(pid_t pid)
{
    if (write(pfd2[1], "c", 1) != 1)
        err_sys("write error");
}
void WAIT_PARENT(void)
{
    char c;
    if (read(pfd1[0], &c, 1) != 1)
        err_sys("read error");
    if (c != ’p’)
        err_quit("WAIT_PARENT: incorrect data");
}

 

 

参考:
APUE P185,P270,P402

字符串切割问题求解

June 30th, 2015 by JasonLe's Tech 1,109 views

我在做Leetecode的一道题时,遇到了一道切割字符串求解回文字符串的题目,题目大意如下:

Given a string s, partition s such that every substring of the partition is a palindrome.

Return all possible palindrome partitioning of s.

For example, given s = "aab",
Return

  [
    ["aa","b"],
    ["a","a","b"]
  ]

这个时候我们需要使用DFS算法,进行深搜,但是这个里我们需要注意的一个问题是,每个字符只能用一次,而且不能使用拼接的方式,需要直接从string s中截取子字符串,所以我们使用s.substr(start,count)的方式。这个不同于之前的combinationSum的题目,需要有一个中间target保存。我们只需要传入下面几个参数即可,使用step来标示当前指向s的开头index,i为结束index。

void DFS(string &s,vector<vector<string>> &result,vector<string> &path,int step){
		if(step>=s.size()){
			result.push_back(path);
			return;
		}
		for(auto i = step;i<s.size();i++){
			if(is_palindrome(s,step,i)){
				path.push_back(s.substr(step,i-step+1));
				DFS(s,result,path,i+1);
				path.pop_back();
			}
		}
	}

	bool is_palindrome(string &s,int start,int end){
		while(start < end){
			if(s[start]!=s[end])
				return false;
			start++;
			end--;
		}
		return true;
	}
};

一个长度为n 的字符串,有n-1 个地方可以砍断,每个地方可断可不断,因此复杂度为O(2^(n-1))

 

https://leetcode.com/problems/palindrome-partitioning/