一、OOM 是什么?

Linux 内核有个机制叫OOM killer(Out-Of-Memory killer),该机制会监控那些占用内存过大,尤其是瞬间很快消耗大量内存的进程,为了防止内存耗尽而内核会把该进程杀掉。
Linux用户内存都是读写时分配,所以系统发现需要内存基本上都是发生在handle_mm_fault()的时候(其他特殊流程类似,这里忽略),handle_mm_fault()要为缺的页分配内存,就会调alloc_pages()系列函数,从而调prepare_alloc_pages(),进而进入__alloc_pages_direct_reclaim(),这里已经把可以清到磁盘上的缓冲都清了一次了。这样之后还是分配不到内存,就只好进入OMM Killer了(pagefault_out_of_memory())。
到了这种状态,系统中的内存只有可能被正在运行的进程和内核占据了,大家都不让,系统就只有死。内核是官家,进程是商家,官家不能杀,只好杀商家,商家杀一个也是杀,杀十个也是杀,那就杀个最胖的,少拉点仇恨。也就只能这样了,换你,你能怎么选?
Unix/Linux的内存分配策略是lazy的,申请的时候不会分配物理内存,只有在使用的时候才分配,为了尽可能地提高内存地利用效率,系统大部分情况下都会“答应”申请内存的要求。
最终OOM-Killer是通过/proc//oom_score这个值来决定哪个进程被杀死。这个值是系统综合进程的内存消耗量、CPU时间(utime+stime)、存活时间(utime - start_time)和oom_adj计算出的,消耗内存越多oom_score值越高,存活时间越长值越低。

二、OOM机制实现

内核在触发OOM机制时会调用到out_of_memory函数,此函数的调用顺序如下:

  1. __alloc_pages
  2. |-->__alloc_pages_nodemask
  3. |--> __alloc_pages_slowpath
  4. |--> __alloc_pages_may_oom
  5. | --> out_of_memory

以上函数__alloc_pages_may_oom在调用之前会先判断oom_killer_disabled的值,如果有值,则不会触发OOM机制;
Bool型变量oom_killer_disabled定义在文件mm/page_alloc.c中,并没有提供外部接口更改此值,但是在内核中此值默认为0,表示打开OOM-kill。
Linux中内存都是以页的形式管理的,所以不管是怎么申请内存,都会调用alloc_page函数,最终调用到函数out_of_memory,触发OOM机制。
下面简要说明函数out_of_memory流程。
1. 判断全局变量sysctl_panic_on_oom==2,成立则直接panic
2. 获取/proc/sys/vm/overcommit_memory中的配置的值:
a) 为2:直接将当前进程杀死;
b) 为0:判断sysctl_panic_on_oom是否有值,有值直接panic,否则调用out_of_memory函数;
c) 为1:调用
out_of_memory函数,其流程如下:
linux内存管理之OOM机制 - 图1
以上流程中值得关注的地方有
1. 变量sysctl_panic_on_oom,此值可以通过/proc/sys/vm/panic_on_oom设置,默认为0;
2. 变量sysctl_oom_kill_allocating_task,此值可以通过/proc/sys/vm/oom_kill_allocating_task设置,默认为0;
3. 当内存资源紧张,同时也没有可以杀死的进程时,系统会panic;
4. 杀死进程需要调用函数oom_kill_process,此函数核心步骤就是先杀死指定进程的子进程p->children,然后杀死指定进程p,具体杀死进程的函数为oom_kill_task。
函数oom_kill_task
此函数流程如下:
linux内存管理之OOM机制 - 图2

以上流程中值得关注的地方有
1. p->signal->oom_adj的值,此值可以通过/proc//oom_adj设置;
2. 变量sysctl_would_have_oomkilled,此值可以通过/proc/sys/vm/would_have_oomkilled设置,默认为0。

三、总结

通过前面介绍,可以看到Linux中OOM机制是否会触发,与以下几个变量有关:

  1. oom_killer_disabled:此值虽然没有提供外部接口更改,但是可以通过给内核打patch,增加一个外部接口,使此值可动态改变
  2. sysctl_panic_on_oom:此值一般不建议设置,因为如果申请不到内存,系统就panic了;
  3. sysctl_oom_kill_allocating_task:此值设置为1,类似给接口/proc/sys/vm/overcommit_memory设置值为2,被设置后,某个进程要申请内存失败时直接将该进程杀死;
  4. sysctl_would_have_oomkilled:此值设置为1,也不会真正的去杀死进程
  5. p->signal->oom_adj:此值可以通过接口/proc//oom_adj设置值为-17,表示该进程不可被杀死。
  6. Linux下每个进程都有一个OOM权重,在/proc//oom_adj里面,取值是-17到+15(为-17此进程不会被杀掉),取值越高,越容易被杀掉。