Linux OOM killer

做为Linux下的程序员,有时不得不面对一个问题,那就是系统内存被用光了,这时当进程再向内核申请内存时,内核会怎么办呢?程序里面调用的malloc函数会返回null吗?html

为了处理内存不足时的问题,Linux内核发明了一种机制,叫OOM(Out Of Memory) killer,经过配置它能够控制内存不足时内核的行为。linux

OOM killer

当物理内存和交换空间都被用完时,若是还有进程来申请内存,内核将触发OOM killer,其行为以下:程序员

1.检查文件/proc/sys/vm/panic_on_oom,若是里面的值为2,那么系统必定会触发panic
2.若是/proc/sys/vm/panic_on_oom的值为1,那么系统有可能触发panic(见后面的介绍)
3.若是/proc/sys/vm/panic_on_oom的值为0,或者上一步没有触发panic,那么内核继续检查文件/proc/sys/vm/oom_kill_allocating_task
3.若是/proc/sys/vm/oom_kill_allocating_task为1,那么内核将kill掉当前申请内存的进程
4.若是/proc/sys/vm/oom_kill_allocating_task为0,内核将检查每一个进程的分数,分数最高的进程将被kill掉(见后面介绍)编程

进程被kill掉以后,若是/proc/sys/vm/oom_dump_tasks为1,且系统的rlimit中设置了core文件大小,将会由/proc/sys/kernel/core_pattern里面指定的程序生成core dump文件,这个文件里将包含
pid, uid, tgid, vm size, rss, nr_ptes, nr_pmds, swapents, oom_score_adj
score, name等内容,拿到这个core文件以后,能够作一些分析,看为何这个进程被选中kill掉。ubuntu

这里能够看看ubuntu默认的配置:vim

#OOM后不panic
dev@ubuntu:~$ cat /proc/sys/vm/panic_on_oom
0

#OOM后kill掉分数最高的进程
dev@ubuntu:~$ cat /proc/sys/vm/oom_kill_allocating_task
0

#进程因为OOM被kill掉后将生成core dump文件
dev@ubuntu:~$ cat /proc/sys/vm/oom_dump_tasks
1

#默认max core file size是0, 因此系统不会生成core文件
dev@ubuntu:~$ prlimit|grep CORE
CORE max core file size 0 unlimited blocks

#core dump文件的生成交给了apport,相关的设置能够参考apport的资料
dev@ubuntu:~$ cat /proc/sys/kernel/core_pattern
|/usr/share/apport/apport %p %s %c %P

参考:apportsegmentfault

panic_on_oom

正如上面所介绍的那样,该文件的值能够取0/1/2,0是不触发panlic,2是必定触发panlic,若是为1的话就要看mempolicycpusets,这篇不介绍这方面的内容。bash

panic后内核的默认行
为是死在那里,目的是给开发人员一个连上去debug的机会。但对于大多数应用层开发人员来讲没啥用,却是但愿它赶忙重启。为了让内核panic后重启,能够修改文件/proc/sys/kernel/panic,里面表示的是panic多少秒后系统将重启,这个文件的默认值是0,表示永远不重启。oracle

#设置panic后3秒重启系统
dev@ubuntu:~$ sudo sh -c "echo 3 > /proc/sys/kernel/panic"

调整分数

当oom_kill_allocating_task的值为0时(系统默认配置),系统会kill掉系统中分数最高的那个进程,这里的分数是怎么来的呢?该值由内核维护,并存储在每一个进程的/proc/<pid>/oom_score文件中。app

每一个进程的分数受多方面的影响,好比进程运行的时间,时间越长代表这个程序越重要,因此分数越低;进程从启动后分配的内存越多,表示越占内存,分数会越高;这里只是列举了一两个影响分数的因素,实际状况要复杂的多,须要看内核代码,这里有篇文章能够参考:Taming the OOM killer

因为分数计算复杂,比较难控制,因而内核提供了另外一个文件用来调控分数,那就是文件/proc/<pid>/oom_adj,这个文件的默认值是0,但它能够配置为-17到15中间的任何一个值,内核在计算了进程的分数后,会和这个文件的值进行一个计算,获得的结果会做为进程的最终分数写入/proc/<pid>/oom_score。计算方式大概以下:

  • 若是/proc/<pid>/oom_adj的值为正数,那么分数将会被乘以2的n次方,这里n是文件里面的值

  • 若是/proc/<pid>/oom_adj的值为负数,那么分数将会被除以2的n次方,这里n是文件里面的值

因为进程的分数在内核中是一个16位的整数,因此-17就意味着最终进程的分数永远是0,也即永远不会被kill掉。

固然这种控制方式也不是很是精确,但至少比没有强多了。

修改配置

