上一篇文章我们总结了OOM的各种操作用法,会用和深入理解还有一段距离。本次就深入源码,来好好研究一下斩杀线OOM是如何设计的。第一步必然是走到触发斩杀线OOM的函数,out_of_memory,即当前超出内存资源限制了,怎么办注意:以下源码属于类伪代码,非全部源码,***里面说明了对应上一篇文章的哪一部分out_of_memory(mm/oom_kill.c)[ if (oom_killer_disabled) return false; if (!is_memcg_oom(oc) && !oom_cpuset_eligible(task, oc)) { // 不是memcg_oom且未被选中,忽略 mem_cgroup_account_oom_skip(task, oc); return 0; } if (!is_memcg_oom(oc) && sysctl_oom_kill_allocating_task && current->mm && !oom_unkillable_task(current) && oom_cpuset_eligible(current, oc) && current->signal->oom_score_adj != OOM_SCORE_ADJ_MIN) { // 不是memcg oom且sysctl_oom_kill_allocating_task非0且不是不可杀进程且且被选中且未设置oom_score_adj为-1000 // ***谁触发的OOM,就杀谁,上篇文章表中第五项oom_kill_allocating_task*** get_task_struct(current); oc->chosen = current; **oom_kill_process(oc, "Out of memory (oom_kill_allocating_task)");** return true; } //内存配置相关查询,不同配置走不同策略尝试回收部分内存 select_bad_process(oc)(mm/oom_kill.c) [ if cgroup mem oom: mem_cgroup_select_bad_process(oc)(mm/memcontrol.c) [ // ***cgroup优先级策略,上篇文章表中第一项cgoup优先级*** if(oc->use_priority_oom) { mem_cgroup_select_victim_cgroup [ if (!memcg->use_hierarchy) return memcg // 迭代遍历选择优先级最低的memcg return 优先级最低的memcg ] } mem_cgroup_scan_tasks(victim, **oom_evaluate_task**, oc) // 计算分数的逻辑与非cgroup的oom一致 ] else: for_each_process(p) if (oom_evaluate_task(p, oc))(mm/oom_kill.c) [ ... if (oom_task_origin(task)) { // 设置了kill触发者,固选择将触发oom的进程kill // ***谁触发的OOM,就杀谁,上篇文章表中第五项oom_kill_allocating_task*** } points = oom_badness(task, oc->totalpages); [ // ***上篇文章表中第三项oom_score_adj*** adj = (long)p->signal->oom_score_adj; if task是不可杀死的进程 or task是锁定内存的进程 return LONG_MIN if oom_score_adj == -1000: return LONG_MIN points = 常驻内存集(rss)+页表+swap内存比例 adj *= 总页表 / 1000 points += adj return points ] if (points == LONG_MIN) { // 忽略该进程,不进行oom kill } ] break; ] ... // 杀掉被选择的进程 oom_kill_process ]
还有两个重要的策略,主要集中在内存策略是否有交集,所以还需要看一下重要的函数oom_cpuset_eligible,即判断内存是否有交集来决定杀死谁oom_cpuset_eligible(struct task_struct *start, struct oom_control *oc)[ const nodemask_t *mask = oc->nodemask if (is_memcg_oom(oc)) return true; for_each_thread(start, tsk) { if (mask) { // 如果这是由内存策略(mempolicy)约束的 OOM(内存耗尽)情况,那么任务(tsk)的 cpuset(CPU 集)是无关紧要的。仅当任务的内存策略与当前内存策略存在交集时才返回 true(真),否则它可能会被不必要地终止 // ***上篇文章表中第四项*** ret = mempolicy_nodemask_intersects(tsk, mask); [ // 如果任务(tsk)的内存策略为“default”NULL,则返回“true”表示采用默认策略。 否则,对于“bind”或“interleave”策略,检查掩码与策略节点掩码是否存在交集。对于“perferred”或“local”策略,由于回退时可能在其他地方分配内存,因此始终返回 true mempolicy = tsk->mempolicy if (!mempolicy) return true switch(mempolicy->mode){ .... nodes_intersects(mempolicy->v.nodes, \*mask) [ bitmap_intersects() // 判断两个位图(bitmap)是否存在交集,即两个位图中是否有至少一个相同的位被设置为1 ] } ] } else { // 这不是一个由内存策略(mempolicy)约束的 OOM(内存耗尽)情况,因此仅检查任务(tsk)的 cpuset(CPU 集)所允许的内存节点(mems) // ***上篇文章表中第二项*** ret = cpuset_mems_allowed_intersects(current, tsk); [ // 内存分配范围与当前触发 OOM 的进程有重叠 // 1. 获取两个任务的 `mems_allowed`(cpuset 允许的内存节点掩码) // 2. 对两个掩码进行按位与(AND)操作 // 3. 如果结果非零,则表示有交集 // 4. 有交集返回true ] } if (ret) break; } return ret]