作为Linux下的程序员,有时候不得不面对一个问题,那就是系统内存用完了。这时,当进程向内核申请内存时,内核会怎么做呢?程序中调用的malloc函数会不会返回null?为了处理内存不足的问题,Linux内核发明了一种机制叫做OOM(OutOfMemory)killer,可以通过配置来控制内存不足时内核的行为。OOMkiller当物理内存和swap空间用完后,如果还有进程申请内存,内核会触发OOMkiller,其行为如下:1.查看文件/proc/sys/vm/panic_on_oom,如果里面的值为2,那么系统肯定会触发panic2。如果/proc/sys/vm/panic_on_oom的值为1,那么系统可能会触发panic(见下面的介绍)3.如果/proc/sys/vm/panic_on_oom的值为0,或者上一步没有未触发恐慌,然后内核继续检查文件/proc/sys/vm/oom_kill_allocating_task3。如果/proc/sys/vm/oom_kill_allocating_task为1,那么内核会杀掉当前申请内存的进程。4.如果/proc/sys/vm/oom_kill_allocating_task为0,内核会检查每个进程的得分,得分最高的进程会在进程被杀死后被杀死(见后面的介绍),如果/proc/sys/vm/oom_dump_tasks为1,系统如果在rlimit中设置了core文件大小,则coredump文件会由/proc/sys/kernel/core_pattern中指定的程序生成。这个文件会包含pid,uid,tgid,vmsize,rss,nr_ptes,nr_pmds,swapents,oom_score_adjscore,name等。拿到core文件后,可以做一些分析,看看为什么选择杀掉这个进程。这里可以看到ubuntu的默认配置:#Nopanicdev@ubuntu:~$afterOOMcat/proc/sys/vm/panic_on_oom0#kill掉OOM后得分最高的进程dev@ubuntu:~$cat/proc/sys/vm/oom_kill_allocating_task0#进程会生成coredump文件dev@ubuntu:~$cat/proc/sys/vm/oom_dump_tasks1#默认maxcorefilesize为0,所以系统不会生成core文件dev@ubuntu:~$prlimit|grepCORECOREmaxcorefilesize0unlimitedblocks#coredump文件生成交给apport,相关设置可以参考apport的资料dev@ubuntu:~$cat/proc/sys/kernel/core_pattern|/usr/share/apport/apport%p%s%c%P参考:apporpanic_on_oom上面说了这个文件的值可以是0/1/2,0表示不触发panlic,2表示必须触发panlic,如果是1,取决于mempolicy和cpusets,本文不介绍这方面的内容。panic之后内核的默认行为就是死在那里,目的是给开发者一个连接调试的机会。但是对于大部分应用层开发者来说是没有用的,但是希望它能快点重启。为了让内核在panic后重启,可以修改文件/proc/sys/kernel/panic,它表示panic后多少秒后系统会重启。该文件的默认值为0,表示永不重启。#设置panic后3秒重启系统dev@ubuntu:~$sudosh-c"echo3>/proc/sys/kernel/panic"调整分值当oom_kill_allocating_task的值为0时(系统默认配置),系统会杀掉系统中得分最高的进程,这里的得分是怎么来的呢?该值由内核维护并存储在每个进程的/proc//oom_score文件中。每个进程的得分受很多方面的影响,比如进程的运行时间。时间越长,节目越重要,得分越低;进程启动后分配的内存越多,占用的内存越多,得分越高;这里只是影响分数的一两个因素。实际情况要复杂得多。您需要查看内核代码。这里有一篇文章可供参考:TamingtheOOMkiller由于分数计算的复杂性,很难控制,所以内核提供了另外一个文件来控制分数,即文件/proc//oom_adj,该文件的默认值为0,但可以配置为-17到15之间的任意值,内核计算出进程的得分后,将以该文件的值进行计算,并将结果写入以/proc//oom_score作为进程的最终得分。计算方法大致如下:如果/proc//oom_adj的值为正数,则将分数乘以2的n次方,其中n为文件中的值如果/proc//oom_adj如果值为负数,则将分数除以2的n次方,其中n为文件中的值。由于进程的score在内核中是一个16位的整数,-17意味着最终进程的score永远为0,即永远不会被杀死。当然,这种控制方式不是很精确,但至少总比没有强很多。修改配置可以通过以下三种方式修改以上文件。这里以panic_on_oom为例:直接写入文件(重启后会失效)dev@ubuntu:~$sudosh-c"echo2>/proc/sys/vm/panic_on_oom"通过控制命令修改配置文件(重启后失败)dev@dev:~$sudosysctlvm.panic_on_oom=2(重启后继续生效)#通过编辑器将vm.panic_on_oom=2添加到文件sysctl中.conf中(如果已经存在,只需修改配置项)dev@dev:~$sudovim/etc/sysctl.conf#Reloadsysctl.conf让修改立即生效dev@dev:~$sudosysctl-pLogs一旦触发OOMkiller,内核会产生相应的日志,一般可以在/var/log/messages中看到。如果配置了syslog,则日志可能位于/var/log/syslog中。这是ubuntu示例dev@dev:~$grepoom/var/log/syslogJan2321:30:29devkernel:[490.006836]eat_memoryinvokedoom-killer:gfp_mask=0x24280ca,order=0,oom_score_adj=0Jan2321:30:29devkernel:[490.006871][]oom_kill_process+0x202/0x3c0cgroup的OOMkiller除了系统的OOMkiller,如果配置了内存cgroup,进程也会被限制它所属的内存cgroup。如果超过了cgroup的限制,就会触发cgroup的OOMkiller。cgroup的OOMkiller和系统的OOMkiller的行为略有不同。详见LinuxCgroup系列(04):限制cgroup的内存使用。mallocmalloc是libc的一个函数。C/C++程序员应该熟悉这个函数。它实际上调用了内核的sbrk和mmap。为了避免频繁调用内核函数和优化性能,它基于内核函数。它实现了一组自己的内存管理功能。既然有OOMkiller帮我们在内存不足的时候kill掉进程,那么此时调用的malloc会不会返回NULL给应用进程呢?答案是否定的,因为此时只有两种情况:当前申请内存的进程被杀死:全部被杀死,返回什么也没有意义。其他进程被杀死:空闲内存被释放,所以内核会为当前进程分配内存,所以当我们调用malloc时,它会返回NULL。从malloc函数的帮助文件可以看出,以下两种情况会返回NULL:所使用的虚拟地址空间超过了RLIMIT_ASused的限制数据空间超过了RLIMIT_DATA的限制。这里的数据空间包括程序的数据段、BSS段和堆。虚拟地址空间和堆的介绍可以参考Linux进程的内存使用。这两个参数的默认值是无限的,所以只要不修改它们的默认配置,就不会触发限制。有一种极端情况需要注意,就是代码写的有问题,超出了系统虚拟地址空间的范围。比如32位系统的虚拟地址空间范围只有4G。方法返回错误。上面rlimit提到的RLIMIT_AS和RLIMIT_DATA可以通过函数getrlimit和setrlimit来设置和读取,Linux也提供了一个prlimit程序来设置和读取rlimit的配置。prlimit是一个用来替代ulimit的程序。除了设置以上两个参数外,还有其他参数,比如core文件的大小。prlimit的使用请参考其帮助文件。#默认情况下,RLIMIT_AS和RLIMIT_DATA的值都是unlimiteddev@dev:~$prlimit|egrep"DATA|AS"ASaddressspacelimitunlimitedunlimitedbytesDATAmaxdatasizeunlimitedunlimitedbytes测试代码C语言程序会受到影响libc,可能会在触发OOM杀手之前触发segmentfault错误。如果要使用C语言程序来测试触发OOMkiller,必须注意受MMAP_THRESHOLD影响的malloc行为。如果一次分配的内存太多,malloc会调用mmap来映射内存。所以OOMkiller不一定会被触发,具体细节还不清楚。这里有一个触发oomkiller的例子供参考:#include#include#include#include#defineM(1024*1024)#defineK1024intmain(intargc,char*argv[]){char*p;整数大小=0;while(1){p=(char*)malloc(K);if(p==NULL){printf("内存分配失败!\n");返回-1;}内存集(p,0,K);尺寸+=K;if(size%(100*M)==0){printf("%d00M内存已分配\n",size/(100*M));睡觉(1);}}return0;}结论对于一个进程来说,内存的使用受到各种因素的限制,在系统内存耗尽之前可能会达到rlimit和内存cgroup的限制,也可能受到进程使用的相关内存管理库的影响不同的编程语言。即使系统处于内存不足的状态,申请新的内存也不一定会触发OOMkiller,需要具体问题具体分析。参考sysctl/vm.txtHowtoConfiguretheLinuxOut-of-MemoryKillerWhenLinuxRunsOutMemory