Linux 7.2-rc1 于 6 月 28 日发布,合并窗口关闭,代码树突破 4300 万行。Linus Torvalds 在发布公告中用了"things look reasonably normal"来定性这次发布——从节奏看确实如此。同一周,字节跳动工程师郑琦(Qi Zheng)向 LKML 提交了 Reserved THP 的 RFC 补丁,试图填补 HugeTLB 和 THP 各自留下的架构缺口。两件事并列,说明的是内核演进的一个固定模式:代码在增长,因为真实场景在逼着已有设计暴露边界。
4300 万行的构成:旧债还清,新场景覆盖
从 7.1 的 4292 万行到 7.2 的 4389 万行,单个合并窗口净增约 97 万行——同期大量旧代码也在被清理:PROFIBUS 驱动退出(1998 年从 SCO Unix 移植,多年无人使用)。增长的来源是新场景的代码化覆盖,而不是旧代码的堆叠。
增长最大的单一来源是 AMD GPU 驱动。AMDGPU/AMDKFD 目录现在占 635 万行,比 7.1 多了约 19 万行。Linus 在公告中提到,本次补丁集"三分之一是 AMD GPU 寄存器定义的头文件"——这是硬件复杂度向软件层的直接投影,无法压缩。另一端是历史债务的偿还记录:strncpy API 最终被删除,历时 6 年、360 多个补丁,散落在内核各处的依赖逐一替换完毕。4300 万行是两类工作叠加的结果:一类是硬件多样性扩张,一类是接口安全性在补欠账。
热升级场景把 HugeTLB 与 THP 的局限同时触发
Linux 内核的大页机制有两条并行路线,各自基于不同的假设。HugeTLB 假设使用方知道自己需要多少内存:启动时通过 /proc/sys/vm/nr_hugepages 预留连续物理内存,之后从固定池子分配,延迟低、TLB miss 少,数据库和 JVM 类应用依赖此保证。代价是静态预留不能被普通进程借用,且不支持 swap——物理内存紧张时,预留的大页无法换出。THP 的假设相反:内核透明地把连续 4KB 页合并成 2MB 大页,应用无感知,也不需要预留。代价是分配时依赖物理内存连续性,碎片化严重的服务器上成功率下降;khugepaged 后台扫描的开销导致延迟尖刺难以消除。
字节跳动的热升级场景把两条路线的代价同时触发。郑琦在 RFC 中描述:某个使用 16GB HugeTLB 的进程,热升级期间需要同时为新旧两个进程版本保留内存,预留量必须翻倍到 32GB。旧进程退出后多出的 16GB 立即变成浪费。更进一步,郑琦发现热升级期间旧进程有约 10GB 的 HugeTLB 实际是冷数据,理论上可以回收腾出空间——但 HugeTLB 不支持 swap,这条路堵死了。THP 可以 swap,但无法预留,分配保证不存在。两条路线的能力边界,在这个具体运维场景里同时碰壁。
Reserved THP:预留的刚性与分配的弹性并存
郑琦的设计思路是:保留 HugeTLB 的预留机制,将底层实现替换为 THP。具体做法是在启动时预留一块连续内存区域,但不把它绑定为 HugeTLB 的独占静态池——应用通过 madvise() 请求使用预留大页,普通内存分配无法消耗这块预留;因为底层是 THP 实现,swap 支持自然接入,冷数据可以换出,热升级期间的额外内存需求可以从释放的冷页中满足。按字节跳动内部估算,最极端情况下只需预留 22GB,而非 32GB。
这个方案目前是 RFC 阶段,尚未合并进主线。内核社区对 HugeTLB 和 THP 统一的讨论已有多年,但一直停留在讨论层面。Reserved THP 提交的是一个具体补丁,附带的是真实场景的失败数据——两者的差别在于后者有答案需要被验证,前者只有问题需要被讨论。
字节跳动选择上游,而非维护内部 fork
把生产问题转化为上游补丁,而不是保持为内部私有修改,在国内互联网公司的内核贡献中有一条可识别的轨迹。华为工程师主导了 EROFS 只读文件系统从 staging 进入主线,解决的是嵌入式和移动端存储场景的性能问题。这些贡献的共同特征是把超大规模运营中暴露的内核行为边界以可复现的形式提交给社区——区别于为特定硬件写驱动的适配型贡献。
Reserved THP 属于同一类型。郑琦此前已有其他内存管理相关的上游提交记录,这次 RFC 延续了同样的路径:先在生产环境验证设计,再带着数据进入社区审查流程。选择上游而非 fork 的逻辑不只是技术社区的价值观——维护内部分支的长期成本,在内核版本持续演进的背景下会持续累积,而上游合并一旦完成,维护成本由社区分担。4300 万行内核代码里,有一部分就是这类"把内部修复带回上游"的循环在积累。