ITADN
rfjakob/earlyoom
README.md
以下内容由 AI 翻译,如有问题请点此提交 issue 反馈

earlyoom - 早期 OOM 守护进程

CI MIT License Latest release

oom-killer 在 Linux 用户中通常名声不佳。这可能部分解释了为什么 Linux 只在别无选择时才会调用它。 它会先交换出桌面环境,丢弃整个页面缓存并清空所有缓冲区,最终才会杀死一个进程。至少我认为它会这样做。 我还没有足够的耐心坐在无响应的系统前等待它发生。

这让我和其他人开始怀疑 oom-killer 是否可以配置为更早介入:reddit r/linuxsuperuser.comunix.stackexchange.com

事实证明,不行。至少使用内核中的 oom-killer 时不行。 然而,在用户空间中,我们可以为所欲为。

earlyoom 旨在简单且稳固。它用纯 C 编写,没有依赖项。 一个广泛的测试套件(单元测试和集成测试)是用 Go 编写的。

它做什么

earlyoom 每秒最多检查 10 次可用内存和空闲交换空间(如果空闲内存很多,则检查频率更低)。 默认情况下,如果两者都低于 10%,它将杀死最大的进程(最高的 oom_score)。 百分比值可以通过命令行参数进行配置。

在下面的 free -m 输出中,可用内存为 2170 MiB, 空闲交换空间为 231 MiB。

总计 已用 空闲 共享 缓冲/缓存 可用 内存: 7842 4523 137 841 3182 2170 交换: 1023 792 231

为什么检查“可用”内存而不是“空闲”内存? 在健康的 Linux 系统上,“空闲”内存应该接近于零, 因为 Linux 会使用所有可用的物理内存来缓存磁盘访问。 这些缓存可以在内存被其他用途需要时随时被丢弃。

“可用”内存对此进行了计算。它汇总了所有未使用或可以立即释放的内存。

请注意,你需要较新版本的 free 和 Linux 内核 3.14+ 才能看到“可用”列。如果你拥有 较新的内核,但旧版本的 free,你可以从 grep MemAvailable /proc/meminfo 中获取该值。

当你的可用内存和空闲交换空间都低于用户空间进程可用总内存(=total-shared)的 10% 时, 它会向内核认为使用内存最多的进程发送 SIGTERM 信号(/proc/*/oom_score)。

另请参阅

  • nohang,一个类似于 earlyoom 的项目, 使用 Python 编写,并带有额外的功能和配置选项。
  • Facebook 的压力停滞信息(psi)内核补丁 以及配套的用户空间辅助工具 oomd。 这些补丁已合并到 Linux 4.20 中。

为什么不触发内核 oom killer?

你可以通过传递 --kernel-oom 标志来让 earlyoom 触发内核 oom killer(echo f > /proc/sysrq-trigger)。但请注意以下几点:

在某些 Linux 内核版本(已在 v4.0.5 上测试)中,手动触发内核 oom killer 完全不起作用。 也就是说,它可能只会释放一些图形 内存(这些内存会立即被重新分配),而实际上并没有杀死 任何进程。在这里 你 可以看到在我的机器上(Intel 集成显卡)这种情况的表现。

此问题已在 Linux v5.17 (commit f530243a) 中修复。

与 Linux 内核的默认行为一样, earlyoom 通过读取 /proc/*/oom_score 来寻找其目标进程。

earlyoom 使用多少内存?

大约 2 MiBVmRSS),但其中只有 220 kiB 是私有内存(RssAnon)。 其余部分是 libc 库(RssFile),与其他进程共享。 所有内存都使用 mlockall() 锁定,以确保 earlyoom 在低内存情况下不会变慢。

下载和编译

自行编译很简单:

git clone https://github.com/rfjakob/earlyoom.git
cd earlyoom
make

可选:运行集成自检:

make test

通过将其注册为服务来自动启动 earlyoom:

sudo make install              # systemd
sudo make install-initscript   # non-systemd

