在虚拟化与云原生架构中,宿主机内核的 Live Update(热升级)是实现无感运维的关键技术。Linux 内核的 KHO (Kernel Highway Overpass) 机制允许系统在跨内核重启(Re-exec)时保留关键内存数据(Preserved Memory),从而实现虚拟机的零中断热升级。
然而,当宿主机将绝大部分物理内存配置为 1GiB 巨页(Gigantic Huge Pages) 供 VM 使用时,KHO 遭遇了严重的架构瓶颈:巨页分配挤爆了 KHO 的早期引导空间(Scratch Area),导致热升级流程频繁崩溃。
近期 upstream 出现的一套重构补丁集,通过引入 Extended Scratch(扩展 Scratch 区域) 与 Radix 树摘要算法,彻底解耦了巨页与 Scratch 空间的死锁逻辑。
一、 根因剖析:为什么 1GiB 巨页会“算爆” KHO Scratch?
KHO 在 Early Boot 阶段依赖一个独立的预留内存区——Scratch 区域。它的核心架构约束是:仅供新内核解压与初始化使用,绝对不能包含任何跨重启保留的数据(Preserved Memory)。
在旧有的实现中,存在一个致命的逻辑闭环:
【旧机制的崩溃死锁】
Early Boot Memblock 分配
│
▼
┌───────────────────┐
│ KHO Scratch 区域 │ ◄─── 巨页被迫强行挤入
└─────────┬─────────┘
│
┌─────────────────────────┴─────────────────────────┐
▼ ▼
[ 衍生问题 1: 容量算爆 ] [ 衍生问题 2: 属性违例 ]
巨页被打上 RSRV_KERN 标签, 巨页属于 Preserved Memory,
引发 Scratch Scale 乘法暴胀 (超出物理内存上限) 打破了 Scratch 不含保留数据的底线
-
分配路径交织:在 Early Boot 阶段,所有由
memblock申请的内存(包括 VM 巨页)全都被强制落入初始 Scratch 区域内。 -
容量计算暴胀(Accounting Breakdown):
memblock默认将巨页标记为内核预留内存(RSRV_KERN)。KHO 会根据RSRV_KERN的大小乘以缩放系数(scratch_scale,如 1.5)来推算下一个内核所需的 Scratch 空间:Scratch Size = Total(RSRV_KERN) * scratch_scale
若宿主机配置了 800 GB 巨页,SRV_KERN 就会被误统计为 800+ GB,乘上系数后 KHO 试图预留 1.2 TB 的 Scratch 空间,导致系统直接因 OOM 崩溃。
-
架构红线违例:VM 巨页属于需要在 KHO 重启间保留的 Preserved Memory。将其存放在 Scratch 区域中,会导致新内核在覆盖 Scratch 时面临破坏 VM 内存的风险。
二、 核心破局:Patchset 的“分流与扩展”架构
这套 Patchset 的本质思路非常清晰:解除巨页对 Scratch 的绑架,让巨页在 Scratch 外部的安全空闲块中进行分配。
【新机制的分流架构】
Early Boot Memblock 分配
│
┌────────────────────────┴────────────────────────┐
▼ ▼
(常规内核引导内存) (VM 巨页分配)
│ │
▼ ▼
┌──────────────────┐ ┌─────────────────────┐
│ Initial Scratch │ (仅存内核代码/临时结构) │ Extended Scratch │ (由 Digest Radix 树
└──────────────────┘ └─────────────────────┘ 动态发现的 1G 空闲块)
三、 补丁集的关键技术实现
1. 专属分配通道:memblock_alloc_hugetlb()
Patchset 为巨页打造了专属接口 memblock_alloc_hugetlb(),在代码层做出硬性隔离:
-
主动绕开 Scratch:在
memblock_find_in_range_node()寻找空闲内存段时,若发现候选区间落在 KHO 初始 Scratch 内,直接跳过(Skip)。 -
独立类型标记:将巨页标为
MEMBLOCK_RSRV_HUGETLB,不再混入通用内核预留内存(RSRV_KERN)。 -
保护镜像内存(Mirrored Memory):明确禁止巨页占用昂贵的硬件镜像内存,将珍贵资源留给核心内核结构。
2. 算法创新:Radix 树摘要(Digest Radix Tree)与动态扩展
既然巨页不能在初始 Scratch 中分配,Early Boot 阶段去哪里找足够的物理内存?Patchset 引入了 Extended Scratch 机制,其核心是一套巧妙的 Radix 树转换算法:
[ 原始 Preserved Radix Tree ] ──(单次遍历)──► [ Digest Radix Tree (1GiB 粒度) ]
(包含分散的 4KB, 2MB 保留页) (按 1GiB 物理块汇总的位图树)
│
▼
[ 遍历 Digest 树找到空隙 ]
│
▼
[ 标记为 Extended Scratch ]
-
单次遍历与摘要(Digest):在启动极早期,遍历记录跨重启保留内存的原始
kho_radix_tree,并将其“摘要(Digest)”压缩建出另一棵临时的 Digest Radix Tree。 -
1GiB 粒度对齐与找空隙:Digest 树以 1 GiB(
KHO_SCRATCH_EXT_BLKSIZE)为粒度规整物理内存。树中相邻两个被占用的 Key 之间的空白区间,即代表这几吉字节的物理内存绝对没有任何保留页。 -
低开销与用完即销毁:对于小于 32 TiB 的服务器,整个 Digest 树仅需 6 个内存页(
KHO_TREE_MAX_DEPTH == 6)。在向memblock注册完成 Extended Scratch 后,系统会立即调用新引入的kho_radix_destroy_tree()彻底销毁这棵临时树,零运行时内存残留。
3. 容量计算公式的精确闭环
在将巨页打上 RSRV_HUGETLB 标记后,Patchset 引入 memblock_reserved_hugetlb_size(),并将 KHO Scratch 的计算公式修正为:
Scratch Size = (Total(RSRV_KERN) - Total(RSRV\_HUGETLB)) * scratch_scale
通过显式扣除巨页占用的内存,真实反映内核自身的内存消耗(如 20 GB),彻底消除了计算暴胀问题。
4. 基础设施与 Pageblock 迁移类型重构
为了支持 Extended Scratch 与常规内存区域合并(Merge)后的属性精确划定:
-
早期分配器支持:新增
kho_radix_alloc_node(),在slab_is_available()未就绪的极早期,自动降级使用memblock申请 Radix 树节点。 -
Pageblock 粒度评估:废弃了以往对连续 range 整体查询迁移类型的逻辑,改在
memmap_init()阶段逐个 Pageblock(如 2MB/4MB 粒度)调用kho_scratch_overlap(),解决了内存块合并引起的 Migration Type 属性错乱。
四、 实测性能与架构意义
在 64GB 内存、预分配 50 个 1GiB 巨页的 QEMU KVM 压测环境中,该补丁集的表现极为出色:
-
轻度保留场景(含 2 个 1M memfd):仅需 47 微秒($\mu s$) 即可完成 57GB Extended Scratch 空间的扫描与扩展。
-
重度碎片化场景(超 50 万项 4KB 页保留):在极度碎片化的情况下,扫描并拓展 52GB 空间仅耗时 22.5 毫秒($ms$)。
总结
这套补丁集通过 memblock_alloc_hugetlb() 隔离、Digest Radix 树动态发现 和 计算公式扣减 三位一体的改动,彻底打破了“巨页分配”与“Scratch 隔离”之间的矛盾,为云厂商在大内存宿主机上部署 KHO Live Update 扫清了关键的架构障碍。


3894

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



