如何理解Linux中的OOM(Out Of Memory Killer)機制?

現在了解到,這個機制是為了防止當系統物理內存不夠用的時候系統崩潰,而選擇一個佔用內存最高的程序,把它Kill掉!

Linux為什麼要這樣做?不是有虛擬內存嗎,當物理內存不夠的時候啟用虛擬內存不就行了嗎。這個機制我最大的困惑是,一個程序的行為有可能使另一個正常程序"躺槍「(一個進程A狂分配內存,導致系統把毫無關係的進程B給kill了)。這樣設計的好處有哪些?為什麼不可以當一個程序試圖malloc內存的時候簡單地返回錯誤?

另外,這個機制是Linux特有的嗎?還是Unix系的全是這樣?


本回答基於4.14-rc6。

這個流程是這樣的:

Linux用戶內存都是讀寫時分配,所以系統發現需要內存基本上都是發生在handle_mm_fault()的時候(其他特殊流程類似,這裡忽略),handle_mm_fault()要為缺的頁分配內存,就會調alloc_pages()系列函數,從而調prepare_alloc_pages(),進而進入__alloc_pages_direct_reclaim(),這裡已經把可以清到磁碟上的緩衝都清了一次了。這樣之後還是分配不到內存,就只好進入OMM Killer了(pagefault_out_of_memory())。

到了這種狀態,系統中的內存只有可能被正在運行的進程和內核佔據了,大家都不讓,系統就只有死。內核是官家,進程是商家,官家不能殺,只好殺商家,商家殺一個也是殺,殺十個也是殺,那就殺個最胖的,少拉點仇恨。也就只能這樣了,換你,你能怎麼選?

所以啊,這些個胖商家都不得不希望國家安定,民族富強,否則死的都是胖子。

至於OOM Killer這種叫法,一聽就是自由軟體的人想出來的,Linux很早的時候根本就沒有什麼OOM Killer,只有OOM(很接近OOP:)),OOP完就大家一起死翹翹。至於Unix這種打領帶上班的,人家修養高,怎麼可能OOP嘛,人家都是打電話給客戶,用濃重的倫敦腔說「Dear Sir,你的,內存,大大地,不夠了,你想怎麼處理?」,然後很專業地,讓你自己來殺進程或者熱插內存。否則怎麼好意思賣你幾百萬一台啊?


剛好最近遇到了幾次OOM的問題。

Linux的OOM機制的存在跟它的overcommit特性有關。

所謂overcommit就是操作系統分配給進程的總內存大小超過了實際可用的內存,這樣做的原因是進程實際上使用的內存往往比申請的內存要少。比如有個進程申請了1G的內存,但實際上它只在一小段時間裡載入了大量數據,需要使用較大的內存,而在運行過程的其他大部分時間裡只用了100M的內存。這樣其實有900多M的內存在大部分時間裡是閑置的,完全可以分給其他進程,overcommit的機制就能充分利用這些閑置的內存。

overcommit相關內容可以看下這裡:理解Linux的memory overcommit

Unix/Linux的內存分配策略是lazy的,申請的時候不會分配物理內存,只有在使用的時候才分配,為了儘可能地提高內存地利用效率,系統大部分情況下都會「答應」申請內存的要求。因為overcommit的存在,系統沒辦法在進程運行的時候就預判內存是否會耗盡,只有在真正分配內存的時候才會發覺:誒,內存不夠了?這時候為了防止系統崩潰,只好用OOM killer犧牲掉一個或者幾個進程了。相比系統崩潰,這種做法更可以接受一點。

個人理解OOM和overcommit算是一種trade-off,這種機制讓系統儘可能地利用了物理內存,代價就是在某些情況下需要犧牲一些「無辜」的進程。在大部分情況下這種機制還是挺好用的,只要物理內存沒被耗光就不會有什麼問題。


關於OOM最近我有深刻體會。

我的linux機器內存16G,硬碟SSD,上次自己編譯強行跑了一個開源軟體。結果沒過一會兒,我突然滑鼠鍵盤完全失去響應,圖形桌面凍住不動……自然是因為程序發瘋耗盡我的內存,然後瘋狂磁碟交換,不知道OOM為什麼沒有發生。如果不是ssh連上去還能一幀一幀的操作,我恐怕只能強摁電源重啟了。

內存壓力過高時拒絕給運行中的應用程序分配內存,那就相當於把釋放內存壓力的任務交給了一個很可能已經發瘋了的應用程序進程……這樣的操作系統能指望有多穩定健壯?單進程fast failed總比重啟整個系統損失小。

