Linux 内核正在重构内存管理子系统:用 memory descriptors 替换沿用三十年的 struct page
Linux 内核的内存管理子系统正在进行一场持续多年的基础重构:用 memory descriptors 替换沿用三十年的 struct page。这不是普通的代码整理,而是内存规模从 MB 膨胀到 TB、拓扑从单核扩展到多 NUMA 域的过程中,历史欠账的集中清算。Matthew Wilcox 从 2022 年引入 struct folio 开始计时,四年后工程仍在推进,且终点并不清晰。
每个物理页背后藏着 64 字节的历史包袱
struct page 的诞生逻辑很清晰:内核需要用一个统一的数据结构追踪每一个物理内存页的状态——谁在用它、用于什么目的、引用计数是多少。在内存以 MB 计量的年代,这是合理的。整个系统的页描述符加在一起也不过几 MB,且大多数页只用于一两种固定场景。
但三十年后,同一个 struct page 需要同时描述完全不同的事物:匿名内存页、文件系统缓存页、复合页(compound page)、大页(hugetlbfs)、设备内存页(DAX),以及内核自身的各种内部用途。为了让同一个 64 字节结构服务于所有这些场景,内核工程师大量使用了 C 语言的 anonymous union——同一块内存区域,在不同上下文下解释成不同字段。
这个设计保持了内存开销固定,避免运行时的类型分发,代价是类型安全几乎不存在——编译器无法检查某个文件系统页是否被错误地当作匿名页来读取字段。在大型代码库里,这类错误实际上发生过,且难以排查。
规模也带来了直接的物理开销。在一台配备 512GB 内存的服务器上,有约 1.34 亿个 4KB 页面,对应的 struct page 元数据占用约 8.5GB RAM——光是用于追踪内存的结构体,就消耗了总内存的 1.6%。对于字节计费的云环境,这不是零成本。
struct folio 是战略性妥协,不是最终答案
Matthew Wilcox 在 2022 年随 Linux 5.16 引入 struct folio,通常被描述为"replacing struct page for compound pages",但这个说法掩盖了它更深的战略意图。
struct folio 的核心思路不是替换,而是分离关注点:它代表的是"一组连续的物理页,作为单一内存对象被管理",与 struct page 的"任意单个物理页"拉开了语义距离。实际上,Btrfs 正在为 Linux 7.2 准备 huge folios 支持,这意味着文件系统层已经开始围绕 folio 语义重新组织 I/O 路径。
但 folio 不是终点。它更像一个楔子:先在内核的高层(文件系统、页面缓存、内存分配器的上层接口)建立类型安全的边界,再逐步向下推进。真正的目标——LWN 本周文章所说的 memory descriptors——是更彻底的结构分离:不同类型的内存对象用不同的专用描述符表示,彻底废弃 union 字段的混用。
这是一个"先建立新世界,再废除旧世界"的迁移策略,比一次性的大重构更安全,但也更漫长。
最难的不是新结构,是三十年的调用链
内核工程师通常不怕设计新数据结构。真正让 struct page 迁移耗时"多年"的,是调用链的迁移成本。
Linux 内核有数百万行代码,其中直接操作 struct page * 的函数调用遍布内存管理、文件系统、网络栈、设备驱动的每个角落。每一处调用点都需要判断:这个用法应该迁移到 folio,还是等待 memory descriptor,还是暂时保留?不同子系统的迁移速度不同,在过渡期内两套 API 需要并存,这本身就是维护负担。
更复杂的是,struct page 在某些地方扮演的不仅是数据结构,还是隐式的内存布局约定。一些驱动和硬件平台直接依赖 struct page 在内存中的位置关系来做地址计算(pfn_to_page / page_to_pfn 这类宏背后)。重构这些约定,需要跨越内核与硬件抽象层的边界,不是单纯的代码修改。
Wilcox 和参与者们选择了增量式迁移:逐个子系统转换,保持内核在任何中间状态都可以正常构建和运行。这个选择合理,但也意味着代码库里会长期留下"双轨并行"的痕迹——在某些地方,struct page 和 folio 会同时存在于相邻的函数调用里。
内核工程的时间单位是"发布周期的倍数"
struct page 迁移值得关注的,不仅是技术本身,而是它所代表的一种内核工程现实:在拥有全球最活跃维护者社区的项目里,一个"明显需要修复"的问题,从开始动手到完成,需要以多年计。
内核的稳定性承诺意味着每一步迁移都必须向后兼容,不能在中途破坏任何现有驱动或工作负载。这个约束是真实的保护,也是真实的代价。
发行版、云平台和嵌入式项目在依赖上游内核特性时,选型视野不能只看当前可用性——memory descriptors 的迁移路径现在就可以作为参照系写进技术选型文档。