OOM全称是Out Of Memory,指的是kernel因分配不出内存而报的错误,同时会触发kernel调用OOM killer杀进程来解除这种状况。
OOM发生的条件一般有两个:
1. VM里面分配不出更多的page(注意linux kernel是延迟分配page策略,及用到的时候才alloc;所以malloc + memset才有效)。
2. 用户地址空间不足,这种情况在32bit机器上及user space超过了3GB,在64bit机器上不太可能发生。
下面通过分析linux kernel中oom_kill.c代码来了解一下OOM的机制。OOM在kernel中对应的函数有两个:out_of_memory()和pagefault_out_of_memory(),最终调用的都是__out_of_memory()。
__out_of_memory()做两件事情:
1. 调用select_bad_process选择一个要kill的进程;
2. 调用oom_kill_process杀死select出来的进程。
select_bad_process函数扫描整个进程列表:
1) 跳过kernel thread、没有占用mem的进程、INIT进程、以及被设置为OOM_DISABLE的进程;可以通过设置进程的 /proc/<pid>/oom_adj 来调整oom_adj的值,oom_adj范围是[-17, 15],值越大越容易被oom kill掉,设为OOM_DISABLE(-17)的进程不会被oom。
2) 对其它的进程调用badness()函数来计算相应的score,score最高的将被选中。badness()函数计算score (points)的因子有下面几个:
a) score起始为该进程占用的total_vm;
points = mm->total_vm;
b) 如果该进程有子进程,子进程独自占用的total_vm/2加到本进程score;
points += child->mm->total_vm/2 + 1;
c) score和进程的cpu_time以及run_time成反比;
points /= int_sqrt(cpu_time);
points /= int_sqrt(int_sqrt(run_time));
d) nice大于0的进程,score翻倍;
if (task_nice(p) > 0) points *= 2;
e) 对设置了超级权限的进程和直接磁盘交互的进程降低score;
if (CAP_SYS_ADMIN | CAP_SYS_RESOURCE | CAP_SYS_RAWIO) points /= 4;
f) 如果和current进程在内存上没有交集的进程降低score;
if (!has_intersects_mems_allowed(p)) points /= 8;
g) 最后是根据该进程的oom_adj计算最终的score;
points <<= abs(oom_adj);
oom_kill_process函数的功能很简单,就一句话:
force_sig(SIGKILL, p);
可以看到发的是SIGKILL信号,其实就是执行kill -9 pid,因为SIGKILL是不能被捕获的。
可以通过下面两个参数来配置OOM策略:
/proc/sys/vm/overcommit_memory
/proc/sys/vm/overcommit_ratio
overcommit_memory取值为[0-2]:
0:表示按启发模式进行overcommit(可以提交超过物理内存大小的alloc page申请),也是默认的设置;
1:表示总是允许overcommit,这种模式最容易触发oom;
2:表示不能overcommit。这种模式下,最大的User Space限制在:SS + RAM*(r/100),SS是swap大小,r就是overcommit_ratio设置的值,范围为:[0-100]。
有一种mem_notify的机制在内存不足时可以给应用进程发信号,让应用进程去释放内存,如果不能释放再调用oom killer,但在linux 2.6.28以后的版本都不能用了,所以避免OOM还是做好应用的内存管理以及监控。
参考资料:
1. http://lxr.linux.no/linux+v2.6.32.60/mm/oom_kill.c
2. http://lwn.net/Articles/267013/
3. http://www.kernel.org/doc/man-pages/online/pages/man5/proc.5.html