1. 概述
Linux 操作系统有特定的内存管理方式。其中一项策略是Overcommit,它允许应用程序提前预订所需的内存。然而,承诺的内存在实际使用时可能无法使用。然后,系统必须提供一种特殊的方法来避免内存不足。
在本教程中,我们将了解内存不足 (OOM) Killer,这是一个为了系统稳定性而消除应用程序的过程。
2. OOM Killer调用时
让我们注意到,要让Killer发挥作用,系统必须允许Overcommit。然后,根据系统从消除它中获得的收益对每个进程进行评分。
最后,当遇到低内存状态时,内核会杀死得分最高的进程。
我们可以在/proc/PID/oom_score文件中通过进程的PID找到进程的分数。现在,让我们启动一个终端并打印它的进程分数,因为$$变量保存它的PID:
$ cat /proc/$$/oom_score
0
接下来,让我们列出所有进程及其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
我们使用进程替换来为读取命令提供ps的结果。
现在让我们检查一下结果:
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
分数取值从 0 到 1000。让我们注意oom_score的值为零意味着该进程对于 OOM Killer是安全的。
3. 保护进程免受 OOM Killer 攻击
现在,让我们降低消除该过程的可能性。这对于长期存在的流程和服务尤为重要。所以,对于这样一个过程,我们应该设置oom_score_adj参数。
该参数取值范围从 -1000 到 1000(含)。因此,它的负值会降低oom_score,使该过程对Killer的吸引力降低。相反,正值会导致分数上升。最后,oom_score_adj = -1000的进程不会被杀死。
我们应该检查文件/proc/PID/oom_score_adj 中的参数。所以,对于我们的终端:
$ cat /proc/$$/oom_score_adj
0
让我们看看终端的分数在任何一个方向上都没有调整。
3.1. 手动设置oom_score_adj
作为最简单的方法,我们可以手动写入oom_score_adj文件。首先,让我们检查一下Firefox网络浏览器进程的分数。我们需要pgrep来获取它的PID:
$ cat /proc/$(pgrep firefox)/oom_score
60
接下来,让我们让它更容易被杀死:
$ 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
请注意,我们需要sudo权限才能将调整因子降低到零以下。
3.2. choom命令
我们应该使用choom来报告分数并修改其调整。该命令是util-linux软件包的一部分。因此,让我们使用p开关再次检查Firefox进程:
$ choom -p $(pgrep firefox)
pid 3061's current OOM score: 40
pid 3061's current OOM score adjust value: -30
然后,让我们通过n开关提供oom_score_adj的新值来增加它的分数:
$ 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 立即启动一个进程:
$ 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. 配置服务
对于服务,我们可以永久调整位于或链接在/etc/systemd文件夹中的服务配置中的分数。因此,我们需要编辑服务部分中的OOMScoreAdjust条目。例如,让我们看一下snapd服务的配置:
[Unit]
Description=Snap Daemon
# some output skipped
[Service]
# some output skipped
OOMScoreAdjust=-900
ExecStart=/usr/lib/snapd/snapd
# more output skipped
4. oom_score是如何计算的
我们应该知道,分数的计算方式取决于内核版本。除了内存占用之外,旧版本可能会考虑运行时间、nice级别或 root 所有权。
然而,在版本 5 中,只有总内存使用量很重要。要找到它,我们应该检查oom_kill.c源文件中的函数oom_badness 。
首先,函数检查进程是否免疫。通常,这要归功于oom_score_adj = -1000。在这种情况下,任务获得零分。
否则,将汇总任务的 RAM、虚拟内存和交换空间大小。然后将结果除以总可用内存。最后,该函数将比率归一化为1000。
这时,oom_score_adj就派上用场了。所以,它增加了分数。因此,它的负值实际上降低了这个值。
现在我们应该意识到,如果这个过程对我们很重要,我们需要自己照顾它的生存能力。因此,我们应该适当调整它的oom_score_adj参数。
5. 控制Overcommit的系统范围参数
我们可以通过设置overcommit_memory参数来改变Linux系统的Overcommit策略。该参数在/proc/sys/vm/overcommit_memory文件中并采用:
0 允许适度的Overcommit。但是,不合理的内存分配会失败。这是默认设置
1 总是Overcommit
2 不允许Overcommit。进程通常不会被 OOM Killer终止,但内存分配尝试可能会返回错误
我们应该意识到,使用默认策略以外的策略会对应用程序处理内存的方式提出更高的要求。
6. 示范
现在让我们展示 OOM Killer是如何工作的。因此,让我们模拟具有高内存消耗的进程。然而,他们不应该单独耗尽系统的能力。然后,只有启动下一个进程才会被检测为内存不足威胁。
所以,让我们使用测试脚本来吃内存:
#!/bin/bash
for x in {0..6999999}
do
y=$x$y
done
让我们注意到脚本分配了大量内存但没有任何尖峰需求。因此,它的内存使用水平很快。
该演示是在 Ubuntu 20.04 LTS 上进行的,内核为 5.4.0-58-generic,具有大约 4 GB 的内存和 4 GB 的交换空间。通常,系统最多可以维持三个测试应用程序同时运行。然后,开始第四个实例,唤醒 OOM killer。
6.1. 记录进程的oom_score
因为进程的oom_score没有出现在内核的日志中,我们需要基于cron设备一个简单的记录器。因此,让我们使用oom_score_reader每分钟记录五个得分最高的进程:
$ 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
由于我们已经在终端中开始了我们的任务,所以在某个时候,我们将在其中一个终端中获取一条消息:
$ ./test
Killed
让我们在kern.log中寻找相应的事件:
$ 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
然后让我们获取有关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是一个回收内存的辅助进程。最后,我们可以在日志文件中找到进程的最后oom_score :
1537 13 gnome-shell
2361 24 gnome-software
20256 235 test
20257 235 test
24214 236 test
让我们看看20257的最后得分235在记录时是第二高的。然而,这是由于cron的粒度大至 1 分钟。
7. 结论
在本教程中,我们了解了 Linux 管理内存的方法。首先,我们查看了Overcommit策略,它允许任何合理的内存分配。然后我们遇到了 OOM Killer,在内存不足时保护系统稳定的进程。
接下来,我们查看了进程的内存使用情况评分,并了解了如何保护它们免受 OOM Killer的攻击。此外,我们还查看了系统范围的Overcommit设置。
最后,我们提供了一个 OOM Killer如何工作的例子。
欢迎点赞,关注,转发,Happy Coding.
网友评论