背景

DAMON 的物理地址空间监控(paddr)靠 access bit 采样工作:每个采样间隔先清掉采样页的 accessed bit,下一个间隔再读回来,重新置位就说明这期间被访问过。虚拟化宿主机上这套监控的用武之地本来最大:guest 内存通常由 hugetlbfs 的 hugetlb 页支撑,作者的生产宿主机上约 95% 的 RAM 是 1GiB hugetlb 页。但对这样的内存,paddr 监控一直给不出有用的访问信号,原因叠了两层。

访问信号在两层上断掉
┌────────────────────────────────────────┐
│ paddr 监控的访问检查从 damon_get_folio │
│ 开始,拒收非 LRU folio                 │
└───────────────────┬────────────────────┘
                    ▼
┌────────────────────────────────────────┐
│ hugetlb folio 本就不上 LRU,全部报成   │
│ 从未访问,第一层断在不可见             │
└───────────────────┬────────────────────┘
                    ▼
┌────────────────────────────────────────┐
│ 清 access bit 不带 TLB flush,1GiB     │
│ 映射下 TLB 几乎不 refill,第二层断在   │
│ 信号失真                               │
└───────────────────┬────────────────────┘
                    ▼
┌────────────────────────────────────────┐
│ 观察值钉死在 TLB-refill 率,饱和 guest │
│ 与空闲 guest 无法区分                  │
└────────────────────────────────────────┘

第一层是不可见:每次访问检查都从 damon_get_folio() 开始,而它拒收不在 LRU 链表上的 folio,hugetlb folio 按设计就不上 LRU,于是所有 hugetlb 内存被静默报成从未访问。

第二层是信号失真:就算 folio 可见,DAMON 为了低开销,清 access bit 时故意不带 TLB flush,缓存翻译命中的访问不走页表、不会重新置位;普通负载下 TLB 压力本身会频繁重走热页,这个取舍问题不大,但 1GiB 映射下几百 GiB 内存只占几百个 TLB 项,翻译几乎永不驱逐,观察到的访问率被钉死在 TLB-refill 率上,饱和的 guest 与空闲的 guest 看起来一样。

问题

  • damon_get_folio() 拒收非 LRU folio,hugetlb 内存全被报成从未访问
  • 清 access bit 不带 TLB flush,缓存翻译命中的访问不会重新置位
  • 1GiB 映射下 TLB 几乎不 refill,访问率观察值钉死在 TLB-refill 率
  • 1 TiB 主机实测:guest 内 842 GiB 随机访问一小时与空闲统计上不可区分

方案

让 hugetlb folio 对监控可见;信号真实度一层不在本系列,推迟另发。

可见性一层,folio 粒度的 rmap walker 学会处理 hugetlb folio:page_vma_mapped_walk() 对 hugetlb folio 返回的是 huge PTE,walker 原先按普通 PTE 粒度读写与通知,现在改为按整页大小 aging(清 young bit 走 set_huge_pte_at()),mmu_notifier 通知也覆盖整页。guest 侧的访问记录挂在 secondary MMU(KVM/EPT)的 accessed bit 上,通知覆盖整页才能让它浮出来。paddr 的两个监控原语(mkold 与 young 检查)换用新加的 damon_get_monitor_folio(),它额外接纳 hugetlb folio;空闲 hugetlb pool 里的 folio 引用计数为零,照旧被自然拒掉。DAMOS action 的施加方(reclaim、LRU 调整、迁移)仍用原来的 damon_get_folio():这些动作本来就作用不了 hugetlb folio,行为不变。注意监控粒度随之变为整页:1GiB 页里一个字节被碰,整页报 accessed。

信号真实度一层推迟:带 TLB flush 的 aging_flush 选项要等 workload 侧影响与工作集测量的量化数据补齐后再发。

修复 hugetlb 监控的可见性
┌──────────────────────────────────────────┐
│ rmap walker 学会按 huge PTE 粒度 aging, │
│ mmu_notifier 覆盖整页,EPT accessed      │
│ bit 浮出                                 │
└────────────────────┬─────────────────────┘
                     ▼
┌──────────────────────────────────────────┐
│ paddr 监控原语换用接纳 hugetlb 的取      │
│ 值,DAMOS action 保持 LRU 限定不变       │
└──────────────────────────────────────────┘

收益

Intel EPT 宿主机,944 GiB guest 跑 842 GiB masim 负载,估计访问率的空闲到负载对比:

配置 空闲到负载访问率(GB/s) 分离度
未打补丁 0.08→0.21 无(guest 内存不可见)
本系列(可见性修复) 1.70→1.96 1.2 倍