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,須要具體問題具體分析。

參考

相關文章
相關標籤/搜索