请注意,对于禁用了 SELinux 的系统(Ubuntu 19.04、Debian 9 ...),可以安全地忽略报告设置上下文失败的 chcon 警告。

对于 Debian 10+ 和 Ubuntu 18.04+,有一个 Debian 软件包

sudo apt install earlyoom

对于带有 EPEL 的 Fedora 和 RHEL 8,有一个 Fedora 软件包

sudo dnf install earlyoom
sudo systemctl enable --now earlyoom

对于 Arch Linux,有一个 Arch Linux 软件包

sudo pacman -S earlyoom
sudo systemctl enable --now earlyoom

其他发行版的可用性:参见 repology 页面

使用

只需启动您刚刚编译的可执行文件:

./earlyoom

它会告知你拥有多少内存和交换空间,最小值是多少, 可用内存有多少,以及空闲交换空间有多少。

./earlyoom
eearlyoom v1.8
mem total: 23890 MiB, user mem total: 21701 MiB, swap total: 8191 MiB
sending SIGTERM when mem avail <= 10.00% and swap free <= 10.00%,
        SIGKILL when mem avail <=  5.00% and swap free <=  5.00%
mem avail: 20012 of 21701 MiB (92.22%), swap free: 5251 of 8191 MiB (64.11%)
mem avail: 20031 of 21721 MiB (92.22%), swap free: 5251 of 8191 MiB (64.11%)
mem avail: 20033 of 21723 MiB (92.22%), swap free: 5251 of 8191 MiB (64.11%)
[...]

如果数值低于最小值,进程将被终止,直到其再次高于最小值。每个操作都会记录到 stderr。如果你正在以 systemd 服务的方式运行 earlyoom,你可以使用

systemctl status earlyoom

测试

为了查看 earlyoom 的实际效果,请创建/模拟一个内存泄漏,并让 earlyoom 执行其功能:

tail /dev/zero

检查日志

如果在进程被 earlyoom 终止后需要执行其他操作(例如发送邮件),您可以通过以下方式解析日志:

sudo journalctl -u earlyoom | grep sending

上述测试命令(tail /dev/zero)的示例输出如下:

Feb 20 10:59:34 debian earlyoom[10231]: sending SIGTERM to process 7378 uid 1000 "tail": oom_score 156, VmRSS 4962 MiB

对于 earlyoom 的较旧版本,请使用:

sudo journalctl -u earlyoom | grep -iE "(sending|killing)"

通知

从 1.6 版本开始,earlyoom 可以通过系统 d-bus 发送关于被终止进程的通知。传入 -n 以启用它们。

要在 GUI 会话中实际看到这些通知,你需要以用户身份运行 systembus-notify

此外,earlyoom 可以为每个被终止的进程执行一个脚本,并通过 EARLYOOM_PIDEARLYOOM_UIDEARLYOOM_NAME 环境变量提供有关该进程的信息。传入 -N /path/to/script 以在进程被终止后启用 ,或传入 -P /path/to/script 以在终止前调用。

警告:在 dryrun 模式下,脚本将快速连续执行,请确保你实现了某种形式的速率限制。

优先进程

命令行标志 --prefer 指定优先终止的进程; 同样,--avoid 指定 避免终止的进程。详情请参阅 https://github.com/rfjakob/earlyoom/blob/master/MANPAGE.md#--prefer-regex。

配置文件

如果你将 earlyoom 作为系统服务运行(通过 systemd 或 init.d),你可以通过 /etc/default/earlyoom 中提供的文件来调整其配置。该文件的注释中已包含一些示例,你可以基于支持的命令行选项,利用这些示例来构建自己的配置集,例如:

EARLYOOM_ARGS="-m 5 -r 60 --avoid '(^|/)(init|Xorg|ssh)$' --prefer '(^|/)(java|chromium)$'"

调整文件后,只需重启服务即可应用更改。例如,对于 systemd:

systemctl restart earlyoom

请注意,此配置文件对 systemd/init.d 之外的 earlyoom 实例没有影响。

命令行选项

