跨内核实时更新:如何实现 hugetlbfs 巨页的无缝保留与恢复?

随着云原生和虚拟化基础设施的演进,在不中断宿主机上运行的租户虚拟机(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()
                                              ▼
                                    [ 新内核引导启动 ]

整个实时更新机制由两个关键组件协作完成:

  1. kexec Handover(内核子系统) :纯内核态接口(无用户空间 API)。负责在 kexec 重启期间注册并标记需要保留的物理内存区域,保证其页面描述符和元数据不被释放。

  2. 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 完成后:

  1. 内核实例化一个新的基于 hugetlbfs 的 memfd 文件描述符。

  2. 将保留下来的物理巨页按照原偏移量重新放回新内核的页面缓存(Page Cache)中。

  3. 对 Control-group(cgroup)重新进行内存计费。

  4. 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)的能力。

相关推荐
qeen871 小时前
【Linux】make/Makefile 自动化工具的介绍
linux·运维·服务器·自动化
funnycoffee1231 小时前
Cisco ip access-list序号重排方法
linux·网络·tcp/ip
艾莉丝努力练剑2 小时前
【Linux:动静态库】Linux 动静态库与可执行文件
linux·运维·服务器·学习·面试·文件系统·动静态库
殷忆枫2 小时前
Linux 4G模块驱动适配实战:从手动绑定到自动识别
linux·运维·服务器
独隅2 小时前
CLion 在 Linux 上的完整安装与配置使用指南
linux·运维·服务器·c语言·c++·ide
☆凡尘清心☆2 小时前
CentOS Stream 9 编译安装 Redis 7.2.7 完整版详细步骤
linux·redis·centos
wuminyu2 小时前
JUC组件逐层剥离与深度剖析
java·linux·c语言·jvm·c++·算法
ocean'2 小时前
防火墙策略路由
linux·服务器·数据库
三言老师3 小时前
awk条件筛选日志内容实操
linux·运维·服务器·centos