Linux內(nèi)核OOM機(jī)制的詳細(xì)分析

Linux內(nèi)核根據(jù)應(yīng)用程序的要求分配內(nèi)存,通常來(lái)說(shuō)應(yīng)用程序分配了內(nèi)存但是并沒(méi)有實(shí)際全部使用,為了提高性能捂人,這部分沒(méi)用的內(nèi)存可以留作它用,這部分內(nèi)存是屬于每個(gè)進(jìn)程的矢沿,內(nèi)核直接回收利用的話比較麻煩滥搭,所以內(nèi)核采用一種過(guò)度分配內(nèi)存(over-commit memory)的辦法來(lái)間接利用這部分“空閑”的內(nèi)存,提高整體內(nèi)存的使用效率捣鲸。一般來(lái)說(shuō)這樣做沒(méi)有問(wèn)題论熙,但當(dāng)大多數(shù)應(yīng)用程序都消耗完自己的內(nèi)存的時(shí)候麻煩就來(lái)了,因?yàn)檫@些應(yīng)用程序的內(nèi)存需求加起來(lái)超出了物理內(nèi)存(包括swap)的容量摄狱,內(nèi)核(OOM killer)必須殺掉一些進(jìn)程才能騰出空間保障系統(tǒng)正常運(yùn)行脓诡。用銀行的例子來(lái)講可能更容易懂一些无午,部分人取錢的時(shí)候銀行不怕,銀行有足夠的存款應(yīng)付祝谚,當(dāng)全國(guó)人民(或者絕大多數(shù))都取錢而且每個(gè)人都想把自己錢取完的時(shí)候銀行的麻煩就來(lái)了宪迟,銀行實(shí)際上是沒(méi)有這么多錢給大家取的。
比如某天一臺(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:Killprocess 1865(sshd)類似的錯(cuò)誤信息意荤。又比如有時(shí)VPS的MySQL總是無(wú)緣無(wú)故掛掉,或者VPS 經(jīng)常死機(jī)只锻,登陸到終端發(fā)現(xiàn)都是常見(jiàn)的 Out of memory 問(wèn)題玖像。這通常是因?yàn)槟硶r(shí)刻應(yīng)用程序大量請(qǐng)求內(nèi)存導(dǎo)致系統(tǒng)內(nèi)存不足造成的,這時(shí)會(huì)觸發(fā) Linux 內(nèi)核里的 Out of Memory (OOM) killer齐饮,OOM killer 會(huì)殺掉某個(gè)進(jìn)程以騰出內(nèi)存留給系統(tǒng)用捐寥,不致于讓系統(tǒng)立刻崩潰。如果檢查相關(guān)的日志文件(/var/log/messages)就會(huì)看到下面類似的Out of memory:Kill process 信息:

Out of memory: Kill process 9682(mysqld) score 9 or sacrifice child

Killed process 9682, UID 27,(mysqld) total-vm:47388kB, anon-rss:3744kB, file-rss:80kB

httpd invoked oom-killer:gfp_mask=0x201da, order=0, oom_adj=0, oom_score_adj=0

httpd cpuset=/ mems_allowed=0

Pid: 8911, comm: httpd Not tainted2.6.32-279.1.1.el6.i686 #1

21556 total pagecache pages

21049 pages in swap cache

Swap cache stats: add 12819103,delete 12798054, find 3188096/4634617

Free swap = 0kB

Total swap = 524280kB

131071 pages RAM

0 pages HighMem

3673 pages reserved

67960 pages shared

124940 pages non-shared
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)程殺掉捺僻。
內(nèi)核檢測(cè)到系統(tǒng)內(nèi)存不足乡洼、挑選并殺掉某個(gè)進(jìn)程的過(guò)程可以參考內(nèi)核源代碼 linux/mm/oom_kill.c,當(dāng)系統(tǒng)內(nèi)存不足的時(shí)候匕坯,out_of_memory()被觸發(fā)就珠,然后調(diào)用 select_bad_process() 選擇一個(gè)“bad”進(jìn)程殺掉,判斷和選擇一個(gè)“bad”進(jìn)程的過(guò)程由 oom_badness()決定醒颖,最 bad 的那個(gè)進(jìn)程就是那個(gè)最占用內(nèi)存的進(jìn)程妻怎。

/**

  • oom_badness -heuristic function to determine which candidate task to kill

  • @p: taskstruct of which task we should calculate

  • @totalpages:total present RAM allowed for page allocation

  • The heuristicfor determining which task to kill is made to be as simple and

  • predictableas possible. The goal is to return thehighest value for the

  • task consumingthe most memory to avoid subsequent oom failures.

*/

unsigned long oom_badness(struct task_struct *p,struct mem_cgroup *memcg,

                              const nodemask_t *nodemask, unsigned longtotalpages)