上面的这些文件均可以经过下面三种方式来修改,这里以panic_on_oom为例作个示范:

  • 直接写文件(重启后失效)

    dev@ubuntu:~$ sudo sh -c "echo 2> /proc/sys/vm/panic_on_oom"
  • 经过控制命令(重启后失效)

    dev@dev:~$ sudo sysctl vm.panic_on_oom=2
  • 修改配置文件(重启后继续生效)

    #经过编辑器将vm.panic_on_oom=2添加到文件sysctl.conf中(若是已经存在,修改该配置项便可)
    dev@dev:~$ sudo vim /etc/sysctl.conf
    
    #从新加载sysctl.conf,使修改当即生效
    dev@dev:~$ sudo sysctl -p

日志

一旦OOM killer被触发,内核将会生成相应的日志,通常能够在/var/log/messages里面看到,若是配置了syslog,日志可能在/var/log/syslog里面,这里是ubuntu里的日志样例

dev@dev:~$ grep oom /var/log/syslog
Jan 23 21:30:29 dev kernel: [  490.006836] eat_memory invoked oom-killer: gfp_mask=0x24280ca, order=0, oom_score_adj=0
Jan 23 21:30:29 dev kernel: [  490.006871]  [<ffffffff81191442>] oom_kill_process+0x202/0x3c0

cgroup的OOM killer

除了系统的OOM killer以外,若是配置了memory cgroup,那么进程还将受到本身所属memory cgroup的限制,若是超过了cgroup的限制,将会触发cgroup的OOM killer,cgroup的OOM killer和系统的OOM killer行为略有不一样,详情请参考Linux Cgroup系列(04):限制cgroup的内存使用

malloc

malloc是libc的函数,C/C++程序员对这个函数应该都很熟悉,它里面实际上调用的是内核的sbrkmmap,为了不频繁的调用内核函数和优化性能,它里面在内核函数的基础上实现了一套本身的内存管理功能。

既然内存不够时有OOM killer帮咱们kill进程,那么这时调用的malloc还会返回NULL给应用进程吗?答案是不会,由于这时只有两种状况:

  1. 当前申请内存的进程被kill掉:都被kill掉了,返回什么都没有意义了

  2. 其它进程被kill掉:释放出了空闲的内存,因而内核就能给当前进程分配内存了

那何时咱们调用malloc的时候会返回NULL呢,从malloc函数的帮助文件能够看出,下面两种状况会返回NULL:

  • 使用的虚拟地址空间超过了RLIMIT_AS的限制

  • 使用的数据空间超过了RLIMIT_DATA的限制,这里的数据空间包括程序的数据段,BSS段以及heap

关于虚拟地址空间和heap之类的介绍请参考Linux进程的内存使用状况,这两个参数的默认值为unlimited,因此只要不修改它们的默认配置,限制就不会被触发。有一种极端状况须要注意,那就是代码写的有问题,超过了系统的虚拟地址空间范围,好比32位系统的虚拟地址空间范围只有4G,这种状况下不肯定系统会以一种什么样的方式返回错误。

rlimit

上面提到的RLIMIT_AS和RLIMIT_DATA均可以经过函数getrlimit和setrlimit来设置和读取,同时linux还提供了一个prlimit程序来设置和读取rlimit的配置。

prlimit是用来替代
ulimit的一个程序,除了能设置上面的那两个参数以外,还有其它的一些参数,好比core文件的大小。关于prlimit的用法请参考它的帮助文件

#默认状况下,RLIMIT_AS和RLIMIT_DATA的值都是unlimited
dev@dev:~$ prlimit |egrep "DATA|AS"
AS         address space limit                unlimited unlimited bytes
DATA       max data size                      unlimited unlimited bytes

测试代码

C语言的程序会受到libc的影响,可能在触发OOM killer以前就触发了segmentfault错误,若是要用C语言程序来测试触发OOM killer,必定要注意malloc的行为受MMAP_THRESHOLD影响,一次申请分配太多内存的话,malloc会调用mmap映射内存,从而不必定触发OOM killer,具体细节目前还不太清楚。这里是一个触发oom killer的例子,供参考:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define M (1024 * 1024)
#define K 1024

int main(int argc, char *argv[])
{
    char *p;
    int size =0;
    while(1) {
        p = (char *)malloc(K);
        if  (p == NULL){
            printf("memory allocate failed!\n");
            return -1;
        }
        memset(p, 0, K);
        size += K;
        if (size%(100*M) == 0){
            printf("%d00M memory allocated\n", size/(100*M));
            sleep(1);
        }
    }

    return 0;
}

结束语

对一个进程来讲,内存的使用受多种因素的限制,可能在系统内存不足以前就达到了rlimit和memory cgroup的限制,同时它还可能受不一样编程语言所使用的相关内存管理库的影响,就算系统处于内存不足状态,申请新内存也不必定会触发OOM killer,须要具体问题具体分析。

参考

相关文章
相关标签/搜索