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