关注了就能看到更多这么棒的文章哦~
The trouble with MAX_ORDER
By Jonathan Corbet
January 1, 2024
ChatGPT translation
https://lwn.net/Articles/956321/
或许人们认为关于定义整数常量值的一个很简单宏可能没有太多可说的。但内核似乎不是这样。对 MAX_ORDER
定义的更改带来了许多随之而来的影响,清理这个改动的任务尚未完成。因此,也许有必要对 MAX_ORDER
进行一番了解。
与 Linux 内核最早的版本里其他部分一样,内存管理也是很简单的实现,主要用来做单个页面的跟踪和管理。然而,到了 1994 年左右,随着内存大小的增加和内核的复杂化,很明确需要能处理较大块的页面。1994 年 4 月的 1.1.0 发布包含了内核的“伙伴分配器(buddy allocator)”的雏形,它以二的幂次方 size 的 block 作为单位来跟踪和分配页面。
随之而来的是“order(阶)”的概念,它只是内存块中页面数的二进制对数。阶为 0 的块就是单一 page,阶为 1 的块包含两个 page,依此类推。在 1.1.0 中,不同阶的内存块被保存在单独的列表中,有一个宏(NR_MEM_LISTS)描述了有多少个这样的列表。最初它被赋值为 6,这意味着可以管理的最大内存块是 order 为 5 的 block,也就是 32 个页面。1997 年 1 月的 2.1.23 发布里增加了AP1000 CPU的特殊情况支持,该 CPU 需要能够分配 8MB(阶为 11)的内存块;如果内核为该体系架构来构建的话,则将 NR_MEM_LISTS
设置为 12。
在 2.3.27(1999 年 11 月)中, NR_MEM_LISTS
得到了一个新的名字 — MAX_ORDER
— 并对除 AP1000 外的所有情况都设置为 10。对 AP1000 的支持最终在 2000 年 2.3.42 中被移除,但调整 MAX_ORDER
来适应特定体系架构的做法变得越来越普遍。MAX_ORDER 的默认值现在可以在不同体系结构之间有很大的变化;在大多数系统上默认为 11。因此,在这些系统上,页面可以以 order 为 0 到 10 的内存块作为分配单位。
MAX_ORDER
目前的一个有趣之处是,按照迄今为止的描述,它实际上并不是最大 order;如果 MAX_ORDER
为 11,则可以分配的最大阶为 10。2023 年 3 月,Kirill Shutemov 决定解决这个不一致性,因为他注意到 MAX_ORDER
的一些代码中存在误解含义而导致的 bug;于是生成了这一系列补丁,修复了九个不同地方的此类 bug,包括了一个长期存在的bug,位于软盘驱动程序中。
然而,工作并没有就此结束;在这一系列的最后,Shutemov 重新定义了MAX_ORDER的含义:分配可以达到的最大 order。因此,MAX_ORDER 现在通常默认为十就导致内核(仍然)维护 11 个空闲块列表。当时,对于这一变化大部分反馈都积极接受,尽管 Mel Gorman 担心这一改动可能会导致 stable kernel 分支出现问题。David Laight 建议更改 MAX_ORDER
的名称将有助于防止这些问题,但是这个建议被忽略了,Shutemov 的一系列补丁在 2023 年 6 月的 6.4 版本中合并。
当时似乎问题已经解决。然而,在接下来的九月底,Paolo Bonzini 指出三个改动都是与 MAX_ORDER 改动同时开发的,它们仍在使用旧的含义。这些改动在编写时是正确的,但在 MAX_ORDER 更改后就出现问题了,但没有人注意到。近两个月后,Mike Snitzer 发送了一个拉取请求,其中包含了对其中一个 bug 的修复,该错误是由于为 6.5 合并的 dm-crypt 目标更改引入的。人们发现那些 out-of-tree 的代码比如 backport 代码就可能会被这一改动悄悄地破坏 — 这是在考虑 MAX_ORDER 改动时没有人提出的一个担忧。
这个错误导致 Linus Torvalds 质疑是否应该进行这个更改。他建议在没有强力理由需要保持这一改动的情况下,干脆撤销掉;他后来补充道:
啊。我看了一下引出这个改动的 commit,我真的认为“如果这是 24 年历史的结果,那么它并不是一件大事”。
与语义变更对 backport 的必然出现的问题相比(在这个合并窗口已经导致了问题),我仍然认为这一切很可能是一个错误。
他还说,与 MAX_ORDER
相关的错误通常不容易通过测试发现,因为很少会分配最大尺寸的内存块。
Shutemov 回应道,他在那个系列中修复的 bug 只是进入主线的并且从未被发现过的 bug;“我个人因 MAX_ORDER-1 的事情花了一些时间调试”。 Shutemov 接着说,真正的错误在于(正如 Laight 所指出的)保留了 MAX_ORDER 名称而改变其语义:“只要将其重命名为 MAX_PAGE_ORDER 或类似的东西就会提醒那些尚未推到 upstream 的代码”。他随后提交了另一个补丁,添加了一个新的符号 NR_ORDERS
,定义为 MAX_ORDER+1
。Torvalds 同意这一更改,但坚持认为 MAX_ORDER 的名称需要改掉以避免将来出现问题。
在十二月底,Shutemov 发布了两个新的补丁进行这些更改。其中第一个引入了新名称(现在是 NR_PAGE_ORDERS
),描述了页面分配器支持的分配 order。第二个然后将 MAX_ORDER
重命名为 MAX_PAGE_ORDER
,结果是任何使用 MAX_ORDER
的外部代码现在将无法编译。这一变更应该防止 dm-crypt bug 的重复;它还应该防止 MAX_ORDER
的错误进入稳定的内核。
这些补丁似乎可能在 6.8 合并窗口打开后被合入,这将结束内核中 MAX_ORDER
的漫长存在历史。这个故事的教训是名称确实很重要;错误的名称可能导致产生不正确的代码。同样重要的是,名称的含义不应该在没有引起可能受影响代码的注意的情况下被更改。如果一个名称不对,它可能也完全应该继续保留。
全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。
欢迎分享、转载及基于现有协议再创作~
长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~