在 Linux 内核演进中,KHO (Kexec HandOver) 是近来备受瞩目的技术方案。它的核心目标是:在利用 kexec 实现内核热重载(Live Update / Quick Reboot)时,将旧内核中的关键数据结构、内存状态甚至 DMA 分配跨版本“无缝移交”给新内核,从而大幅缩短高可用场景下的业务中断时间。
然而,从理论设计到落地内核主线,KHO 遇到了诸多极为棘手的工程痛点。本文整理了近期 KHO 开发者在 Linux 内核邮件列表(LKML)上的多轮讨论与重构补丁,带你一探这项黑科技背后的阵痛与演进。
痛点一:语义模糊与内存分类混乱 (Memblock & NOPRSRV)
在早期实现中,系统启动阶段的内存管理器 memblock 在处理 KHO 暂存内存(Scratch Memory)时存在严重混淆。
旧逻辑:[从 KHO 拿到的暂存内存] == 标记为 MEMBLOCK_KHO_SCRATCH == [启动过程中新发现的暂存内存]
(初始化逻辑混淆、语义模糊)
新逻辑:[从 KHO 拿到的暂存内存] ----------> 区分管理
[启动过程中新发现的暂存内存] ----------> 标记为 NOPRSRV (Non-Preserved)
乱象剖析
-
混淆来源:
memblock会将两类完全不同的内存统一打上MEMBLOCK_KHO_SCRATCH标签:-
上个内核通过 KHO 传递过来的“既有暂存内存”;
-
新内核在本次 Boot 过程中自己识别并划出的“新暂存内存”。
-
-
严重后果:这两类内存在后期的初始化逻辑中必须被差异化对待。标签混用直接导致代码可读性极差(难以被开发者 grok),且极易埋下初始化逻辑错乱的隐患。
解决方案与重构
开发者决定将相关标志重命名为 NOPRSRV (Non-Preserved)。通过明确表示“非保留/可释放”的物理特性,消除了歧义,使 memblock 能精确掌握各类内存的属性并施加正确的初始化行为。
痛点二:命令行参数重命名与社区“无破坏原则”的博弈
为了进一步清除技术债,开发者尝试将 kho_scratch= 这一命令行参数重命名。但这触碰了 Linux 内核最敏感的红线之一——不可破坏既有接口(Don't break userspace / ABI)。
争议与反驳
-
维护者的质疑:随意重命名启动参数可能导致现有生产环境的引导脚本失效。
-
开发者的破局逻辑:
-
引用历史先例 (Prior Art):列举了内核历史上的多个提交(如
c5bfece2d612将nohz_extended改名为nohz_full;9406415f46f6将sched_debug改名为sched_verbose),证明社区一直允许将“晦涩模糊”的参数重命名为更准确的名称。 -
评估真实影响:
kho=on本身已经能极其出色地自动计算并分配尺寸,99% 的用户根本不需要手动敲入kho_scratch=。手动设置仅适用于极少数高级调试场景。 -
澄清技术本质:KHO 本质上是 内核到内核(Kernel-to-Kernel) 的内部数据传递机制,没有任何用户态 API (uAPI)。修改它绝不会破坏用户空间程序的运行。
-
折中方案:虽然合理性充足,但开发者依然承诺:若未来确实有真实用户反馈影响,将第一时间补齐对旧参数的向下兼容支撑。
痛点三:kdump 内存惨遭“挤爆”引发内核 Panic
在 KHO 与崩溃转储内核(kdump / crashkernel)叠加使用的场景中,曾触发过一个致命的 OOM 崩溃 Bug。
Bug 发生的连锁反应
kdump 内核启动 (运行在极小的 crashkernel 预留空间,如 1GB 内)
│
▼
未接收到 handover FDT (因为此前提交已禁止向 crash kimage 添加元数据)
│
▼
触发默认降级逻辑:调用 kho_reserve_scratch() 强制预留 200% 内存
│
▼
内存耗尽:176MB 基础占用 + 650MB 强制暂存预留 = crashkernel 空间直接瘫痪!
│
▼
Panic 报错:bio: can't create integrity buf pool (崩溃转储彻底失败)
根本原因与修复
kdump 内核是一个“临终工具”,它的使命只有一条:把死掉的旧内核内存镜像转储到磁盘,然后重启系统。它既不需要继承任何 KHO 状态,更不需要将自身状态 Handover 给下一个内核。
修复措施:直接在 kdump 内核中彻底禁用 KHO 机制。无需预留任何暂存内存,成功释放了数百兆极其宝贵的 crashkernel 内存空间。
痛点四:伙伴分配器高阶页(High-order Pages)恢复引发内存泄漏
在推进“跨 KHO 保持 DMA 内存分配”时,开发者遇到了 KHO 恢复逻辑与伙伴分配器(Buddy Allocator)物理现实之间的底层冲突。
经典物理现实的错位
| 概念 | 实际物理/内核状态 (如 dma_alloc_coherent) | 旧版 KHO Restore 逻辑的行为 |
| 内存结构 | 高阶非复合页 (High-order Non-compound Page) | 误判为一串独立的 4KB 页 (Split Pages) |
| 引用计数 (Refcount) | Head Page = 1 Tail Pages = 0 | Head Page = 1 Tail Pages 强制被赋予 1 |
导致的严重后果
当新内核后续试图释放这块 DMA 内存时,__free_pages_prepare() 会调用检查函数。系统在发现原本应该为 0 的尾页引用计数竟然是 1 时,会立刻判定内存状态损坏:
-
❌ 内核被污染 (Kernel Tainted):触发 VM Debug 警告。
-
❌ 内存永久泄漏 (Memory Leak):由于不敢回收状态异常的页面,伙伴分配器直接放弃回收这块物理内存。
重构方案:重构显式 API
放弃了不可靠的“自动类型检测”,重构并推出了明确分工的持久化 / 恢复 API:
/* 旧 API:仅用于独立、分散的 4KB 页面集合 */
kho_preserve_pages(...) / kho_restore_pages(...)
/* 新 API:专为高阶页/DMA 内存设计,精准维持 Head=1, Tail=0 的物理状态 */
kho_preserve_page(...) / kho_restore_page(...)
底层同时引入了 kho_init_high_order_page() 辅助函数,将元数据校验、状态清理与 managed page 统计统一收拢,完美解决了跨内核重载后 DMA 高阶连续内存恢复导致内核污染的问题。
总结
KHO(Kexec HandOver)作为 Linux 内核实现无感升级的关键技术,其演进过程完美展示了 Linux 社区对底层代码质量、内存物理真实性以及系统可靠性的苛刻要求。从消除语义模糊、理顺参数变更,到修复 kdump 内存挤爆与高阶页 refcount 错乱,这一系列重构正在为 KHO 迈向大规模生产环境铺平道路。


402

被折叠的 条评论
为什么被折叠?



