1.1 Oom
1.1.1 简介
Oom的全称是out-of-memory,是内核在处理系统内存不足而又回收无果的情况下采取的一种措施,内核会经过选择杀死一些进程,以释放一些内存,满足当前内存申请的需求。所以oom是一种系统行为,对应到memcg的oom,其原理和动机跟全局oom是一样的,区别只在于对象的不同,全局oom的对象是整个系统中所有进程,而memcg oom只针对memcg中的进程(如果使能了hierarchy,还包括所有子memcg中的进程),这里的对象主要是指oom时内核选择从哪些进程中杀死一些进程,所以memcg的oom只可能杀死属于该memcg的进程。
1.1.2 实现
跟全局oom一样,memcg的oom也分成select_bad_process和oom_kill_process两个过程:
a. select_bad_process找出该memcg下最该被kill的进程(如果memcg设置了hierarchy,也会考虑子memcg下的进程);
b. oom_kill_process杀掉选中的进程及与其共用mm的进程(杀进程的目的是释放内存,所以当然要把mm的所有引用都干掉);
对于实现的代码细节,不同的版本代码演进较快,之前memcg中会直接调用内核的select_bad_process和oom_kill_process,在最新的3.10中,memcg实现了自己的select_bad_process,即在memcg的代码中自己来找到要杀死的进程。虽然函数调用不同,但是找到要杀死的进程的原理都是类似的,select的过程会给memcg(或及其子memcg)下的每个进程打一个分,得分最高者被选中。评分因素每个版本不尽相同,主要会考虑以下因素:
a. 进程拥有page和swap entry越多,分得越高;
b. 可以通过/proc/$pid/oom_score_adj进行一些分值干预;
c. 拥有CAP_SYS_ADMIN的root进程分值会被调低;
kill的过程比较简单,简单的说就是向要杀死的进程发送SIGKILL信号,但其中依然有一些细节:
a. 如果被选中的进程有一些子进程跟他不共用同一个mm,并且也是可以被杀死的,那么就挑选这些子进程中badness得分最高的一个来代替父进程被杀死,这样是为了确保我们在释放内存的同时失去更少的东西;
b. 上面已经说了,oom_kill的过程会杀死选中的进程及与其共用mm的进程,所以会遍历所有用户态进程,找到并杀死与选中进程共用同一个mm的进程;
c. 遍历进程的过程中,会过滤掉通过/proc/$pid/oom_score_adj干预的不可被oom_kill掉的进程(目前是设置为OOM_SCORE_ADJ_MIN的进程);
在oom的过程中,另外值得一说的是其中的同步过程。
oom过程会向选中的进程发送SIGKILL信号,但是距离进程处理信号、释放空间,还是需要经历一定时间的。如果系统负载较高,则这段时间内很可能有其他上下文也需要却得不到page,而触发新的oom。那么如果大量oom在短时间内爆发,可能会大面积杀死系统中的进程,带来一场浩劫。
所以oom过程需要同步:在给选中的进程发送SIGKILL后,会设置其TIF_MEMDIE标记。而在select被杀死进程的过程中如果发现记有TIF_MEMDIE的进程,则终止当前的oom过程,并等待上一个oom过程结束。这样做可以避免oom时大面积的kill进程。
而在进程退出时,会先将task->mm置为NULL,再mmput(mm)释放掉引用计数,从而导致内存空间被释放(如果引用计数减为0的话)。所以,只要task->m