Linux下面有个特性叫OOM killer(Out Of Memory killer),这个东西会在系统内存耗尽的情况下跳出来,选择性的干掉一些进程以求释放一些内存。相信广大从事Linux服务端编程的农民工兄弟们或多或少遇到过(人在江湖漂,哪有不挨刀啊)。典型的情况是:某天机器突然登不上了,能ping通,但是ssh死活连不了。原因是sshd进程被OOM killer干掉了(泪流满面)。重启机器后查看系统日志会发现血淋淋的Out of Memory: Killed process ×××、Out of Memory: Killed process 〇〇〇。一篇狼藉,惨不忍睹。具体的记录日志是在/var/log/messages中,如果出现了Out of memory字样,说明系统曾经出现过OOM!
二、When(什么时候出现)
Linux下允许程序申请比系统可用内存更多的内存,这个特性叫Overcommit。这样做是出于优化系统考虑,因为不是所有的程序申请了内存就立刻使用的,当你使用的时候说不定系统已经回收了一些资源了。不幸的是,当你用到这个Overcommit给你的内存的时候,系统还没有资源的话,OOM killer就跳出来了。
参数/proc/sys/vm/overcommit_memory可以控制进程对内存过量使用的应对策略
-
- 1.当overcommit_memory=0 允许进程轻微过量使用内存,但对于大量过载请求则不允许,也就是当内存消耗过大就是触发OOM killer。
- 2.当overcommit_memory=1 永远允许进程overcommit,不会触发OOM killer。
- 3.当overcommit_memory=2 永远禁止overcommit,不会触发OOM killer。
三、相关系统参数
3.1 overcommit_memory
参数/proc/sys/vm/overcommit_memory可以控制进程对内存过量使用的应对策略
-
- 1.当overcommit_memory=0 允许进程轻微过量使用内存,但对于大量过载请求则不允许,也就是当内存消耗过大就是触发OOM killer。
- 2.当overcommit_memory=1 永远允许进程overcommit,不会触发OOM killer。
- 3.当overcommit_memory=2 永远禁止overcommit,不会触发OOM killer。
3.2 panic_on_oom
参数:panic_on_oom: 用来控制当内存不足时该如何做。
查看:cat /proc/sys/vm/panic_on_oom
值为0:内存不足时,启动 OOM killer。
值为1:内存不足时,有可能会触发 kernel panic(系统重启),也有可能启动 OOM killer。
值为2:内存不足时,表示强制触发 kernel panic,内核崩溃GG(系统重启)。
3.3 oom_kill_allocating_task
参数:oom_kill_allocating_task: 用来决定触发OOM时先杀掉哪种进程
cat /proc/sys/vm/oom_kill_allocating_task
值为0:会 kill 掉得分最高的进程。
值为非0:会kill 掉当前申请内存而触发OOM的进程。
当然,一些系统进程(如init)或者被用户设置了oom_score_adj的进程等可不是说杀就杀的。
3.4 oom_dump_tasks
参数:oom_dump_tasks:用来记录触发OOM时记录哪些日志
cat /proc/sys/vm/oom_dump_tasks
oom_dump_tasks参数可以记录进程标识信息、该进程使用的虚拟内存总量、物理内存、进程的页表信息等。
值为0:关闭打印上述日志。在大型系统中,可能存在上千进程,逐一打印使用内存信息可能会造成性能问题。
值为非0:有三种情况会打印进程内存使用情况。
1、由 OOM 导致 kernel panic 时;
2、没有找到符合条件的进程 kill 时;
3、找到符合条件的进程并 kill 时。
3.5 oom_adj、oom_score_adj 和 oom_score
参数:oom_adj、oom_score_adj 和 oom_score:用来控制进程打分(分数越高,就先杀谁)
这三个参数的关联性比较紧密,都和具体的进程相关,位置都是在 /proc/进程PID/ 目录下。
内核会对进程打分(oom_score),主要包括两部分,系统打分和用户打分。系统打分就是根据进程的物理内存消耗量(进程自身的空间、swap空间、页缓存空间);用户打分就是 oom_score_adj 的值。如果用户指定 oom_score_adj 的值为 -1000,也就是表示禁止 OOM killer 杀死该进程。
用户可以通过调整 oom_score_adj 的值来决定最终 oom_score 的值,oom_score_adj 的取值范围是 -1000~1000,为0时表示用户不调整 oom_score。另外,root进程拥有3%的内存使用特权,因此做最终 oom_score 计算时需要减去这些内存使用量。
oom_adj是一个旧的接口参数,其功能类似oom_score_adj,为了兼容,目前仍然保留这个参数,当操作这个参数的时候,kernel实际上是会换算成oom_score_adj。
四、OOM机制详解
4.1 OOM分析
oom_killer(out of memory killer)是Linux内核的一种内存管理机制,在系统可用内存较少的情况下,内核为保证系统还能够继续运行下去,会选择杀掉一些进程释放掉一些内存。通常oom_killer的触发流程是:进程A想要分配物理内存(通常是当进程真正去读写一块内核已经“分配”给它的内存)->触发缺页异常->内核去分配物理内存->物理内存不够了,触发OOM。
一句话说明oom_killer的功能:当系统物理内存不足时,oom_killer遍历当前所有进程,根据进程的内存使用情况进行打分,然后从中选择一个分数最高的进程,杀之取内存。
4.2 函数解析
4.2.1 out_of_memory函数
oom_killer的处理主要集中在mm/oom_kill.c。
核心函数为out_of_memory,函数处理流程:
-
- 通知系统中注册了oom_notify_list的模块释放一些内存,如果从这些模块中释放出了一些内存,那么皆大欢喜,直接结束oom killer流程,回收失败, 那只有进入下一步开始oom_killer了;
- 触发oom killer通常是由当前进程进行内存分配所引起,而如果当前进程已经挂起了一个SIG_KILL信号或者正在退出,直接选中当前进程,否则进入下一步;
- check_panic_on_oom检查系统管理员的态度,看oom时是进行oom killer还是直接panic掉(系统崩溃重启),如果进行oom killer,则进入下一步;
- 如果系统管理员规定,谁引起oom,杀掉谁,那就杀掉正在尝试分配内存的进程,oom killer结束,否则进入下一步;
- 调用select_bad_process选中合适进程,然后调用oom_kill_process杀死选中进程,如果不幸select_bad_process没有选出任何进程,那么内核走投无路,只有panic了。
out_of_memory函数位于linux-source-4.15.0\mm\oom_kill.c中,源码如下:
1 /** 2 * out_of_memory - kill the "best" process when we run out of memory 3 * @oc: pointer to struct oom_control 4 * 5 * If we run out of memory, we have the choice between either 6 * killing a random task (bad), letting the system crash (worse) 7 * OR try to be smart about which process to kill. Note that we 8 * don't have to be perfect here, we just have to be good. 9 * 当我们内存不足时,我们有两种处理方案:随机杀死一个任务,这可能会导致系统崩溃; 10 * 或者尝试有策略地选出值得杀死的那个任务。我们没有必要做到最好,但我们只需尽力把这事做好”。 11 * -- 潜台词:这件事我们至今都没找到一个完美算法,误杀进程我们不背锅。 12 */ 13 bool out_of_memory(struct oom_control *oc) 14 { 15 unsigned long freed = 0; 16 enum oom_constraint constraint = CONSTRAINT_NONE; 17 18 /** 19 * 系统没有配置OOM Killer 20 */ 21 if (oom_killer_disabled) 22 return false; 23 24 if (!is_memcg_oom(oc)) { 25 /** 26 * 通知注册在oom_notify_list上的模块,释放一些内存出来,如果成功,那就不用启动oom killer了 27 */ 28 blocking_notifier_call_chain(&oom_notify_list, 0, &freed); 29 if (freed > 0) 30 /** 31 * Got some memory back in the last second. 32 * 如果从这些模块中释放出了一些内存,那么皆大欢喜,直接结束oom killer流程 33 */ 34 return true; 35 } 36 37 /* 38 * If current has a pending SIGKILL or is exiting, then automatically 39 * select it. The goal is to allow it to allocate so that it may 40 * quickly exit and free its memory. 41 * 如果当前进程有一个挂起的SIGKILL未决信号或者正在退出,则自动选择它, 42 * 目标是允许它分派以便它可以尽快的的退出和释放掉它所占有的内存 43 * 44 * task_will_free_mem函数其实是去检查一下当前有没有进程挂了,有的话就回收他的内存, 45 * 回收内存后那就不需要oom killer杀进程了 46 */ 47 if (task_will_free_mem(current)) { 48 mark_oom_victim(current); 49 wake_oom_reaper(current); 50 return true; 51 } 52 53 /* 54 * The OOM killer does not compensate for IO-less reclaim. 55 * pagefault_out_of_memory lost its gfp context so we have to 56 * make sure exclude 0 mask - all other users should have at least 57 * ___GFP_DIRECT_RECLAIM to get here. But mem_cgroup_oom() has to 58 * invoke the OOM killer even if it is a GFP_NOFS allocation. 59 * OOM 杀手不补偿 IO-less 回收。 60 * pagefault_out_of_memory 丢失了它的 gfp 上下文,所以我们必须确保排除 0 掩码 - 所有其他用户应该至少有 61 * ___GFP_DIRECT_RECLAIM 才能到达这里。但是 mem_cgroup_oom() 必须调用 OOM 杀手,即使它是 GFP_NOFS 分配。 62 */ 63 if (oc->gfp_mask && !(oc->gfp_mask & __GFP_FS) && !is_memcg_oom(oc)) 64 return true; 65 66 /* 67 * Check if there were limitations on the allocation (only relevant for 68 * NUMA and memcg) that may require different handling. 69 * 检查可能需要不同处理的分配限制(仅与 NUMA 和 memcg 相关)。 70 * 71 * 对于有NUMA节点,会有节点间的限制 72 */ 73 constraint = constrained_alloc(oc); 74 if (constraint != CONSTRAINT_MEMORY_POLICY) 75 oc->nodemask = NULL; 76 77 /** 78 * 检查/proc/sys/vm/panic_on_oom的设置,看看系统管理员是什么态度 79 * 值为0:内存不足时,启动 OOM killer。 80 * 值为1:内存不足时,有可能会触发 kernel panic(系统重启),也有可能启动 OOM killer。 81 * 值为2:内存不足时,表示强制触发 kernel panic,内核崩溃GG(系统重启)。 82 */ 83 check_panic_on_oom(oc, constraint); 84 85 /** 86 * /proc/sys/vm/oom_kill_allocating_task为true的时候,直接kill掉当前想要分配内存的进程(此进程能够被kill时) 87 * 当系统内存不足时,OOM Killer要选择哪个进程来杀死呢?怎样的进程才符合被杀死的条件? 88 * 系统有如下选择: 89 * 谁触发了OOM就干掉谁; 90 * 谁最坏就干掉谁。 91 * oom_kill_allocating_task参数就是用于控制OOM Killer选择杀死谁的。 92 * 当oom_kill_allocating_task为0时就选择2,谁最坏就杀死谁; 93 * 当oom_kill_allocating_task为其他值时,就选择1,谁触发的OOM就杀死谁。 94 */ 95 if (!is_memcg_oom(oc) && sysctl_oom_kill_allocating_task && 96 current->mm && !oom_unkillable_task(current, NULL, oc->nodemask) && 97 current->signal->oom_score_adj != OOM_SCORE_ADJ_MIN) { 98 get_task_struct(current); 99 oc->chosen = current; 100 oom_kill_process(oc, "Out of memory (oom_kill_allocating_task)"); 101 return true; 102 } 103 104 /** 105 * 执行到此处,内核开始对所有进程进行审判,择其最坏者杀之 106 */ 107 select_bad_process(oc); 108 /* Found nothing?!?! Either we hang forever, or we panic. */ 109 /** 110 * 找了一圈,没有找到任何一个进程可以被杀死(全都是背景深厚的进程…),内核走投无路,自杀 111 */ 112 if (!oc->chosen && !is_sysrq_oom(oc) && !is_memcg_oom(oc)) { 113 dump_header(oc, NULL); 114 panic("Out of memory and no killable processes...\n"); 115 } 116 117 /** 118 * 幸运的找到了一个合适的进程,去kill它,释放一点内存出来 119 */ 120 if (oc->chosen && oc->chosen != (void *)-1UL) { 121 oom_kill_process(oc, !is_memcg_oom(oc) ? "Out of memory" : 122 "Memory cgroup out of memory"); 123 /* 124 * Give the killed process a good chance to exit before trying 125 * to allocate memory again. 126 * 如果有进程被选中了kill掉,且又不是当前进程,那主动让出CPU,给被选中 127 * 的进程一些时间去处理后事,结束它自己的生命 128 */ 129 schedule_timeout_killable(1);/*主动让出cpu*/ 130 } 131 return !!oc->chosen; 132 }