LWN:趋近“小内存分配不会失败”的规则!

关注了就能看到更多这么棒的文章哦~

Toward a real "too small to fail" rule

By Jonathan Corbet
March 18, 2024
ChatGLM translation
https://lwn.net/Articles/964793/

Kernel 开发人员长期以来一直被告知,任何一次分配内存的动作都可能会出错失败,因此他们的代码必须准备好应对内存分配不到的情况。然而,内核的内存管理子系统在某种程度上实施了一个策略,即在申请某个特定 size 以下的时候(至少在进程上下文中)不会失败,无论内存有多紧张。最近在 linux-mm 列表上进行了讨论,关于将 "太小而不会失败" 规则变为开发人员可以依赖的规则。

内核无法使用虚拟内存,因此严格受限于系统中的物理内存数量。根据正在运行的工作负载的类型,内存可能会被用各种方式被占用,并且无法分配给其他地方。允许分配请求失败,就让内核有更大的自由可以在内存压力较大时避免使情况更糟。

当然,使内存分配请求失败也有一些不利之处。需要该内存的那些操作本身也很可能失败,而该失败可能会传播到用户空间,导致用户不满。此外,即使开发人员已经非常谨慎,内核也有很大的可能无法正确处理分配失败。因为失败路径很难测试到;内核中的许多路径可能从未被执行过,因此很多可能都存在错误。在操作进行到一半时撤销操作(unwinding an operation)的处理可能会非常复杂,这不是人们希望看到会交给未经测试的代码来处理的工作。

最近,Neil Brown 在关于内存管理策略的广泛讨论中启动了一个子线程讨论,他建议重新考虑关于 GFP_KERNEL 分配的规则。目前,程序员必须准备好这些调用会失败,即使实际上内核不会让小型分配失败。Brown 提议将 "太小而无法失败" 的行为作为一个明确规则,至少适用于预定义大小以下的分配。他说, GFP_KERNEL 分配允许休眠,因此可以利用内核释放内存的所有各种机制。在最坏的情况下,可以调用 out-of-memory (OOM) killer 从系统中移除一些进程。如果此代码无法创建一些空闲内存,他说,"机器肯定完蛋了"。相反,如果 GFP_KERNEL 分配总是成功,他总结说,"这将使我们能够删除大量未经测试的错误处理代码"。

Kent Overstreet提出了反对意见。他表示,内核代码通常会尝试分配内存来高效执行任务,但如果内存不可用,则可以退回到较慢的处理方式上去;如果内存请求不失败,这样的机制将无法运行。更糟糕的是,内核为满足此类请求而进行的努力可能会导致系统中其他地方的性能恶化。没有分配失败,就没有信号可以表明内存紧张;他说,用于用户空间的内存超额分配的实现使得人们无法在这种情况下高效使用内存。

他说,真正的解决方案是对所有这些错误路径进行适当的测试;"依赖于 OOM killer 并且导致我们不编写和测试代码中的错误路径,这是一种懒散的应对方式"。James Bottomley 不同意,指出 OOM killer 仅在极端情况下运行,错误路径是一个问题。"错误路径是 C 语言中最少被执行,但最多出现错误,因此容易被利用的代码片段。如果我们能摆脱它们,我们就应该摆脱它们。"Overstreet 却不以为然:"需要有能正常工作的错误处理路径,这是 基本的 要求,学会如何测试你的代码也是基本要求。如果你不愿意做到这一点,那你就不应该编写内核代码。"

而 Dave Chinner则对这个想法表示了热情的支持。他说,XFS 文件系统最初是为一个提供了分配保证的内核(IRIX)开发的。"将长期存在的行为改变为我们可以依赖的实际归在的这个简单变动,意味着我们可以删除代码和测试所有排列组合的开销 - 这在我看来是双赢的。"

Brown 后来稍微修改了他的提议,指出改变 GFP_KERNEL 的语义可能会对现有代码造成问题。相反,也许 GFP_KERNEL 完全可以被弃用,取而代之的是一组新的分配类型。他后来提出了这样的层次结构:

  • GFP_NOFAIL 将显式请求"不会失败"的行为,因此可能会等待很长时间才能满足分配请求。

  • GFP_KILLABLE 与 GFP_NOFAIL 相同,唯一的区别在于,在存在 fatal signal 时请求将失败。

  • GFP_RETRY 将尝试多次满足分配请求,但如果没有取得进展,最终将失败。

  • GFP_NO_RETRY 仅允许一次尝试(可能仍然会休眠)分配内存,之后请求将失败。

  • GFP_ATOMIC 完全不会休眠(这是当前行为)。

考虑到这些选项,他说, GFP_KERNEL 可能会被淘汰:

我不明白 "GFP_KERNEL" 如何适配到这一些组合中去。它的定义是"我会很努力尝试,但可能会失败,我们无法真正告诉你会在什么情况下失败" 没有什么吸引力。

Overstreet再次回应,表示这些改变并不需要:"我们只需要确保错误路径得到测试 - 我们需要注入更多的实际故障,仅此而已。"Chinner则评论说, GFP_KILLABLE 和 GFP_RETRY 本质上是相同的东西;Brown回应说,也许这些分配类型的关键区别特征是它们不会调用 OOM killer;也许它们两者都可以替换为单一的 GFP_NOOOM 类型。"我们可能需要比 GFP_NOOOM 更好的名称 :-)。"

Matthew Wilcox提出了 另一种反对意见。对于任何给定请求的适当分配策略取决于发出请求的上下文;从中断处理程序调用的函数比在进程上下文中运行的函数具有更少的选项。有时,知晓这个上下文信息的代码在调用链中距离执行分配操作的函数有几好几层函数调用。他说,设置分配类型的方法是通过应用于当前线程的上下文标志来实现的。

Brown却指出,这个上下文并不代表完整全面的信息。如果代码已经假定了 GFP_NOFAIL 的行为,那么就不允许上下文把分配动作改成一个可以失败的:"上下文不能添加错误处理"。

Vlastimil Babka担心将 GFP_KERNEL 弃用将是一个永无休止的任务。相反,保证 "太小而无法失败" 这个动作可以很快完成,并修改特定的调用位置来允许分配失败将是一个相对容易的任务,因此他建议采取这种方式。但 Brown却回答道,移除大内核锁也花了很长时间:"我认为我们不应该害怕这个。"他说,重新定义 GFP_KERNEL 还意味着移除错误处理代码,因此应该一次只处理一个调用位置。

讨论到这个地步后逐渐平息,但很可能我们会再次听到这些想法。内核实际上已经针对 8 个 page 或更少的分配实现了 GFP_NOFAIL 的行为。将该行为转变为一个保证,将允许代码显着简化,并删除大量未经测试的代码。这是一个具有重大吸引力的想法;如果五月份的Linux存储、文件系统、内存管理和BPF峰会没有提到它,那将会令人惊讶。

全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。

欢迎分享、转载及基于现有协议再创作~

长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~

6d48148978dcbf9c26b972d0189a7c2d.jpeg

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值