随着云原生和虚拟化基础设施的演进,在不中断宿主机上运行的租户虚拟机(VM)的前提下完成宿主机内核升级(Host Kernel Live Update),已从"锦上添花"变成了生产环境的刚需。
尽管 Linux 内核在 6.19 开发周期中正式合并了零停机升级的基础设施------kexec 切换(kexec handover)与实时更新协调器(live update orchestrator) ,但仍遗留了一个影响性能的核心痛点:基于 hugetlbfs 管理的大页(Huge Pages)内存尚无法在实时更新过程中存活。
在 2026 年 Linux 存储、文件系统、内存管理与 BPF 峰会(LSFMM&BPF)上,Pratyush Yadav 提出了补齐这一短板的架构设计,力求让 GB 级别的巨页能够在内核重启替换时平滑过渡。
一、 架构背景:kexec 切换与实时更新协调器
要理解 hugetlbfs 保留的难点,首先需要看懂 Linux 实时更新的底层分层设计:
┌─────────────────────────────────────────────────────────┐
│ 运行中的系统 (HOST) │
│ ┌───────────────┐ ┌────────────────┐ │
│ │ QEMU / VM │ │ Live Update │ │
│ │ (进程物理内存) │ │ Orchestrator │ │
│ └───────┬───────┘ └───────┬────────┘ │
│ │ 用户态 │ 用户态 │
├──────────┼──────────────────────────────────┼───────────┤
│ │ 内存页面 │ 控制指令 │
│ ┌───────▼───────┐ ┌───────▼────────┐ │
│ │ hugetlbfs ├─[冻结与固定]──────►│ Kexec Handover │ │
│ └───────────────┘ └───────┬────────┘ │
│ │ 内核态 │
└─────────────────────────────────────────────┼───────────┘
│ kexec_load()
▼
[ 新内核引导启动 ]
整个实时更新机制由两个关键组件协作完成:
-
kexec Handover(内核子系统) :纯内核态接口(无用户空间 API)。负责在
kexec重启期间注册并标记需要保留的物理内存区域,保证其页面描述符和元数据不被释放。 -
Live Update Orchestrator(用户态协调器) :面向用户态的控制工具。负责协调资源注册、触发状态保存,并最终向内核发送
kexec_load()系统调用启动新内核。
6.19 版本的局限:小页 vs 高性能
在 6.19 合并的早期版本中,kexec handover 仅支持通过 memfd_create() 创建的共享内存文件。虽然这能勉强带飞虚拟机,但由于普通内存页(如 4KB)存在深刻的页表层级和频繁的 TLB 缺失,将虚拟机放在普通 memfd 中运行性能极差。
为了实现最佳性能,主流 Hypervisor(如 QEMU/KVM)通常会向宿主机的 hugetlbfs 申请 2MB 或 1GB 的巨页来挂载 VM 内存。而在当前,任何挂载在 hugetlbfs 上的虚拟机在宿主机内核更新前都必须关机。
二、 解决方案:巨页跨内核传递的设计思路
Yadav 的设计秉承了一个极其重要的内核原则:尽可能减少需要保留的状态(Minimal State Preservation)。因为任何被保留下来的状态,本质上都会变成内核 ABI(应用程序二进制接口)的一部分,保持极简能大幅降低后续内核迭代的维护成本。
整个巨页保留流程分为三个关键阶段:
阶段 1:冻结与固定(Freezing & Pinning)
在旧内核准备下线前,必须锁定目标巨页,防止数据在更新期间被修改、迁移(Migration)或压缩(Compaction)。社区讨论了两种方案:
-
Inode 级别标志位(Inode-level Flagging) :在对应的 hugetlbfs inode 上增加标记(类似
shmem的做法)。但这种方案被部分开发者批评为绕过 VFS 语义的"凑合妥协(hack)"。 -
地址空间标志位(Address-Space Flagging,首选):在 filemap 层级引入新的 address-space flag。虚拟文件系统(VFS)层能够显式感知到冻结状态,并在任何写入修改发生前将其拦截。
锁定标志设置完成后,相关物理内存页会被立即 Pin(固定) 住。
阶段 2:元数据序列化
旧内核将每个被冻结巨页的关键几何信息(包括物理基地址、页面大小、偏移量)打包序列化。这个极小的元数据块与被冻结的物理页面一起被标记为"kexec 保留"。
阶段 3:在新内核中重建恢复
当新内核 Boot 完成后:
-
内核实例化一个新的基于 hugetlbfs 的
memfd文件描述符。 -
将保留下来的物理巨页按照原偏移量重新放回新内核的页面缓存(Page Cache)中。
-
对 Control-group(cgroup)重新进行内存计费。
-
QEMU 进程恢复运行,整个过程中 VM 甚至完全没有感知到宿主机内核已经换了一拨人。
三、 工程挑战与未解难题
将 hugetlbfs 的状态带过 kexec 重启,必然会与 Linux 内存管理的其他子系统产生冲突,其中最为突出的有两个问题:
旧内核启动 新内核启动
┌───────────────────────────┐ ┌───────────────────────────┐
│ hugetlbfs 预分配 100 巨页 │ │ hugetlbfs 预分配 100 巨页 │
└─────────────┬─────────────┘ └─────────────┬─────────────┘
│ │
▼ ▼
┌───────────────────────────┐ ┌───────────────────────────┐
│ 实时更新保留了 20 个巨页 ├────────►│ 恢复旧内核传递来的 20 巨页 │
└───────────────────────────┘ └─────────────┬─────────────┘
│
▼
┌───────────────────────────┐
│ 风险:巨页过度分配! │
│ (总请求量暴涨至 120 页) │
└───────────────────────────┘
挑战 A:巨页的"过度分配(Over-Allocation)"
通常情况下,hugetlbfs 会在内核引导早期根据启动参数(如 hugepages=N)预先分配固定数量的巨页池。
如果新内核启动时照常预分配了 N 个新巨页,随后又从 kexec handover 恢复了旧内核传过来的 M 个保留巨页,系统巨页总量就会变成 N + M。这极易导致系统物理内存被挤爆或引导失败。
- 解决方案: 新内核在引导极早期检查 kexec handover 的元数据,统计出保留巨页的数量
M,然后自动将本次hugetlbfs的预分配目标相应缩减为N - M。
挑战 B:与 CMA(连续内存分配器)的兼容性
如果旧内核中的巨页是从 CMA(Contiguous Memory Allocator)区域动态借来的,在恢复时就需要将这些页面插回新内核的 CMA 管理区。但现有的 CMA 并不支持在 Boot 后动态扩展或注入既有的物理内存块。
- 当前妥协: 当前的 Patch 集采取了"一刀切"策略------如果启用了 Live Update,直接禁止 hugetlbfs 从 CMA 中获取内存。CMA 的状态保留被留作未来的研究课题。
四、 项目进展与未来展望
-
补丁集现状: 首个 RFC Patch 已经在 2025 年 12 月 提交至内核邮件列表。在回应首轮反馈的过程中,作者发现 kexec handover 本身存在若干底层的架构缺陷,目前正在进行重构。
-
下一步计划: 作者即将在回应完社区意见后发布更新后的 Patch 系列,重点完善 VFS 冻结语义。
一旦该特性正式合并,Linux 宿主机内核的实时更新将真正具备支撑高性能生产级虚拟化(Zero-Downtime Hypervisor)的能力。