{

       long points;

       long adj;

       if (oom_unkillable_task(p, memcg, nodemask))

                 return 0;

       p = find_lock_task_mm(p);

       if (!p)

                 return 0;

       adj = (long)p->signal->oom_score_adj;

       if (adj == OOM_SCORE_ADJ_MIN) {

                 task_unlock(p);

                 return 0;

       }

       /*

        * The baseline for thebadness score is the proportion of RAM that each

        * task's rss, pagetable and swap space use.

        */

       points = get_mm_rss(p->mm) + atomic_long_read(&p->mm->nr_ptes)+

                  get_mm_counter(p->mm, MM_SWAPENTS);

       task_unlock(p);

       /*

        * Root processes get 3% bonus, just like the__vm_enough_memory()

        * implementation used by LSMs.

        */

       if (has_capability_noaudit(p,CAP_SYS_ADMIN))

                 adj -= (points * 3) / 100;

       /*Normalize to oom_score_adj units */

       adj *= totalpages / 1000;

       points += adj;

       /*

        * Never return 0 for an eligible taskregardless of the root bonus and

        * oom_score_adj (oom_score_adj can't beOOM_SCORE_ADJ_MIN here).

        */

       returnpoints > 0 ? points : 1;

}

從上面的 oom_kill.c 代碼里可以看到 oom_badness() 給每個(gè)進(jìn)程打分,根據(jù) points 的高低來(lái)決定殺哪個(gè)進(jìn)程泞歉,這個(gè) points 可以根據(jù) adj 調(diào)節(jié)逼侦,root 權(quán)限的進(jìn)程通常被認(rèn)為很重要,不應(yīng)該被輕易殺掉腰耙,所以打分的時(shí)候可以得到 3% 的優(yōu)惠(分?jǐn)?shù)越低越不容易被殺掉)榛丢。我們可以在用戶空間通過(guò)操作每個(gè)進(jìn)程的 oom_adj 內(nèi)核參數(shù)來(lái)決定哪些進(jìn)程不這么容易被 OOM killer 選中殺掉。比如挺庞,如果不想 MySQL 進(jìn)程被輕易殺掉的話可以找到 MySQL 運(yùn)行的進(jìn)程號(hào)后晰赞,調(diào)整 /proc/PID/oom_score_adj 為 -15(注意 points越小越不容易被殺)防止重要的系統(tǒng)進(jìn)程觸發(fā)(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中找到掖鱼。每個(gè)進(jìn)程都有一個(gè)oom_score的屬性然走,oom killer會(huì)殺死oom_score較大的進(jìn)程,當(dāng)oom_score為0時(shí)禁止內(nèi)核殺死該進(jìn)程戏挡。設(shè)置/proc/PID/oom_adj可以改變oom_score芍瑞,oom_adj的范圍為【-17,15】褐墅,其中15最大-16最小拆檬,-17為禁止使用OOM,至于為什么用-17而不用其他數(shù)值(默認(rèn)值為0)妥凳,這個(gè)是由linux內(nèi)核定義的竟贯,查看內(nèi)核源碼可知:路徑為linux-xxxxx/include /uapi/linux/oom.h。

image.png
image

oom_score為2的n次方計(jì)算出來(lái)的逝钥,其中n就是進(jìn)程的oom_adj值屑那,oom_score的分?jǐn)?shù)越高就越會(huì)被內(nèi)核優(yōu)先殺掉。當(dāng)oom_adj=-17時(shí)晌缘,oom_score將變?yōu)?齐莲,所以可以設(shè)置參數(shù)/proc/PID/oom_adj為-17禁止內(nèi)核殺死該進(jìn)程痢站。
上面的那個(gè)MySQL例子可以如下解決來(lái)降低mysql的points磷箕,降低被殺掉的可能:
# ps aux | grep mysqld
mysql 2196 1.6 2.1 623800 44876 ? Ssl 09:42 0:00 /usr/sbin/mysqld
# cat /proc/2196/oom_score_adj
0
# echo -15 > /proc/2196/oom_score_adj

   當(dāng)然了,保證某個(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
   為了驗(yàn)證OOM機(jī)制的效果岳枷,我們不妨做個(gè)測(cè)試。
   首先看看我系統(tǒng)現(xiàn)有內(nèi)存大小呜叫,沒(mé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剧罩,呵呵栓拜,效果明顯,然后執(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ò)程中沒(méi)有被殺赏陵,如果不希望被殺可以執(zhí)行
  pgrep -f "bigmem" | while read PID; do echo -17 > /proc/$PID/oom_adj;done

執(zhí)行以上命令前后饼齿,明顯會(huì)對(duì)比出效果,就可以體會(huì)到內(nèi)核OOM機(jī)制的實(shí)際作用了蝙搔。
注意缕溉,由任意調(diào)整的進(jìn)程衍生的任意進(jìn)程將繼承該進(jìn)程的 oom_score。例如:如果 sshd 進(jìn)程不受 oom_killer 功能影響吃型,所有由 SSH 會(huì)話產(chǎn)生的進(jìn)程都將不受其影響证鸥。這可在出現(xiàn) OOM 時(shí)影響 oom_killer 功能救援系統(tǒng)的能力。

當(dāng)然還可以通過(guò)修改內(nèi)核參數(shù)禁止在內(nèi)存出現(xiàn)OOM時(shí)采取殺掉進(jìn)程的這種機(jī)制勤晚,但此時(shí)會(huì)觸發(fā)kernel panic枉层。當(dāng)內(nèi)存嚴(yán)重不足時(shí),內(nèi)核有兩種選擇:1.直接panic 2.殺掉部分進(jìn)程赐写,釋放一些內(nèi)存鸟蜡。通過(guò)/proc/sys/vm/panic_on_oom可以控制,當(dāng)panic_on_oom為1時(shí)挺邀,直接panic揉忘,當(dāng)panic_on_oom為0時(shí)內(nèi)核將通過(guò)oom killer殺掉部分進(jìn)程。(默認(rèn)是為0的)
          # sysctl -w vm.panic_on_oom=1
          vm.panic_on_oom = 1 //1表示關(guān)閉端铛,默認(rèn)為0表示開(kāi)啟OOM killer
          # sysctl –p
   我們可以通過(guò)一些內(nèi)核參數(shù)來(lái)調(diào)整 OOM killer 的行為泣矛,避免系統(tǒng)在那里不停的殺進(jìn)程。比如我們可以在觸發(fā) OOM 后立刻觸發(fā) kernel panic禾蚕,kernel panic 10秒后自動(dòng)重啟系統(tǒng):
          # sysctl -w vm.panic_on_oom=1
          vm.panic_on_oom = 1
          # sysctl -w kernel.panic=10
          kernel.panic = 10
   或者:
          # echo "vm.panic_on_oom=1" >> /etc/sysctl.conf
          # echo "kernel.panic=10" >> /etc/sysctl.conf

當(dāng)然您朽,如果需要的話可以完全不允許過(guò)度分配內(nèi)存,此時(shí)也就不會(huì)出現(xiàn)OOM的問(wèn)題(不過(guò)不推薦這樣做):
# sysctl -w vm.overcommit_memory=2
# echo "vm.overcommit_memory=2" >> /etc/sysctl.conf换淆,
vm.overcommit_memory 表示內(nèi)核在分配內(nèi)存時(shí)候做檢查的方式哗总。這個(gè)變量可以取到0,1,2三個(gè)值。對(duì)取不同的值時(shí)的處理方式都定義在內(nèi)核源碼 mm/mmap.c 的 __vm_enough_memory 函數(shù)中产舞。
0:當(dāng)用戶空間請(qǐng)求更多的的內(nèi)存時(shí)魂奥,內(nèi)核嘗試估算出剩余可用的內(nèi)存。此時(shí)宏為 OVERCOMMIT_GUESS易猫,內(nèi)核計(jì)算:NR_FILE_PAGES 總量+SWAP總量+slab中可以釋放的內(nèi)存總量耻煤,如果申請(qǐng)空間超過(guò)此數(shù)值,則將此數(shù)值與空閑內(nèi)存總量減掉 totalreserve_pages(?) 的總量相加。如果申請(qǐng)空間依然超過(guò)此數(shù)值哈蝇,則分配失敗棺妓。
1:當(dāng)設(shè)這個(gè)參數(shù)值為1時(shí),宏為 OVERCOMMIT_ALWAYS炮赦,內(nèi)核允許超量使用內(nèi)存直到用完為止怜跑,主要用于科學(xué)計(jì)算。
2:當(dāng)設(shè)這個(gè)參數(shù)值為2時(shí)吠勘,此時(shí)宏為 OVERCOMMIT_NEVER性芬,內(nèi)核會(huì)使用一個(gè)決不過(guò)量使用內(nèi)存的算法,即系統(tǒng)整個(gè)內(nèi)存地址空間不能超過(guò)swap+50%的RAM值剧防,50%參數(shù)的設(shè)定是在overcommit_ratio中設(shè)定植锉,內(nèi)核計(jì)算:內(nèi)存總量×vm.overcommit_ratio/100+SWAP 的總量,如果申請(qǐng)空間超過(guò)此數(shù)值峭拘,則分配失敗俊庇。vm.overcommit_ratio 的默認(rèn)值為50。
以上為粗略描述鸡挠,在實(shí)際計(jì)算時(shí)辉饱,如果非root進(jìn)程,則在計(jì)算時(shí)候會(huì)保留3%的空間拣展,而root進(jìn)程則沒(méi)有該限制彭沼。詳細(xì)過(guò)程可看源碼。

找出最有可能被 OOM Killer 殺掉的進(jìn)程:
我們知道了在用戶空間可以通過(guò)操作每個(gè)進(jìn)程的 oom_adj 內(nèi)核參數(shù)來(lái)調(diào)整進(jìn)程的分?jǐn)?shù)瞎惫,這個(gè)分?jǐn)?shù)也可以通過(guò) oom_score 這個(gè)內(nèi)核參數(shù)看到溜腐,比如查看進(jìn)程號(hào)為981的 omm_score译株,這個(gè)分?jǐn)?shù)被上面提到的 omm_score_adj 參數(shù)調(diào)整后(-15)瓜喇,就變成了3:

cat /proc/981/oom_score

18

echo -15 > /proc/981/oom_score_adj

cat /proc/981/oom_score

3
下面這個(gè) bash 腳本可用來(lái)打印當(dāng)前系統(tǒng)上 oom_score 分?jǐn)?shù)最高(最容易被 OOM Killer 殺掉)的進(jìn)程:

!/bin/bash

for proc in (find /proc -maxdepth 1 -regex '/proc/[0-9]+'); do printf "%2d %5d %s\n" \ "(cat proc/oom_score)" \ "(basename proc)" \ "(cat $proc/cmdline | tr '\0' ' ' | head -c 50)"
done 2>/dev/null | sort -nr | head -n 10

chmod +x oomscore.sh

./oomscore.sh

18 981 /usr/sbin/mysqld
4 31359 -bash
4 31056 -bash
1 31358 sshd: root@pts/6
1 31244 sshd: vpsee [priv]
1 31159 -bash
1 31158 sudo -i
1 31055 sshd: root@pts/3
1 30912 sshd: vpsee [priv]
1 29547 /usr/sbin/sshd –D

注意:
1.Kernel-2.6.26之前版本的oomkiller算法不夠精確,RHEL6.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)存地址空間。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末夯缺,一起剝皮案震驚了整個(gè)濱河市蚤氏,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌踊兜,老刑警劉巖竿滨,帶你破解...
    沈念sama閱讀 216,470評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡于游,警方通過(guò)查閱死者的電腦和手機(jī)毁葱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)贰剥,“玉大人倾剿,你說(shuō)我怎么就攤上這事“龀桑” “怎么了前痘?”我有些...
    開(kāi)封第一講書人閱讀 162,577評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)担忧。 經(jīng)常有香客問(wèn)我际度,道長(zhǎng),這世上最難降的妖魔是什么涵妥? 我笑而不...
    開(kāi)封第一講書人閱讀 58,176評(píng)論 1 292
  • 正文 為了忘掉前任乖菱,我火速辦了婚禮,結(jié)果婚禮上蓬网,老公的妹妹穿的比我還像新娘窒所。我一直安慰自己,他們只是感情好帆锋,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布吵取。 她就那樣靜靜地躺著,像睡著了一般锯厢。 火紅的嫁衣襯著肌膚如雪皮官。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 51,155評(píng)論 1 299
  • 那天实辑,我揣著相機(jī)與錄音捺氢,去河邊找鬼。 笑死剪撬,一個(gè)胖子當(dāng)著我的面吹牛摄乒,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播残黑,決...
    沈念sama閱讀 40,041評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼馍佑,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了梨水?” 一聲冷哼從身側(cè)響起拭荤,我...
    開(kāi)封第一講書人閱讀 38,903評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎疫诽,沒(méi)想到半個(gè)月后舅世,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體笼恰,經(jīng)...
    沈念sama閱讀 45,319評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評(píng)論 2 332
  • 正文 我和宋清朗相戀三年歇终,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了社证。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,703評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡评凝,死狀恐怖追葡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情奕短,我是刑警寧澤宜肉,帶...
    沈念sama閱讀 35,417評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站翎碑,受9級(jí)特大地震影響谬返,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜日杈,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評(píng)論 3 325
  • 文/蒙蒙 一遣铝、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧莉擒,春花似錦酿炸、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,664評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至鹿鳖,卻和暖如春扁眯,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背翅帜。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,818評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工姻檀, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人藕甩。 一個(gè)月前我還...
    沈念sama閱讀 47,711評(píng)論 2 368
  • 正文 我出身青樓施敢,卻偏偏與公主長(zhǎng)得像周荐,于是被迫代替她去往敵國(guó)和親狭莱。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評(píng)論 2 353