earlyoom v1.8
Usage: ./earlyoom [OPTION]...

  -m PERCENT[,KILL_PERCENT] set available memory minimum to PERCENT of total
                            (default 10 %).
                            earlyoom sends SIGTERM once below PERCENT, then
                            SIGKILL once below KILL_PERCENT (default PERCENT/2).
  -s PERCENT[,KILL_PERCENT] set free swap minimum to PERCENT of total (default
                            10 %).
                            Note: both memory and swap must be below minimum for
                            earlyoom to act.
  -M SIZE[,KILL_SIZE]       set available memory minimum to SIZE KiB
  -S SIZE[,KILL_SIZE]       set free swap minimum to SIZE KiB
  -n                        enable d-bus notifications
  -N /PATH/TO/SCRIPT        call script after oom kill
  -P /PATH/TO/SCRIPT        call script before oom kill
  -g                        kill all processes within a process group
  -d, --debug               enable debugging messages
  -v                        print version information and exit
  -r INTERVAL               memory report interval in seconds (default 1), set
                            to 0 to disable completely
  -p                        set niceness of earlyoom to -20 and oom_score_adj to
                            -100
  --ignore-root-user        do not kill processes owned by root
  --sort-by-rss             find process with the largest rss (default oom_score)
  --prefer REGEX            prefer to kill processes matching REGEX
  --avoid REGEX             avoid killing processes matching REGEX
  --ignore REGEX            ignore processes matching REGEX
  --dryrun                  dry run (do not kill any processes)
  --syslog                  use syslog instead of std streams
  -h, --help                this help text

有关详细信息,请参阅 man page

贡献

欢迎通过 github 提交 bug 报告和 pull request。特别是,我很乐意 接受

  • 使用案例报告和反馈

实现说明

  • 我们未使用 procps/libproc2,因为 procps_pids_select() 出于某种原因总是解析 /proc/$pid/status。 这相对昂贵,而且我们并不需要它。

