有時(shí)候我們會(huì)發(fā)現(xiàn)系統(tǒng)中某個(gè)進(jìn)程會(huì)突然掛掉,通過(guò)查看系統(tǒng)日志發(fā)現(xiàn)是由于OOM機(jī)制導(dǎo)致進(jìn)程被殺掉。
今天我們就來(lái)介紹一下什么是OOM機(jī)制以及怎么防止進(jìn)程因?yàn)镺OM機(jī)制而被殺掉。
什么是OOM機(jī)制
OOM是 Out Of Memory 的縮寫,中文意思是內(nèi)存不足。而OOM機(jī)制是指當(dāng)系統(tǒng)內(nèi)存不足時(shí),系統(tǒng)觸發(fā)的應(yīng)急機(jī)制。
當(dāng) Linux 內(nèi)核發(fā)現(xiàn)系統(tǒng)中的物理內(nèi)存不足時(shí),首先會(huì)對(duì)系統(tǒng)中的可回收內(nèi)存進(jìn)行回收,能夠被回收的內(nèi)存有如下:
讀寫文件時(shí)的頁(yè)緩存。
為了性能而延遲釋放的空閑 slab 內(nèi)存頁(yè)。
當(dāng)系統(tǒng)內(nèi)存不足時(shí),內(nèi)核會(huì)優(yōu)先釋放這些內(nèi)存頁(yè)。因?yàn)槭褂眠@些內(nèi)存頁(yè)只是為了提升系統(tǒng)的性能,釋放這些內(nèi)存頁(yè)也不會(huì)影響系統(tǒng)的正常運(yùn)行。
如果釋放上述的內(nèi)存后,還不能解決內(nèi)存不足的情況,那么內(nèi)核會(huì)如何處理呢?答案就是:觸發(fā)OOM killer殺掉系統(tǒng)中占用內(nèi)存最大的進(jìn)程。如下圖所示:
可以看出,OOM killer 是防止系統(tǒng)崩潰的最后一個(gè)手段,不到迫不得已的情況是不會(huì)觸發(fā)的。
OOM killer 實(shí)現(xiàn)
接下來(lái),我們分析一下內(nèi)核是如何實(shí)現(xiàn) OOM killer 的。
由于在 Linux 系統(tǒng)中,進(jìn)程申請(qǐng)的都是虛擬內(nèi)存地址。所以當(dāng)程序調(diào)用malloc()申請(qǐng)內(nèi)存時(shí),如果虛擬內(nèi)存空間足夠的話,是不會(huì)觸發(fā) OOM 機(jī)制的。
當(dāng)進(jìn)程訪問(wèn)虛擬內(nèi)存地址時(shí),如果此虛擬內(nèi)存地址還沒(méi)有映射到物理內(nèi)存地址的話,那么將會(huì)觸發(fā)缺頁(yè)異常。
在缺頁(yè)異常處理例程中,將會(huì)申請(qǐng)新的物理內(nèi)存頁(yè),并且將進(jìn)程的虛擬內(nèi)存地址映射到剛申請(qǐng)的物理內(nèi)存。
如果在申請(qǐng)物理內(nèi)存時(shí),系統(tǒng)中的物理內(nèi)存不足,那么內(nèi)核將會(huì)回收一些能夠被回收的文件頁(yè)緩存。如果回收完后,物理內(nèi)存還是不足的話,那么將會(huì)觸發(fā)swapping機(jī)制(如果開(kāi)啟了的話)。
swapping機(jī)制會(huì)將某些進(jìn)程不常用的內(nèi)存頁(yè)寫入到交換區(qū)(硬盤分區(qū)或文件)中,然后釋放掉這些內(nèi)存頁(yè),從而達(dá)到緩解內(nèi)存不足的情況。
如果通過(guò)上面的手段還不能解決內(nèi)存不足的情況,那么內(nèi)核將會(huì)調(diào)用pagefault_out_of_memory()函數(shù)來(lái)殺掉系統(tǒng)中占用物理內(nèi)存最多的進(jìn)程。
我們來(lái)看看pagefault_out_of_memory()函數(shù)的實(shí)現(xiàn):
voidpagefault_out_of_memory(void) { ... out_of_memory(NULL,0,0,NULL,false); ... }
可以看出,pagefault_out_of_memory()函數(shù)最終會(huì)調(diào)用out_of_memory()來(lái)殺死系統(tǒng)中占用內(nèi)存最多的進(jìn)程。
我們繼續(xù)來(lái)看看out_of_memory()函數(shù)的實(shí)現(xiàn):
voidout_of_memory(structzonelist*zonelist,gfp_tgfp_mask,intorder, nodemask_t*nodemask,boolforce_kill) { ... //1.從系統(tǒng)中選擇一個(gè)最壞(占用內(nèi)存最多)的進(jìn)程 p=select_bad_process(&points,totalpages,mpol_mask,force_kill); ... //2.如果找到最壞的進(jìn)程,那么調(diào)用oom_kill_process函數(shù)殺掉進(jìn)程 if(p!=(void*)-1UL){ oom_kill_process(p,gfp_mask,order,points,totalpages,NULL, nodemask,"Outofmemory"); killed=1; } ... }
out_of_memory()函數(shù)的邏輯比較簡(jiǎn)單,主要完成兩個(gè)事情:
調(diào)用select_bad_process()函數(shù)從系統(tǒng)中選擇一個(gè)最壞(占用物理內(nèi)存最多)的進(jìn)程。
如果找到最壞的進(jìn)程,那么調(diào)用oom_kill_process()函數(shù)將此進(jìn)程殺掉。
從上面的分析可知,找到最壞的進(jìn)程是 OOM killer 最為重要的事情。
那么我們來(lái)看看select_bad_process()函數(shù)是怎樣選擇最壞的進(jìn)程的:
staticstructtask_struct* select_bad_process(unsignedint*ppoints,unsignedlongtotalpages, constnodemask_t*nodemask,boolforce_kill) { structtask_struct*g,*p; structtask_struct*chosen=NULL; unsignedlongchosen_points=0; ... //1.遍歷系統(tǒng)中所有的進(jìn)程和線程 for_each_process_thread(g,p){ unsignedintpoints; ... //2.計(jì)算進(jìn)程最壞分?jǐn)?shù)值,選擇分?jǐn)?shù)最大的進(jìn)程作為殺掉的目標(biāo)進(jìn)程 points=oom_badness(p,NULL,nodemask,totalpages); if(!points||points
select_bad_process()函數(shù)的主要工作如下:
遍歷系統(tǒng)中所有的進(jìn)程和線程,并且調(diào)用oom_badness()函數(shù)計(jì)算進(jìn)程的最壞分?jǐn)?shù)值。
選擇最壞分?jǐn)?shù)值最大的進(jìn)程作為被殺掉的目標(biāo)進(jìn)程。
所以,計(jì)算進(jìn)程的最壞分?jǐn)?shù)值就是 OOM killer 的核心工作。我們接著來(lái)看看oom_badness()函數(shù)是怎么計(jì)算進(jìn)程的最壞分?jǐn)?shù)值的:
unsignedlong oom_badness(structtask_struct*p,structmem_cgroup*memcg, constnodemask_t*nodemask,unsignedlongtotalpages) { longpoints; longadj; //1.如果進(jìn)程不能被殺掉(init進(jìn)程和內(nèi)核進(jìn)程是不能被殺的) if(oom_unkillable_task(p,memcg,nodemask)) return0; ... //2.我們可以通過(guò)/proc/{pid}/oom_score_adj文件來(lái)設(shè)置進(jìn)程的被殺建議值, //這個(gè)值越小,進(jìn)程被殺的機(jī)會(huì)越低。如果設(shè)置為-1000時(shí),進(jìn)程將被禁止殺掉。 adj=(long)p->signal->oom_score_adj; if(adj==OOM_SCORE_ADJ_MIN){ ... return0; } //3.統(tǒng)計(jì)進(jìn)程使用的物理內(nèi)存數(shù) points=get_mm_rss(p->mm) +atomic_long_read(&p->mm->nr_ptes) +get_mm_counter(p->mm,MM_SWAPENTS); ... //4.加上進(jìn)程被殺建議值,得出最終的分?jǐn)?shù)值 adj*=totalpages/1000; points+=adj; returnpoints>0?points:1; }
oom_badness()函數(shù)主要按照以下步驟來(lái)計(jì)算進(jìn)程的最壞分?jǐn)?shù)值:
如果進(jìn)程不能被殺掉(init進(jìn)程和內(nèi)核進(jìn)程是不能被殺的),那么返回分?jǐn)?shù)值為 0。
可以通過(guò)/proc/{pid}/oom_score_adj文件來(lái)設(shè)置進(jìn)程的 OOM 建議值(取值范圍為 -1000 ~ 1000)。建議值越小,進(jìn)程被殺的機(jī)會(huì)越低。如果將其設(shè)置為 -1000 時(shí),進(jìn)程將被禁止殺掉。
統(tǒng)計(jì)進(jìn)程使用的物理內(nèi)存數(shù),包括實(shí)際使用的物理內(nèi)存、頁(yè)表占用的物理內(nèi)存和 swap 機(jī)制占用的物理內(nèi)存。
最后加上進(jìn)程的 OOM 建議值,得出最終的分?jǐn)?shù)值。
通過(guò)oom_badness()函數(shù)計(jì)算出進(jìn)程的最壞分?jǐn)?shù)值后,系統(tǒng)就能從中選擇一個(gè)分?jǐn)?shù)值最大的進(jìn)程殺死,從而解決內(nèi)存不足的情況。
禁止進(jìn)程被 OOM 殺掉
有時(shí)候,我們不希望某些進(jìn)程被 OOM killer 殺掉。例如 MySQL 進(jìn)程如果被 OOM killer 殺掉的話,那么可能導(dǎo)致數(shù)據(jù)丟失的情況。
那么如何防止進(jìn)程被 OOM killer 殺掉呢?從上面的分析可知,在內(nèi)核計(jì)算進(jìn)程最壞分?jǐn)?shù)值時(shí),會(huì)加上進(jìn)程的oom_score_adj(OOM建議值)值。如果將此值設(shè)置為-1000時(shí),那么系統(tǒng)將會(huì)禁止 OOM killer 殺死此進(jìn)程。
例如使用如下命令,將會(huì)禁止殺死 PID 為 2000 的進(jìn)程:
$echo-1000>/proc/2000/oom_score_adj
這樣,我們就能防止一些重要的進(jìn)程被 OOM killer 殺死。
審核編輯:劉清
-
PID
+關(guān)注
關(guān)注
35文章
1472瀏覽量
85526 -
MySQL
+關(guān)注
關(guān)注
1文章
811瀏覽量
26580 -
LINUX內(nèi)核
+關(guān)注
關(guān)注
1文章
316瀏覽量
21651
原文標(biāo)題:細(xì)說(shuō):Linux Out Of Memory 機(jī)制
文章出處:【微信號(hào):CPP開(kāi)發(fā)者,微信公眾號(hào):CPP開(kāi)發(fā)者】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論