我正在使用 Linux 5.15 和 Ubuntu 22.04。
我有一个使用大量内存的进程。它需要的内存比我机器上的 RAM 还要多。我第一次运行它时,它被 OOM Killer 杀死了。我的理解是:系统内存不足,OOM Killer 被触发,我的进程被杀死。这是有道理的。我也确信这就是发生的事情:我看了一下dmesg
,一切都在那里。
所以我添加了一些交换空间。我不介意这个过程是否需要很长时间才能运行:我不会经常运行它。
我再次运行该过程。这次比第一次跑的时间更长。整个系统变得非常滞后,系统在进行大量交换时就会出现这种情况。它似乎起作用了……然后就死了。不仅进程死了,它的父进程 shell 进程也死了,它的父进程 Tmux 进程、Tmux 进程的父进程 shell 进程,甚至是 GNOME 终端进程也死了。它是家长!但随后谋杀过程停止了:不再有父母死亡。
起初,我以为 OOM Killer 再次被触发——尽管仍有大量交换空间可用——并且它选择终止 GNOME 终端进程。但我查了一下dmesg
,并journalctl -k
没有什么新的东西。没有任何迹象表明 OOM Killer 已被触发。
那么,第一个问题:是否存在任何情况下可以触发 OOM Killer 而不将任何内容记录到内核环形缓冲区?
让我困惑的是,Linux 内核似乎已经开始交换,但不知何故它交换得不够……或者交换得不够快……或者其他什么。
所以我增加了vm.swappiness
。这确实不应该影响系统稳定性:它只是一个用于性能优化的旋钮。即使vm.swappiness
设置为0
内核,当区域中的可用内存降至临界阈值以下时,内核仍应开始交换。
但似乎它已经开始交换,但交换得还不够……所以我增加了vm.swappiness
鼓励100
它交换更多一点。
然后我再次运行该过程。整个系统变得非常滞后,系统在进行大量交换时就会这样做......直到进程成功运行完成。
那么,第二个问题:为什么内核不使用可用的交换空间,即使空闲内存已降至临界阈值以下并且肯定有足够的可用交换空间?为什么改变vm.swappiness
会带来改变?
更新:
进一步的测试表明该设置vm.swappiness
不是一个可靠的解决方案。即使vm.swappiness
set to ,我也遇到过一些失败100
。它可能提高该过程成功完成的机会,但我不确定。
答案1
在完全使用可用交换空间之前发生 OOM 事件的原因有多种,并且 OOM 事件可能会触发 OOM-killer 线程或更糟……令人讨厌的信号:
A/ 关于内存分配和 OOM 事件的一般性
因为内核开发人员知道很多程序malloc()大量内存“万一”并且不要使用太多它,至少,可能静态地期望系统上运行的所有进程不会同时需要它们请求的内存,内核实际上并不在 malloc 处保留内存(或朋友)点。
相反,它将等待第一次对内存的写访问(这必然会导致页面错误)以进行真正的映射。
如果此时没有立即可用的内存,内核将等待更好的日子 (1),如果这些更好的日子来得不够快,则会触发 OOM 事件。 OOM 事件,取决于一些 sysctl 设置(panic_on_oom),将触发 OOM-killer 或生成内核恐慌。
B/ 为什么无论交换区中有多少可用空间,都可能发生 OOM 事件(2)
- B.1/ 因为交换过程在释放空间方面不够快:
如§A 所示,内核不会等待很长时间,一些内存就会变得可用。所以如果没有正在运行的进程恰好释放一些内存和文件系统缓存已经减少到严格的最小值,因此换出是释放内存页面的唯一方法……这不适合宽限期。即使可以交换 Gig 内存,OOM 事件也会被触发。
对磁盘的随机访问很慢,对交换区域的访问甚至更慢,因为交换空间可能与正在运行的进程使用的文件系统位于同一磁盘上。
尽管如此,还是有一种方法可以避免系统陷入这种情况。记住阿喀琉斯和乌龟: 尽早开始更换。当系统不需要物理内存时开始移动页面。
这就是你实际上间接 (3)增加时设法获得交换性。但是,因为这只是您设置的副作用,“最好的”设置受到高标准差的影响,并且高度依赖于工作负载。需要基准。 (4)(5) - B.2:因为系统已经交换了所有可以交换的东西
流程使用mlock()系统调用可以获得每个设计保证不可交换的页面。更差 ?mlockall()
(6)
这确实会导致相当数量的 MB 不可交换。
巨大的TLB页面在内存压力下也无法换出,cat /proc/meminfo
将报告为服务其目的而保留的内存量。
C/ 为什么当内存压力很大时线程可以终止,而 OOM-killer 不会记录任何内容。 (7)
- C.1:按应用程序设计
过度分配的决定是由内核在malloc
发布时做出的。尽管内核默认为“乐观策略”,内核拒绝保留请求,向malloc()
调用线程返回 NULL 指针的情况总是可能发生。
在这种情况下,根据调用进程如何处理此异常,它将等待更好的时间来更新其请求,或者只是优雅地中止,甚至......只是忽略和段错误,这会终止或导致级联的父母过早死亡,这进而释放相当数量的内存,而不需要 OOM-killer 干预。 (并且再次无论交换中剩余的空间如何) - C.2/ 因为某些线程捕获了一些令人讨厌的信号 因为系统也可以容忍大页的过度分配,如果缺页时不存在大页,该任务被发送 SIGBUS 并且经常不幸死亡。
1:嗯嗯更好的毫秒实际上,因为它最多会检查六次,中间会等待几纳秒。请注意,这些数字属于我对现在旧内核的记忆,从那以后它们可能已经改变。
2:请注意,严格来说,Linux 并不交换自从交换指将整个进程地址空间转移到磁盘。 Linux实际上实现了寻呼因为它实际上传输单个页面。然而文档和讨论使用交换… 就这样吧。
3: “间接地”因为提前开始交换只是该设置的副作用,该设置主要是为了告诉您的偏好文件系统缓存与进程的页面。
由于文件系统的IO开销很大,linux会使用尽可能多的物理内存来进行缓存。
swappiness 的值越高,系统在进程启动时交换进程页面的积极性就越大,这会顺便增加在内存压力下可快速回收的缓存页面数量。
4:顺便说一句,这也解释了您的问题的反面:为什么系统在有大量可用的可用 RAM 的情况下进行交换?
5:虽然我们可以看到主要机构(RHEL、ORACLE...)建议将交换性设置为严格的最低值...(并购买更多 RAM...)Morton(领先的内核开发人员)强烈建议将值设置为 100。
随着技术的可用性,例如交换,可能会使交换成本比文件系统 IO 更便宜,所以 swappiness 的值大于 100 甚至不是荒谬的。
6:
mlockall() locks all pages mapped into the address space of the calling process. This includes the pages of the code, data, and stack segment, as well as shared libraries, user space kernel data, shared memory, and memory-mapped files. All mapped pages are guaranteed to be resident in RAM when the call returns successfully; the pages are guaranteed to stay in RAM until later unlocked.
7:请记住,即使启动了,OOM 杀手也相当……懒惰,更喜欢让讨厌的任务自行终止。因此,如果罪魁祸首的信号正在等待……OOM 杀手将等待他们采取行动……以防万一……
答案2
首先,我要感谢MC68020感谢您花时间为我调查此事。碰巧的是,他们的答案并不包括这种情况下真正发生的事情 - 但他们无论如何都得到了赏金,因为这是一个很好的答案,并且对未来有帮助的参考。
我还要感谢菲利普·库林他的回答虽然也不完全正确,但却为我指明了正确的方向。
问题原来是系统管理工具。
该问题及其解决方案描述如下:如何在 Ubuntu 22.04 中禁用 systemd OOM 进程杀手?
简而言之:
systemctl disable --now systemd-oomd
systemctl mask systemd-oomd
现在我每次都可以可靠地运行我的进程来完成,而无需某些 systemd 服务在没有警告的情况下杀死整个进程树。
答案3
我不知道任何可能导致 OOM 杀手杀死进程的原因,但没有记录这一事实。在一种边缘情况下,OOM Killer 可能会停止负责将内核日志写入磁盘的进程。从你的描述来看这似乎不太可能。
我将从您的描述中提取两个重要且相关的细节:
- 缺少 OOM-Killer 日志
- 事实上,整个进程树包括图形用户界面窗口消失了。
这有点猜测,但这听起来像是 GUI 本身正在杀死它。
很可能是颠簸使它看起来像是坠毁了。我见过一些例子,例如,浏览器由于频繁的抖动而崩溃。崩溃检测器看不到任何活动,并假设程序本身出了问题,不理解程序只是在等待内核响应。
我会尝试切换控制台并在没有 GUI 的情况下从命令行运行它。这至少可以排除来自 GNOME 本身的任何干扰。