变更日志

  • v1.9.0, 2025-09-16

    • 添加 -P /path/to/script 选项,用于在终止某个进程之前执行 (#348)
    • -n-N 增加更多调试日志
    • 在终止某个进程时也记录 oom_score_adj
  • v1.8.2, 2024-05-07

    • 修复 earlyoom.service systemd 单元文件中的问题
      • process_mrelease 添加到允许的 syscalls 中 (commit)
      • 修复 IPAddressDeny 语法 (commit)
      • 允许 -p (commit)
  • v1.8.1, 2024-04-17

    • 修复由消息措辞更改引起的琐碎测试失败 (commit)
  • v1.8, 2024-04-15

    • 引入 user mem total / meminfo_t.UserMemTotal 并基于其计算 MemAvailablePercent (commit, man page 中的更多信息)
    • 使用 process_mrelease (#266)
    • 支持 NO_COLOR (https://no-color.org/)
    • 不再被具有僵尸主线程的进程所混淆 (commit)
    • 添加 --sort-by-rss,感谢 @RanHuang!这将选择具有最大 RSS 的进程进行终止, 而不是具有最大 oom_score 的进程。
    • Gitlab CI 测试套件现在也在 Amazon Linux 2 和 Oracle Linux 7 上运行。
  • v1.7, 2022-03-05

    • 添加 -N 标志,以便在每次终止进程时运行脚本 (commit, man page 章节)
    • 添加 -g 标志以终止整个进程组 (#247)
    • 移除 -i 标志(为兼容性而忽略),因为它在 Linux 内核 5.9+ 上 无法正常工作 (#234)
    • 加固:在启动时丢弃环境能力 (#234)
  • v1.6.2, 2020-10-14

    • 在终止目标进程前再次检查内存状况 (commit)
    • 永不终止自身 (#205)
    • 在 /proc/meminfo 转换错误时转储缓冲区 (#214)
  • 1.6.1, 2020-07-07

    • 清理 dbus-send 僵尸进程 (#200)
    • 跳过 oom_score_adj=-1000 的进程 (210)
  • 1.6, 2020-04-11

  • v1.5, 2020-03-22

    • -p: 将 oom_score_adj 设置为 -100 而不是 -1000 (#170)
    • 允许同时使用 both -M-m,以及 -S-s。 将使用较低的值(转换为百分比)。
    • earlyoom.default 中的内存报告间隔设置为 1 小时 而不是 1 分钟 (#177)
  • v1.4, 2020-03-01

    • 通过延迟加载进程属性使受害者选择逻辑快 50%
    • 除 pid 和名称外,记录被杀死进程的 uid 用户 ID
    • 将调试日志颜色设为浅灰色
    • 代码清理
      • 尽可能使用块局部变量
      • 引入 PATH_LEN 以替换多个硬编码的缓冲区长度
    • 扩展测试套件 (make test)
    • 在可用时运行 cppcheck
    • 添加单元测试基准 (make bench)
    • 在 systemd 单元文件 earlyoom.service 中放弃 root 权限
  • v1.3.1, 2020-02-27

    • 修复在大量 RAM 系统上的虚假测试套件失败 (issue #156)
  • v1.3, 2019-05-26

    • 在发送信号时等待进程实际退出
      • 这修复了 earlyoom 有时在只需终止一个进程时会终止多个进程的问题 (issue #121)
    • 对 SIGTERM 和 SIGKILL 的限制值接受范围更宽松 (issue #97)
      • 如果 SIGTERM 限制 < SIGKILL 限制,不要以致命错误退出
      • 允许 SIGKILL 限制为零
    • 重新格式化启动输出,以明确说明 swap 和 mem 都必须 <= limit
    • 添加 notify_all_users.py 辅助脚本
    • 添加 CODE_OF_CONDUCT.md (Contributor Covenant 1.4) (#102)
    • 修复日志输出中可能被截断的 UTF8 应用名称 (#110)
  • v1.2, 2018-10-28

    • 实现自适应睡眠时间(= 自适应轮询率)以进一步降低 CPU 使用率(issue #61)
    • 移除使用内核 oom-killer 的选项(-k,现出于兼容性考虑被忽略) (issue #80)
    • 优雅处理在 earlyoom 启动后添加或移除交换空间的情况 (issue 62, commit)
    • 实现分阶段终止:先发送 SIGTERM,再发送 SIGKILL,并支持可配置的阈值 (issue #67)
  • v1.1, 2018-07-07

    • 修复通过 GUI 通知可能发生的 shell 代码注入漏洞 (commit)
    • 若未能终止任何进程,仅休眠 1 秒而非 10 秒 (issue #74)
    • 在终止进程之后发送 GUI 通知,而非之前 (issue #73)
    • -h 外,还接受 --help
    • 修复日志和终止通知中进程名称错误的问题 (commit 1, commit 2, issue #52, issue #65, issue #194)
    • 修复使用 -S 时可能发生的除以零错误 (commit)
  • v1.0, 2018-01-28

    • 添加 --prefer--avoid 选项(@TomJohnZ)
    • 添加对 GUI 通知的支持,添加选项 -n-N
  • v0.12: 添加 -M-S 选项(@nailgun);添加 man 页面,参数化 Makefile(@yangfl)

  • v0.11: 修复 get_entry_fatal 中的未定义行为(缺少 return,commit)

  • v0.10: 允许覆盖 Makefile 中的 VERSION 变量以简化打包, 添加 -v 命令行选项

  • v0.9: 若所有进程的 oom_score 均为 0,则使用 VmRss 来查找受害者

  • v0.8: 如果内核未提供 MemAvailable,则使用估算值

  • v0.7: 通过 oom_score 而非 VmRSS 选择受害者,添加选项 -i-d

  • v0.6: 添加命令行选项 -m-s-k

  • v0.5: 添加交换分区支持

  • v0.4: 添加 SysV init 脚本(感谢 @joeytwiddle),使用 /proc/meminfo 中的新 MemAvailable (需要 Linux 3.14+,commit

  • v0.2: 添加 systemd 单元文件

  • v0.1: 初始版本