背景
THP 全局策略(transparent_hugepage=never/madvise/always)在 __thp_vma_allowable_orders() 里门控所有非匿名 VMA 的 huge fault 分发:never 直接拒绝,madvise 只放行带 VM_HUGEPAGE 的 VMA。DAX VMA 是例外,它有提前返回:khugepaged 不去 collapse DAX,但 page fault 照常放行。MADV_COLLAPSE 的强制 collapse 同样不受这组策略门控。
special huge VMA(由 vma_is_special_huge() 标识,典型是 VM_PFNMAP 的设备 BAR 映射)与 DAX 共享同一本质:直接把物理地址映射进页表。以 VFIO PCI 直通的大 BAR 设备为例,VFIO 驱动注册了 .huge_fault 处理函数(分发到 vmf_insert_pfn_pmd/pud),QEMU 的 vfio_region_mmap() 也对齐了 BAR 映射,大页映射的两个前提都具备,但策略检查在到达「信任 huge_fault 处理函数」的代码之前就把请求拦下了。
非匿名 VMA 的 THP 策略门控
┌───────────────────────────────┐
│ 大页缺页分发先过 THP 策略检查 │
└───────────────┬───────────────┘
┌──────────────────┴──────────────────┐
▼ ▼
┌──────────────────────────┐ ┌───────────────────────────────────┐
│ DAX VMA 提前返回直接放行 │ │ special huge VMA 直接映射物理地址 │
│ │ │ 却被策略拦下退化为 4 KiB 映射 │
└──────────────────────────┘ └───────────────────────────────────┘
问题
- special huge VMA 与 DAX 同样直接映射物理地址,却被 THP 策略门控
- never 或 madvise 策略把缺页拦在驱动 .huge_fault 之前
- 单 GPU 的 256 GB BAR 按 4 KiB 映射需 6700 万次缺页
- 8 GPU 主机启动 VM 时 99.98% CPU 耗在 VFIO BAR 映射路径
- 触发条件并不少见:内核命令行 transparent_hugepage=never
- tuned cpu-partitioning 继承 network-latency 设为 never
- RHEL 默认 madvise,而 VFIO VMA 没有 VM_HUGEPAGE
方案
新增 vma_file_bypass_thp_tuneables() 集中判定豁免条件,把注册 huge_fault 的 VM_PFNMAP 映射移出 THP tuneables 的管辖。
THP tuneables 约束的是大页带来的内存分配、compaction、split 与 reclaim 代价,而 PFN 映射一样都不涉及:vm_normal_folio() 对 PFN 映射返回 NULL,这些 folio 的引用计数由驱动而非 core mm 维护,不参与 reclaim 与 compaction,本来就不是 THP 机制的对象。因此豁免只覆盖 VM_PFNMAP 且 huge_fault 非空的映射:VM_MIXEDMAP 映射可能包含已分配内存,不单独豁免;没注册 huge_fault 的 VMA 照旧受策略门控。
两个豁免条件都在 vma_file_bypass_thp_tuneables() 里:
static bool vma_file_bypass_thp_tuneables(const struct vm_area_struct *vma,
enum tva_type type)
{
const bool has_huge_fault = vma->vm_ops->huge_fault;
if (type == TVA_FORCED_COLLAPSE)
return true;
if (vma_test(vma, VMA_PFNMAP_BIT) && has_huge_fault)
return true;
return false;
}
豁免判定先于策略门控
┌─────────────────────────┐
│ 非匿名 VMA 先过豁免判定 │
└────────────┬────────────┘
┌───────────────────────┴───────────────────────┐
▼ ▼
┌──────────────────────────────────────────┐ ┌────────────────────────────────────────┐
│ 命中豁免直接放行,MADV_COLLAPSE │ │ 未命中看全局策略,always 放行,madvise │
│ 本就忽略策略,注册 huge_fault 的 │ │ 需 VM_HUGEPAGE,never 拒绝 │
│ VM_PFNMAP 映射不经 reclaim 与 compaction │ │ │
└──────────────────────────────────────────┘ └────────────────────────────────────────┘
通过判定的 VMA 按请求用途分发:
- 缺页路径直接信任驱动注册的 huge_fault 回调
- smaps 与缺页共用同一豁免判定,THPeligible 上报与缺页实际行为始终一致
- collapse 路径仍只认普通文件(file_thp_enabled()),khugepaged 对这类 VMA 本来就没有可 collapse 的内容
通过判定后按用途分发
┌──────────────────────┐
│ 通过判定后按用途分发 │
└──────────┬───────────┘
┌─────────────────────────────────┴─────────────────────────────────┐
▼ ▼ ▼
┌────────────────────┐ ┌───────────────────────────────┐ ┌─────────────────────┐
│ 缺页路径信任驱动注 │ │ smaps 走同一判定,THPeligible │ │ collapse 路径只认普 │
│ 册的 huge_fault 回 │ │ 如实上报 │ │ 通文件 │
│ 调 │ │ │ │ │
└────────────────────┘ └───────────────────────────────┘ └─────────────────────┘
drm_gem_shmem_mmap() 与 panthor_gem_mmap() 建立的是 shmem folio 的 huge PFN 映射,同样命中豁免。这些 folio 在 drm_gem_get_pages() 里已标记 unevictable,即使不然也过不了 __remove_mapping() 的引用检查,纳入豁免没有副作用。
整个非匿名 VMA 的 THP 检查随之收进 vma_can_map_huge_file(),豁免判定、策略放行与按用途分发各自独立成 helper。
收益
| 指标 | Before | After | 改善 |
|---|---|---|---|
| 单 GPU 256 GB BAR 的映射缺页次数 | 6700 万次(4 KiB 粒度) | 几千次(PMD/PUD 粒度) | 降约 4 个数量级 |
| 单个 128 GiB GPU BAR 的映射耗时 | 20.7 秒 | 0.4 秒 | 降 98.1% |
| 4 块 H100 GPU 的虚拟机启动耗时 | 97 秒 | 17 秒 | 降 82.5% |
后两行来自 Saravanan 的实测:4 块 H100(各 128 GiB BAR1)的 HGX 主机,cloud-hypervisor 虚拟机,THP 策略为 madvise。
其他预期收益:
- THP 策略回归本位,只约束真正涉及内存分配的 VMA
- smaps 的 THPeligible 如实反映 huge PFN 映射的大页资格