Linux 操作系統(tǒng)有特定的內(nèi)存管理方式。其中一項(xiàng)策略是Overcommit,它允許應(yīng)用程序提前預(yù)訂所需的內(nèi)存。然而,承諾的內(nèi)存在實(shí)際使用時(shí)可能無(wú)法使用。然后,系統(tǒng)必須提供一種特殊的方法來(lái)避免內(nèi)存不足。
在本教程中,我們將了解內(nèi)存不足 (OOM) Killer,這是一個(gè)為了系統(tǒng)穩(wěn)定性而消除應(yīng)用程序的過(guò)程。
2. OOM Killer調(diào)用時(shí)
讓我們注意到,要讓Killer發(fā)揮作用,系統(tǒng)必須允許Overcommit。然后,根據(jù)系統(tǒng)從消除它中獲得的收益對(duì)每個(gè)進(jìn)程進(jìn)行評(píng)分。
最后,當(dāng)遇到低內(nèi)存狀態(tài)時(shí),內(nèi)核會(huì)殺死得分最高的進(jìn)程。
我們可以在/proc/PID/oom_score文件中通過(guò)進(jìn)程的PID找到進(jìn)程的分?jǐn)?shù)?,F(xiàn)在,讓我們啟動(dòng)一個(gè)終端并打印它的進(jìn)程分?jǐn)?shù),因?yàn)?$變量保存它的PID:
$ cat /proc/$$/oom_score
0
接下來(lái),讓我們列出所有進(jìn)程及其PID和名稱,按oom_score從低到高排序,使用oom_score_reader腳本:
#!/bin/bash
while read -r pid comm
do
printf '%d\t%d\t%s\n' "$pid" "$(cat /proc/$pid/oom_score)" "$comm"
done < <(ps -e -o pid= -o comm=) | sort -k2 -n
我們使用進(jìn)程替換來(lái)為讀取命令提供ps的結(jié)果。
現(xiàn)在讓我們檢查一下結(jié)果:
10 0 rcu_sched
102 0 kswapd0
...
97 0 devfreq_wq
99 0 watchdogd
1051 1 upowerd
1114 1 sddm-helper
1126 1 systemd
...
1147 2 pulseaudio
2005 2 gnome-shell-cal
2172 2 gsd-datetime
...
4329 6 gedit
2186 7 evolution-alarm
5300 9 qterminal
875 9 Xorg
9215 10 Web Content
3527 17 Privileged Cont
6353 19 Web Content
1679 20 gnome-shell
6314 21 Web Content
8625 21 Web Content
4070 22 Web Content
7753 23 Web Content
3170 27 gnome-software
3615 41 WebExtensions
3653 41 Web Content
3160 62 firefox
分?jǐn)?shù)取值從 0 到 1000。讓我們注意oom_score的值為零意味著該進(jìn)程對(duì)于 OOM Killer是安全的。
3. 保護(hù)進(jìn)程免受 OOM Killer 攻擊
現(xiàn)在,讓我們降低消除該過(guò)程的可能性。這對(duì)于長(zhǎng)期存在的流程和服務(wù)尤為重要。所以,對(duì)于這樣一個(gè)過(guò)程,我們應(yīng)該設(shè)置oom_score_adj參數(shù)。
該參數(shù)取值范圍從 -1000 到 1000(含)。因此,它的負(fù)值會(huì)降低oom_score,使該過(guò)程對(duì)Killer的吸引力降低。相反,正值會(huì)導(dǎo)致分?jǐn)?shù)上升。最后,oom_score_adj = -1000的進(jìn)程不會(huì)被殺死。
我們應(yīng)該檢查文件/proc/PID/oom_score_adj 中的參數(shù)。所以,對(duì)于我們的終端:
$ cat /proc/$$/oom_score_adj
0
讓我們看看終端的分?jǐn)?shù)在任何一個(gè)方向上都沒(méi)有調(diào)整。
3.1. 手動(dòng)設(shè)置oom_score_adj
作為最簡(jiǎn)單的方法,我們可以手動(dòng)寫入oom_score_adj文件。首先,讓我們檢查一下Firefox網(wǎng)絡(luò)瀏覽器進(jìn)程的分?jǐn)?shù)。我們需要pgrep來(lái)獲取它的PID:
$ cat /proc/$(pgrep firefox)/oom_score
60
接下來(lái),讓我們讓它更容易被殺死:
$ echo 500 > /proc/$(pgrep firefox)/oom_score_adj
$ cat /proc/$(pgrep firefox)/oom_score
562
最后,讓我們提高它的安全性:
$ sudo echo -30 > /proc/$(pgrep firefox)/oom_score_adj
$ cat /proc/$(pgrep firefox)/oom_score
31
請(qǐng)注意,我們需要sudo權(quán)限才能將調(diào)整因子降低到零以下。
3.2. choom命令
我們應(yīng)該使用choom來(lái)報(bào)告分?jǐn)?shù)并修改其調(diào)整。該命令是util-linux軟件包的一部分。因此,讓我們使用p開(kāi)關(guān)再次檢查Firefox進(jìn)程:
$ choom -p $(pgrep firefox)
pid 3061's current OOM score: 40
pid 3061's current OOM score adjust value: -30
然后,讓我們通過(guò)n開(kāi)關(guān)提供oom_score_adj的新值來(lái)增加它的分?jǐn)?shù):
$ choom -p $(pgrep firefox) -n 300
pid 3061's OOM score adjust value changed from -30 to 300
$ choom -p $(pgrep firefox)
pid 3061's current OOM score: 371
pid 3061's current OOM score adjust value: 300
最后,使用choom,我們可以使用給定的 oom_score_adj 立即啟動(dòng)一個(gè)進(jìn)程:
$ choom -n 300 firefox
$ choom -p $(pgrep firefox)
pid 3061's current OOM score: 346
pid 3061's current OOM score adjust value: 300
3.3. 配置服務(wù)
對(duì)于服務(wù),我們可以永久調(diào)整位于或鏈接在/etc/systemd文件夾中的服務(wù)配置中的分?jǐn)?shù)。因此,我們需要編輯服務(wù)部分中的OOMScoreAdjust條目。例如,讓我們看一下snapd服務(wù)的配置:
[Unit]
Description=Snap Daemon
# some output skipped
[Service]
# some output skipped
OOMScoreAdjust=-900
ExecStart=/usr/lib/snapd/snapd
# more output skipped
4. oom_score是如何計(jì)算的
我們應(yīng)該知道,分?jǐn)?shù)的計(jì)算方式取決于內(nèi)核版本。除了內(nèi)存占用之外,舊版本可能會(huì)考慮運(yùn)行時(shí)間、nice級(jí)別或 root 所有權(quán)。
然而,在版本 5 中,只有總內(nèi)存使用量很重要。要找到它,我們應(yīng)該檢查oom_kill.c源文件中的函數(shù)oom_badness 。
首先,函數(shù)檢查進(jìn)程是否免疫。通常,這要?dú)w功于oom_score_adj = -1000。在這種情況下,任務(wù)獲得零分。
否則,將匯總?cè)蝿?wù)的 RAM、虛擬內(nèi)存和交換空間大小。然后將結(jié)果除以總可用內(nèi)存。最后,該函數(shù)將比率歸一化為1000。
這時(shí),oom_score_adj就派上用場(chǎng)了。所以,它增加了分?jǐn)?shù)。因此,它的負(fù)值實(shí)際上降低了這個(gè)值。
現(xiàn)在我們應(yīng)該意識(shí)到,如果這個(gè)過(guò)程對(duì)我們很重要,我們需要自己照顧它的生存能力。因此,我們應(yīng)該適當(dāng)調(diào)整它的oom_score_adj參數(shù)。
5. 控制Overcommit的系統(tǒng)范圍參數(shù)
我們可以通過(guò)設(shè)置overcommit_memory參數(shù)來(lái)改變Linux系統(tǒng)的Overcommit策略。該參數(shù)在/proc/sys/vm/overcommit_memory文件中并采用:
0 允許適度的Overcommit。但是,不合理的內(nèi)存分配會(huì)失敗。這是默認(rèn)設(shè)置
1 總是Overcommit
2 不允許Overcommit。進(jìn)程通常不會(huì)被 OOM Killer終止,但內(nèi)存分配嘗試可能會(huì)返回錯(cuò)誤
我們應(yīng)該意識(shí)到,使用默認(rèn)策略以外的策略會(huì)對(duì)應(yīng)用程序處理內(nèi)存的方式提出更高的要求。
6. 示范
現(xiàn)在讓我們展示 OOM Killer是如何工作的。因此,讓我們模擬具有高內(nèi)存消耗的進(jìn)程。然而,他們不應(yīng)該單獨(dú)耗盡系統(tǒng)的能力。然后,只有啟動(dòng)下一個(gè)進(jìn)程才會(huì)被檢測(cè)為內(nèi)存不足威脅。
所以,讓我們使用測(cè)試腳本來(lái)吃內(nèi)存:
#!/bin/bash
for x in {0..6999999}
do
y=$x$y
done
讓我們注意到腳本分配了大量?jī)?nèi)存但沒(méi)有任何尖峰需求。因此,它的內(nèi)存使用水平很快。
該演示是在 Ubuntu 20.04 LTS 上進(jìn)行的,內(nèi)核為 5.4.0-58-generic,具有大約 4 GB 的內(nèi)存和 4 GB 的交換空間。通常,系統(tǒng)最多可以維持三個(gè)測(cè)試應(yīng)用程序同時(shí)運(yùn)行。然后,開(kāi)始第四個(gè)實(shí)例,喚醒 OOM killer。
6.1. 記錄進(jìn)程的oom_score
因?yàn)檫M(jìn)程的oom_score沒(méi)有出現(xiàn)在內(nèi)核的日志中,我們需要基于cron設(shè)備一個(gè)簡(jiǎn)單的記錄器。因此,讓我們使用oom_score_reader每分鐘記錄五個(gè)得分最高的進(jìn)程:
$ crontab -l
#some output skipped
OOMTEST=/home/joe/prj/oom
*/1 * * * * $OOMTEST/./oom_score_reader | tail -n 5<br /> >> $OOMTEST/oom_score.log && echo "-------------" >> $OOMTEST/oom_score.log
6.2. 追蹤 OOM Killer
由于我們已經(jīng)在終端中開(kāi)始了我們的任務(wù),所以在某個(gè)時(shí)候,我們將在其中一個(gè)終端中獲取一條消息:
$ ./test
Killed
讓我們?cè)趉ern.log中尋找相應(yīng)的事件:
$ grep -ia "Killed process" kern.log
Jul 7 18:40:56 virtual kernel: [ 7269.971178] Out of memory: Killed process 20257 (test) total-vm:1980996kB,<br /> anon-rss:21128kB, file-rss:0kB, shmem-rss:0kB, UID:1000 pgtables:3920kB oom_score_adj:0
然后讓我們獲取有關(guān)PID 20257的更多信息:
Jul 7 18:40:56 virtual kernel: [ 7269.971162] oom-kill:constraint=CONSTRAINT_NONE,<br /> nodemask=(null),cpuset=/,mems_allowed=0,global_oom,task_memcg=/user.slice/user-1000.slice/user@1000.service,task=test,pid=20257,uid=1000
Jul 7 18:40:56 virtual kernel: [ 7269.971178] Out of memory: Killed process 20257 (test) total-vm:1980996kB,<br /> anon-rss:21128kB, file-rss:0kB, shmem-rss:0kB, UID:1000 pgtables:3920kB oom_score_adj:0
Jul 7 18:40:56 virtual kernel: [ 7270.002859] oom_reaper: reaped process 20257 (test),<br /> now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB
在這里,oom-reaper是一個(gè)回收內(nèi)存的輔助進(jìn)程。最后,我們可以在日志文件中找到進(jìn)程的最后oom_score :
1537 13 gnome-shell
2361 24 gnome-software
20256 235 test
20257 235 test
24214 236 test
讓我們看看20257的最后得分235在記錄時(shí)是第二高的。然而,這是由于cron的粒度大至 1 分鐘。
7. 結(jié)論
在本教程中,我們了解了 Linux 管理內(nèi)存的方法。首先,我們查看了Overcommit策略,它允許任何合理的內(nèi)存分配。然后我們遇到了 OOM Killer,在內(nèi)存不足時(shí)保護(hù)系統(tǒng)穩(wěn)定的進(jìn)程。
接下來(lái),我們查看了進(jìn)程的內(nèi)存使用情況評(píng)分,并了解了如何保護(hù)它們免受 OOM Killer的攻擊。此外,我們還查看了系統(tǒng)范圍的Overcommit設(shè)置。
最后,我們提供了一個(gè) OOM Killer如何工作的例子。
歡迎點(diǎn)贊,關(guān)注,轉(zhuǎn)發(fā),Happy Coding.