關於malloc,它分配的應該是虛擬內存。應用程序申請大量的虛擬內存並不會立即佔用大量系統物理內存。而物理內存飆高的時候,並不是因為程序在大量malloc,而是程序真的在大範圍訪問已分配的到的虛擬地址空間。

如果讓malloc在虛擬內存用盡時報錯,首先那不太現實,因為虛擬地址空間太大了,其次這也沒啥意義,因為虛擬內存快用盡時可能物理內存並不佔用多少,對系統沒啥影響。

如果讓malloc在物理內存快用盡時報錯,那……那要這虛擬內存機制有何用?


首先解釋兩個概念:

swap:在linux裡面,當物理內存不夠用了,而又有新的程序請求分配內存,那麼linux就會選擇將其他程序暫時不用的數據交換到物理磁碟上(swap out),等程序要用的時候再讀進來(swap in)。這樣做的壞處顯而易見,swap in/swap out這裡的代價比較大,相比數據一直放在內存裡面,多了讀磁碟的操作,而磁碟IO代價。。大家都懂的。

OOM:out of memory,指在linux裡面,由於系統內存壓力,系統會選擇保護一些系統進程,而將一些其他的進程kill掉,釋放內存。

怎麼避免進程因為OOM機制被kill掉?

1. 與OOM相關的幾個文件是 /proc//oom_adj 、 /proc//oom_score。前者是一個權值-16至15,默認是0,設置為-17表示永遠不被kill,其餘情況值越大越容易被kill。後者就是它計算出來的一個值,就是根據這個值來選擇哪些進程被kill掉的。

2. 上面放置使用swap中的第三個方法 overcommit參數,因為它分配不出內存就會返回錯誤,所以永遠也不能達到內存被耗盡。OOM也就不會有影響了。

3 至於Liunx為什麼要這麼做,鬼知道!反正Android也這麼干!


首先應該了解到,所有進程都是在虛擬內存上的,使用越頻繁,該部分在物理內存就越久。

內核的內存有幾種特點:

a. 通過虛存引用的都是可以移動的

b. 可重建的內容無需換出

c. 緩存可以直接被覆蓋

內存處理也就針對這些特點來做,但是就算是這樣,物理內存依然還是會被填滿,此時為了保證系統的正常運行必須騰出一些物理內存出來,騰出物理內存的原因大概有以下兩個:

a. 內核需要一段連續物理內存,各種方法都找不到這樣的內存

b. 真到了山窮水盡的地步,完全沒內存了,比如嵌入式系統

那此時就會檢查系統上的進程,但是內核好像會評估效益,即殺死進程之後是否真能騰出來這麼多內存。

在下之前對此有做過實驗,在2G內存的系統上用malloc分配3.5G內存之後sleep,根本不會出任何問題,但是malloc緊接著memset,可以看到隨著時間往前走,swap逐步被填滿,接著是物理內存,直到剩下幾十M的時候被kill掉。

這個思路同時可以向另外一個方向走,即假設先前一個進程佔了1.8G內存之後sleep,然後後面一個進程又試圖分配500M,如果此時哪個1.8G的進程大部分內存都已經進到swap,而此時500M的內存則大部分都在物理內存中,那可以看下兩種抉擇:

a. 殺掉1.8G(無辜的)的意味著騰出了部分磁碟,然後還要進一步換出內存才能達到目標,這段時間也許又有進程佔用大量內存

b. 直接殺掉當前500M(犯事的)的則立刻可以使用數量相當的內存

作為內核設計者,那種會好一些呢?


這個功能在一些發行版都是默認關閉的


虛擬內存在使用的時候最終要落實到物理內存,如果物理內存只有1G swap 1G 總共2G空間可以用。目前已經使用了1.8G 若有程序現在申請1G,malloc申請可以成功,因為小於虛擬地址空間。但是使用的時候,比如要寫數據到這塊空間,發現這塊空間對應不到物理空間(物理1G內存用完,想要將不用的物理內存數據交換出去吧,發現交換分區已經全用完了) 這時該怎麼辦?

---------------------

個人理解,不一定對


推薦閱讀:

關於Linux的兩個問題?
linux下沒有root許可權如何方便地安裝軟體?
現在的 Linux 內核和 Linux 2.6 的內核有多大區別?
Linux 管理員在 Windows 系統下使用什麼 SSH 客戶端?
epoll的線程切換的問題?

TAG:操作系統 | Linux | Unix |