背景

HVO(HugeTLB Vmemmap Optimization)是 hugetlb 大页的省内存优化:一个 compound page 有大量 tail struct page,它们的 vmemmap 内容可以从 head 推导,于是只保留前几个 struct page 的真实 vmemmap,其余 tail 的 vmemmap 全部映射到同一个共享页并写保护,省下的 vmemmap 页被释放。

HVO 把尾页 vmemmap 去重到一页
┌───────────────────────────────────┐
│ compound page 的 tail struct page │
└─────────────────┬─────────────────┘
                  ▼
┌───────────────────────────────────┐
│ vmemmap 映射到同一共享页并写保护  │
└─────────────────┬─────────────────┘
                  ▼
┌───────────────────────────────────┐
│      省下的 vmemmap 页被释放      │
└───────────────────────────────────┘

bootmem 阶段分配的 HugeTLB 大页走 HVO 时,带着一条自己的 early-boot 路径:在通用 sparse-vmemmap 填充代码运行之前,HugeTLB 自己把优化后的 vmemmap 映射预填充好。sparsemem 专门为这个特例背着一个 SPARSEMEM_VMEMMAP_PREINIT 配置与相应的 section 标记。这条专用路径让 HVO 难以共享给 sparse-vmemmap 优化的其他用户,也把不少 HugeTLB 专用的启动状态留在了通用内存初始化流程里。初始化顺序也别扭:共享尾页上的 struct page 要先在启动时初始化,memmap_init_range() 又会把它们逐页覆盖,HugeTLB 事后还得把元数据恢复回来。

专用 pre-HVO 路径的三处别扭
┌────────────────────────────────────┐
│     HugeTLB 自己预填充优化映射     │
└─────────────────┬──────────────────┘
                  ▼
┌────────────────────────────────────┐
│   sparsemem 为它背 PREINIT 特例    │
└─────────────────┬──────────────────┘
                  ▼
┌────────────────────────────────────┐
│ 共享尾页先初始化又被覆盖,事后恢复 │
└────────────────────────────────────┘

问题

  • HugeTLB 专用预填充路径与通用 sparse-vmemmap 填充逻辑重复
  • sparsemem 专为该特例背着 SPARSEMEM_VMEMMAP_PREINIT
  • 共享尾页要先初始化又被 memmap_init_range 覆盖,需事后恢复
  • HugeTLB 启动状态散落在通用初始化流程,难以共享给其他优化用户

方案

核心思路是把「这段物理内存是多大的 compound page」记进 memory section 元数据,让通用 sparse-vmemmap 代码自己识别并处理共享尾页,HugeTLB 只负责登记。

mem_section 在 HVO 配置下新增 compound_page_order 字段,配套 helper 把 mem_section 的访问留在 sparse helper 内部。vmemmap_optimizable_pfn() 据此判定共享尾页:一个 PFN 在其 compound page 内的偏移超过保留的 struct page 数量,它的 vmemmap 就落在共享页上。

通用侧跟着做两件事。memmap_init_range() 遇到共享尾页直接按 order 对齐跳到 compound page 末尾,不再逐页初始化,覆盖与事后恢复一并消失;pageblock migratetype 的初始化从逐页循环里抽出来,循环结束后对整个范围统一补做。通用 sparse-vmemmap 填充路径则按 section 元数据分配或复用共享尾页 vmemmap,共享尾页 struct page 在分配时初始化。

section 元数据驱动通用路径
┌─────────────────────────────────────┐
│  bootmem 大页把 order 记进 section  │
└──────────────────┬──────────────────┘
                   ▼
┌─────────────────────────────────────┐
│ 通用路径按 section 元数据铺共享尾页 │
└──────────────────┬──────────────────┘
                   ▼
┌─────────────────────────────────────┐
│   memmap_init_range 跳过共享尾页    │
└─────────────────────────────────────┘

HugeTLB 一侧大幅瘦身。bootmem 分配大页时只做一次登记:把该大页范围的 compound page order 写进对应 section。原来的 pre-HVO 早期填充,包括 section 与 vmemmap PMD 两级对齐检查、专用的填充函数、按 zone 预初始化共享尾页 struct page,整体删除;SPARSEMEM_VMEMMAP_PREINIT 随之移除。状态也归并:HUGE_BOOTMEM_HVO 标志删除,folio 是否做过 vmemmap 优化直接看 section 元数据里的 order;其余只在启动期使用的 bootmem 标志与类型一并收敛回 hugetlb 私有范围。

专用路径与 PREINIT 整体退役
┌───────────────────────────────────┐
│    pre-HVO 填充与对齐检查删除     │
└─────────────────┬─────────────────┘
                  ▼
┌───────────────────────────────────┐
│  SPARSEMEM_VMEMMAP_PREINIT 移除   │
└─────────────────┬─────────────────┘
                  ▼
┌───────────────────────────────────┐
│ bootmem 标志位并进 section 元数据 │
└───────────────────────────────────┘

这是 HVO 泛化工作的第二个小步:section 元数据成为 vmemmap 优化的统一载体之后,device DAX 的切换与更大范围的 HVO 整合留给后续系列。

收益

作者未提供性能数据,从代码逻辑推断的预期收益:

  • 共享尾页 struct page 在 early boot 不再被逐页初始化覆盖,省掉重复初始化与事后恢复
  • HugeTLB 与通用 sparse-vmemmap 填充路径合一,PREINIT 特例及其启动状态整体移除
  • section 元数据成为 vmemmap 优化的统一载体,为 device DAX 接入与 HVO 整合铺路