同一 hugetlbfs 文件 · 按 PUD 对齐共享映射
进程 A
VMA · VM_MAYSHARE
进程 B
VMA · VM_MAYSHARE
PUD 边界 · 1G(x86_64)
共享 PMD 页表 · 512 × 2M
huge_pmd_share() 把共享页表装进 PUD:两进程共用同一张,各剩下一层页表
拆除共享 · __huge_pmd_unshare() 清 PUD 项
PUD[i]
共享 PMD 页表 · 512 × 2M 随 PUD 项作废
一次 unshare 的失效范围 = 整个 PUD
MMU notifier 失效范围
1G
进程 C 仍共享这张页表
进程 C
VMA · 同一 hugetlbfs 文件
不覆盖 → 留下过期映射
旧判据 · adjust_range_if_pmd_sharing_possible()
✓ VMA 带 VM_MAYSHARE
✓ 失效范围落在对齐的 PUD 内
可能共享 → 扩到 PUD 边界
真有共享吗?
从不检查
只看「可能」:文件当前没有任何共享页表,也照样命中
2M 打洞 → notifier 范围被拉到 PUD 边界
2M 打洞
KVM 收到 1G 失效
KVM 收到 1G 失效 → 整窗二级映射被删
KVM 二级页表(EPT)· 同一个 PUD 窗口
2M 打洞
guest 缺页后回到同一窗口重填,下一轮打洞又放大
2M 打洞
1G 失效
guest 缺页 → 重填
每个 hugetlbfs inode 维护一个活跃 PMD 共享计数
hugetlbfs inode
atomic64 计数:读锁下可并发 +1,64 位不回绕到 0
活跃 PMD 共享
0
计数只在真正安装 / 真正拆下共享 PMD 页表时 ±1
huge_pmd_share() 安装成功 → +1
__huge_pmd_unshare() 真正拆下 → −1
时间 →
zap / unmap 路径
持有映射写锁
读计数 == 0 → 不扩大
huge_pmd_share() 走同一把锁的读侧
读锁 · 阻塞
写锁释放后才获得读锁
写锁内读到 0 = 此刻没有,也不会有新共享
新代码只问一件事:计数是否非零
if (hugetlbfs_pmd_sharing_active(file_inode(vma->vm_file)))
adjust_range_if_pmd_sharing_possible(vma, start, end);
计数 > 0 ?
计数 0 · 否
跳过扩大
不调用 adjust_range,保持原范围
2M 失效
计数 > 0 · 是
adjust_range_if_pmd_sharing_possible()
扩到 PUD 边界
1G 失效
Redis-in-VM · 2M hugetlb 冷页打洞 · P99 QPS 劣化率
Before
7.09%
After
1.45%
降 5.64 个百分点
没有活跃共享:2M 打洞只发 2M 失效
PUD 1G
2M
同一 PUD 的其他映射不再被误删,guest 不再反复缺页
空格
播放/暂停
←
→
逐动作
R
重播
⚠
1x