Linux內(nèi)核OOM機(jī)制的詳細(xì)分析和防止進(jìn)程被OOM殺死的方法

Linux 內(nèi)核有個(gè)機(jī)制叫OOM killer(Out-Of-Memory killer),該機(jī)制會(huì)監(jiān)控那些占用內(nèi)存過(guò)大,尤其是瞬間很快消耗大量?jī)?nèi)存的進(jìn)程,為了防止內(nèi)存耗盡而內(nèi)核會(huì)把該進(jìn)程殺掉。典型的情況是:某天一臺(tái)機(jī)器突然ssh遠(yuǎn)程登錄不了,但能ping通,說(shuō)明不是網(wǎng)絡(luò)的故障,原因是sshd進(jìn)程被OOM killer殺掉了(多次遇到這樣的假死狀況)。重啟機(jī)器后查看系統(tǒng)日志/var/log/messages會(huì)發(fā)現(xiàn)Out of Memory: Kill process 1865(sshd)類似的錯(cuò)誤信息。

防止重要的系統(tǒng)進(jìn)程觸發(fā)(OOM)機(jī)制而被殺死:可以設(shè)置參數(shù)/proc/PID/oom_adj為-17,可臨時(shí)關(guān)閉linux內(nèi)核的OOM機(jī)制。內(nèi)核會(huì)通過(guò)特定的算法給每個(gè)進(jìn)程計(jì)算一個(gè)分?jǐn)?shù)來(lái)決定殺哪個(gè)進(jìn)程,每個(gè)進(jìn)程的oom分?jǐn)?shù)可以/proc/PID/oom_score中找到。我們運(yùn)維過(guò)程中保護(hù)的一般是sshd和一些管理agent。

保護(hù)某個(gè)進(jìn)程不被內(nèi)核殺掉可以這樣操作:

echo -17 > /proc/$PID/oom_adj

如何防止sshd被殺,可以這樣操作:

pgrep -f "/usr/sbin/sshd" | while read PID;do echo -17 > /proc/$PID/oom_adj;done

可以在計(jì)劃任務(wù)里加入這樣一條定時(shí)任務(wù),就更安全了:

#/etc/cron.d/oom_disable

*/1**** root pgrep -f "/usr/sbin/sshd" | while read PID;do echo -17 > /proc/$PID/oom_adj;done

為了避免重啟失效,可以寫入/etc/rc.d/rc.local

echo -17 > /proc/$(pidof sshd)/oom_adj

至于為什么用-17而不用其他數(shù)值(默認(rèn)值為0),這個(gè)是由linux內(nèi)核定義的,查看內(nèi)核源碼可知:
以linux-3.3.6版本的kernel源碼為例,路徑為linux-3.6.6/include/linux/oom.h,閱讀內(nèi)核源碼可知oom_adj的可調(diào)值為15到-16,其中15最大-16最小,-17為禁止使用OOM。oom_score為2的n次方計(jì)算出來(lái)的,其中n就是進(jìn)程的oom_adj值,所以oom_score的分?jǐn)?shù)越高就越會(huì)被內(nèi)核優(yōu)先殺掉。


image.png

當(dāng)然還可以通過(guò)修改內(nèi)核參數(shù)禁止OOM機(jī)制

# sysctl -w vm.panic_on_oom=1
vm.panic_on_oom = 1 //1表示關(guān)閉,默認(rèn)為0表示開啟OOM
 
# sysctl -p

為了驗(yàn)證OOM機(jī)制的效果,我們不妨做個(gè)測(cè)試。
首先看看我系統(tǒng)現(xiàn)有內(nèi)存大小,沒錯(cuò)96G多,物理上還要比查看的值大一些。


image.png

再看看目前進(jìn)程最大的有哪些,top查看,我目前只跑了兩個(gè)java程序的進(jìn)程,分別4.6G,再往后redis進(jìn)程吃了21m,iscsi服務(wù)占了32m,gdm占了25m,其它的進(jìn)程都是幾M而已。


image.png

現(xiàn)在我自己用C寫一個(gè)叫bigmem程序,我指定該程序分配內(nèi)存85G
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
#define PAGE_SZ (1<<12)
 
int main() {
    int i;
    int gb = 85; //以GB為單位分配內(nèi)存大小
 
    for (i = 0; i < ((unsigned long)gb<<30)/PAGE_SZ ; ++i) {
        void *m = malloc(PAGE_SZ);
        if (!m)
            break;
        memset(m, 0, 1);
    }
    printf("allocated %lu MB\n", ((unsigned long)i*PAGE_SZ)>>20);
    getchar();
    return 0;
}

呵呵,效果明顯,然后執(zhí)行后再用top查看,排在第一位的是我的bigmem,RES是物理內(nèi)存,已經(jīng)吃滿了85G。


image.png

繼續(xù)觀察,當(dāng)bigmem穩(wěn)定保持在85G一會(huì)后,內(nèi)核會(huì)自動(dòng)將其進(jìn)程kill掉,增長(zhǎng)的過(guò)程中沒有被殺,如果不希望被殺可以執(zhí)行

pgrep -f "bigmem" | while read PID; do echo -17 > /proc/$PID/oom_adj;done

執(zhí)行以上命令前后,明顯會(huì)對(duì)比出效果,就可以體會(huì)到內(nèi)核OOM機(jī)制的實(shí)際作用了。

如果你覺得寫C代碼麻煩,我告訴大家另外一個(gè)最簡(jiǎn)單的測(cè)試觸發(fā)OOM的方法,可以把某個(gè)進(jìn)程的oom_adj設(shè)置到15(最大值),最容易觸發(fā)。然后執(zhí)行以下命令:

echo f > /proc/sysrq-trigger // 'f' - Will call oom_kill to kill a memory hog process.

以下我來(lái)觸發(fā)mysqld的OOM看看:


image.png

需要注意的是這個(gè)測(cè)試,只是模擬OOM,不會(huì)真正殺掉進(jìn)程

ps -ef | grep mysqld | grep -v grep

查看mysql進(jìn)程,發(fā)現(xiàn)依然存在

image.png

注意:

  1. Kernel-2.6.26之前版本的oomkiller算法不夠精確,RHEL 6.x版本的2.6.32可以解決這個(gè)問(wèn)題。

  2. 子進(jìn)程會(huì)繼承父進(jìn)程的oom_adj。

  3. OOM不適合于解決內(nèi)存泄漏(Memory leak)的問(wèn)題。

  4. 有時(shí)free查看還有充足的內(nèi)存,但還是會(huì)觸發(fā)OOM,是因?yàn)樵撨M(jìn)程可能占用了特殊的內(nèi)存地址空間。

創(chuàng)作不易喜歡的話記得點(diǎn)擊+關(guān)注哦

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容