首頁 > 軟體

Linux OOM killer 機制

2020-06-16 17:56:02

Linux中的Out Of Memory(OOM) Killer功能是一種確保系統記憶體足夠的最終手段,可以在耗盡系統記憶體或交換區後,按某種演算法判斷佔用系統最多資源的進程,向進程傳送信號,強制終止該進程。

簡單來說該機制會監控那些佔用記憶體過大,尤其是瞬間很快消耗大量記憶體的進程,為了防止記憶體耗盡而核心會把該進程殺掉。

這個功能即使在無法釋放記憶體的情況下,也能夠重複進行確保記憶體的處理過程,防止系統停滯,還可以找出過度消耗記憶體的進程。

典型的情況是:某天一台機器突然ssh遠端登入不了,但能ping通,說明不是網路的故障或者機器down掉,很大可能是sshd進程被 OOM killer殺掉了。

重新啟動機器後檢視系統紀錄檔/var/log/messages會發現 Out of Memory: Kill process 247(sshd)類似的錯誤資訊。

還有另外一種情況也會導致能ping不能ssh,就是網路連線過多把系統檔案描述符資源耗盡,這裡暫時不考慮這種情況。

而在使用vip的高可用方案中,這種情況也很容易出現腦裂的現象。

防止重要的系統進程觸發(OOM)機制而被殺死:可以設定引數/proc/PID/oom_adj為-17,可臨時關閉linux核心的OOM機制。核心會通過特定的演算法給每個進程計算一個分數來決定殺哪個進程,每個進程的oom分數可以/proc/PID/oom_score中找到。

我們認為重要的進程有sshd,或者一些監控守護行程,大家可以根據自己實際情況選擇需要保護的進程。

保護某個進程不被核心殺掉可以這樣操作:

echo -17 > /proc/$PID/oom_adj

可以寫一個簡單的指令碼,部署在crontab上防止重要進程被oom

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

其中的“/usr/sbin/sshd”可以替換為你認為重要的進程,不過在匹配時候注意不要匹配錯了

•進程的選定方法

OOM Killer在記憶體耗盡時,會檢視所有進程,並分別為每個進程計算分數。將信號傳送給分數最高的進程。

計算分數的方法

在OOM Killer計算分數時要考慮很多方面。首先要針對每個進程確認下列1~9個事項再計算分數。

1. 首先,計算分數時是以進程的虛擬記憶體大小為基準的,虛擬記憶體大小可以使用ps命令的VSZ或/proc/<PID>/status的 VmSize來確認。對於正在消耗虛擬記憶體的進程,其最初的得分較高,單位是將1KB作為1個得分,消耗1GB記憶體的進程,得分約為1024*1024。

2.如果進程正在執行swapoff系統呼叫,則得分設定為最大值(unsigned long的最大值)。這是因為禁用swap的行為與消除記憶體不足是相反的,會立刻將其作為OOM Killer的物件進程。

3.如果是母進程,則將所有子進程記憶體大小的一半作為分數。

4. 根據進程的CPU使用時間和進程啟動時間調整得分,這是因為在這裡認為越是長時間執行或從事越多工作的進程越重要,需保持得分較低。

5.對於通過nice命令等將優先順序設定得較低的進程,要將得分翻倍。nice-n中設定為1~19的命令的得分翻倍。

6.特權進程普遍較為重要,因此將其得分設定為1/4。

7.通過capset(3)等設定了功能(capability)CAP_SYS_RAWIO註3的進程,其得分為1/4,將直接對硬體進行操作的進程判斷為重要進程。

8.關於Cgroup,如果進程只允許與促使OOM Killer執行的進程所允許的記憶體節點完全不同的記憶體節點,則其得分為1/8。

9.最後通過proc檔案系統oom_adj的值調整得分。

依據以上規則,為所有進程打分,向得分最高的進程傳送信號SIGKILL(到Linux 2.6.10為止,在設定了功能CAP_SYS_RAWIO的情況下,傳送SIGTERM,在沒有設定的情況下,傳送SIGKILL)。

各進程的得分可以使用/proc/<PID>/oom_score來確認。

但是init(PID為1的)進程不能成為OOM Killer的物件。當成為物件的進程包含子進程時,先向其子進程傳送信號。

向成為物件的進程傳送信號後,對於參照系統的全執行緒,即使執行緒組(TGID)不同,如果存在與物件進程共用相同記憶體空間的進程,則也向這些進程傳送信號。

至於為什麼用-17而不用其他數值(預設值為0),這個是由linux核心定義的,檢視核心原始碼可知:

以linux- 3.3.6版本的kernel原始碼為例,路徑為linux-3.6.6/include/linux/oom.h,閱讀核心原始碼可知oom_adj的可調 值為15到-16,其中15最大-16最小,-17為禁止使用OOM。oom_score為2的n次方計算出來的,其中n就是進程的oom_adj值,所 以oom_score的分數越高就越會被核心優先殺掉。

當然還可以通過修改核心引數禁止OOM機制

# sysctl -w vm.panic_on_oom=1
vm.panic_on_oom = 1 //1表示關閉,預設為0表示開啟OOM
# sysctl -p

測試程式

命令列引數輸入占用記憶體大小N,根據自身實驗環境的實體記憶體大小來設定,例如我的實驗環境為記憶體4G,設為4G就足夠了

程式碼命名為mem.c,編譯方法 gcc -o mem mem.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
#define PAGE_SZ (1<<12)
 
int main(int argc, char* argv[]) {
    int i;
    if (argc != 2) return 0;
    int gb = atoi(argv[1]);

    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 MBn", ((unsigned long)i*PAGE_SZ)>>20);
    getchar();
    return 0;
}

然後執行 ./mem 4

如果不執行任何操作的話,直接執行結果會發現系統自動oom掉這個進程

如果我們進行以下操作,把進程優先順序設定為-17

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

你會發現系統不會把這個佔用大記憶體的進程oom掉,但這時你也會發現系統響應變慢甚至宕機!

•設定任意進程觸發oom

一個最簡單的測試觸發OOM的方法,可以把某個進程的oom_adj設定到15(最大值),最容易觸發。然後執行以下命令:

echo f > /proc/sysrq-trigger

如果要實驗請在測試環境測速,直接對線上環境操作造成任何不良後果請勿怪博主。

本文永久更新連結地址http://www.linuxidc.com/Linux/2015-08/121085.htm


IT145.com E-mail:sddin#qq.com