LWN:修正per-VMA locking的问题!

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

Stabilizing per-VMA locking

By Jonathan Corbet
July 13, 2023
ChatGPT assisted translation
https://lwn.net/Articles/937943/

内核开发过程经常会合入一些对最基本的子系统的大规模更改,并仍然能够每九到十周就发布一次稳定版本。但偶尔会出现开发社区不想看到的情况。在 6.4 版本中进行的 per-VMA locking 的工作就是一个例子。这看起来是一个经过充分测试的可以让 page fault 更加容易支持更大规模(scalability)的改动。然而这段代码中隐藏了一些魔鬼,让早期采用 6.4 内核的用户遇到了困难。

mmap_lock 控制了对进程的地址空间的访问。如果一个进程在同一时间做了很多事情,例如拥有大量线程,那么对 mmap_lock 的竞争就可能会导致严重的性能问题。处理 page fault 的时候可能需要更改 page-table entry,或需要扩展虚拟内存区域(VMA),就一直需要使用 mmap_lock 保护,这意味着许多线程同时生成 page fault 的话就会导致进程执行速度变慢。

开发人员多年来一直在努力解决这个问题,他们通常希望能改成类似 speculative page-fault handling 的方式来处理, 希望能在没有锁保护的情况下完成工作。如果在处理 fault 时发生竞争,内核会放弃之前已经完成的工作,然后重新开始。然而这项工作从未合并进来。最终,Suren Baghdasaryan 的 per-VMA 加锁的工作成果被合并了。由于 page fault 发生在特定的 VMA 内,因此相应的处理就只需要确保对该 VMA 的访问是依次进行就好,而不需要对整个地址空间加锁来保证依次处理。因此,per-VMA locking 是 mmap_lock 的一个更细粒度的版本,可以在执行特定任务时提供更高的并行性。

经过几次迭代,这项工作在 6.4 合并窗口期间被合并到 mainline 内核。进入 mainline 内核通常会引入对这个改动的更多测试,但在整个 6.4 开发周期中,per-VMA locking 都没有出现任何问题。看起来,per-VMA locking 的所有问题已经解决了一样。

Trouble arises

然而,mainline 内核的发布会引入更加多的测试。6.4 版本于 6 月 25 日发布了,Jiri Slaby 在 7 月 2 日报告了一个跟 Go 语言构建比较相关的问题。Jacob Young 随后报告了"频繁但随机的 crash" 的问题,并追查到一个能够重现该问题的非常简单的测试例。这两种情况的调查都很快指向了 per-VMA locking 的改动。看起来,在内核中某些情况下发生了意料之外的并发访问,损坏了数据结构。

当用户升级到新的"stable"内核版本时,他们并不希望遇到随机 crash 或者数据损坏。还有,StackRot 漏洞也引入了不少重要改动,修改了对 stack expansion 的处理,这些改动没有经过公开 review 而很快就被包含在 6.4.1 的稳定更新中。该工作在 6.4 内核中引入了另一个 VMA-locking 相关的 bug,因为它增加了另一种需要确保序列执行的情况。

Baghdasaryan 对所有这些问题都迅速做出了响应,并发布了一个 patch 直接禁用了这个功能,等待今后问题解决。该补丁需要做一些特殊改动,但一直没能合入到 mainline 内核。内存管理维护者 Andrew Morton 希望再 "等待几天",看看是不是能出现正确的 fix 方案。与此同时,Greg Kroah-Hartman 表示,在 mainline 内核中没有任何 patch 之前,stable 内核将无法采取任何措施。因此 6.4 内核就没有采取任何措施来应对这个严重 bug。

7 月 5 日,负责 regression-tracking 的 Thorsten Leemhuis 想知道需要等待多久,他指出那些快速前进的社区发行版可能已经开始使用 6.4 内核。Morton 回答说,他会在届时发布已有的 fix patch,但实际上他还是晚了几天,Leemhuis 认为他今后需要更早地对这种严重的 regression 问题发出警告。他认为如果能及时发出警告,问题可能会得到更快地解决。

7 月 8 日,Morton 向 Linus Torvalds 发送了一组 patch,其中包括了禁用 per-VMA locking。然而,在合并这组 patch 时,Torvalds 撤销了这个改动,因为当时已经有其他三个 patch 直接进入 mainline 内核:

  • 第一个 patch 为新的 stack-expansion 代码增加了 lock 机制,于是修复了这个 security fix 引入的问题。

  • 第二个 patch 确保新创建的 VMA 在对内核的其余部分可见之前都是被 lock 住的。这个 fix 的问题并不影响任何已经发布的内核,但它是一个潜在的 bug,会在未来扩展 per-VMA locking 的时候暴露出来。

  • 最后这个 patch 修复了被报告出来的问题。如果一个进程调用了 fork(),之后在将 VMA 复制到子进程时发生 page fault,可能导致 VMA 的损坏。fix 方法是在开始复制之前明确对每个 VMA 都加锁,这会导致非常频繁使用 fork() 的场景下会出现高达 5%的速度损失。

注意,Torvalds 反对关闭 per-VMA locking 的想法。相反,他坚持认为如果无法 fix 的话就应将其完全删除:

我认为,如果 per-VMA locking 出现了如此严重的问题导致需要在开发内核中禁用掉,那我们应该承认失败,并 revert 所有改动。

而不是采取"先 revert 掉等将来再尝试"的方式。

也就是说,"这个 revert 是因为它增加了我们无法解决的问题",意味着根本不再尝试这种方案,需要在今后有人提出更好易维护的模型之前需要进行更多的思考。

从表面上看,这些 fix 措施(于 7 月 11 日包含在 6.4.3 的 stable update 中)应该是足够了,per-VMA locking 现在是稳定的。因此,在这种情况下,不需要 revert,也不需要更多的思考了。不过,值得注意的是,在 6.4 发布之前的时候这个改动看起来也挺稳定的。

Closing thoughts

per-VMA locking 对内核的核心子系统进行了根本性的改动,将一些 page fault 处理代码移到了一个庞大锁定范围之外,这个锁的保护范围可能没有人能够完全想清楚。因此出现一些微妙的 bug 并不意外。在不引发问题的情况下对内核进行这种改动是很困难的,但这也正是为了让 Linux 能适应当代的需求,开发人员所必须要进行的那种改动。

从理论上讲,使用 Rust 等语言的优势之一就是能够避免一些微妙的 lock 问题。然而,在内核这个上下文中,是否可以真正保证使用 Rust 编写的 VMA 抽象能在保持性能的同时也使用了合适的锁,这一点尚未得到证明。

可以说,这个 regression 问题可能可以更好地处理。也许应该有一种方法在稳定发布中立即"禁用有问题的功能"的 patch,哪怕该改动尚未进入 mainline 内核,甚至是预计在不久的将来会有更好的 fix 方法的情况下。内核开发人员经常强调,最新的内核是社区所知道的最好的内核,用户在升级到最新版本的时候不应该犹豫。因此当升级被证明是个坏主意时,能够更快地做出反应就是有助于增强人们信任这个建议的信心。

不过与此同时,开发人员对这个问题做出了迅速的反应,并找到了可以保留该功能的适当 fix 措施。受到该错误影响的用户数量希望是比较少的。换句话说,可以说这个问题处理得相当好,我们所有人都可以受益于这个工作来享受更快的内存管理所带来的好处。

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

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

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

format,png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值