背景

/proc/pid/smaps_rollup 汇总一个进程的内存统计,是监控工具的常读接口。此前的实现进门先拿 mmap_lock 读锁,然后逐个 VMA 走页表聚合数据,全程不放。VMA 多、页表大的进程一读就要长时间占锁,mmap、munmap、mprotect 这类写者全被堵在门外,smaps_rollup 因此成了 mmap_lock 长持有的典型来源。

全程持 mmap_lock 的读取
┌─────────────────────────────────────┐
│ 读 smaps_rollup 先拿 mmap_lock 读锁 │
└──────────────────┬──────────────────┘
                   ▼
┌─────────────────────────────────────┐
│       逐 VMA 走页表,全程不放       │
└──────────────────┬──────────────────┘
                   ▼
┌─────────────────────────────────────┐
│       写者排队等锁,竞争放大        │
└─────────────────────────────────────┘

maps、smaps、numa_maps 几个兄弟接口早已改成 RCU 加 per-VMA read lock 的组合:RCU 保证 VMA 树遍历安全,per-VMA lock 稳定正在处理的 VMA 与它的页表遍历。smaps_rollup 还没跟上。

问题

  • smaps_rollup 全程持 mmap_lock 读锁遍历 VMA 与页表
  • 长持锁堵住 mmap、munmap 等写者,是 mmap_lock 竞争的典型来源
  • maps 等兄弟接口已改用 RCU 加 per-VMA lock,它还没跟上
  • 回退 mmap_lock 时 VMA 可能已合并或增长,续扫位置要处理

方案

复用 maps 同款锁组合

show_smaps_rollup() 不再进门就锁 mmap_lock,改为 RCU read lock 下用 proc_get_vma() 逐个取 VMA 并尝试 per-VMA read lock,锁住当前 VMA 后走页表聚合,页表遍历换用可以在 per-VMA lock 下调用的 walk_page_range_vma()。proc_get_vma() 原本是 maps 序列接口的 helper,结尾会额外返回 gate VMA 供 maps 报告;这个专用行为挪回 maps 自己的调用方之后,它变成干净的通用取 VMA 接口,smaps_rollup 才得以复用同一条加锁路径。

竞争回退与合并续扫

per-VMA lock 拿不到时仍回退 mmap_lock;回退之后 mmap_lock 若也有写者竞争,就临时放锁减压,处理下一个 VMA 时再试 per-VMA lock,最坏不过是再回退一次。放锁窗口里 VMA 树可能已变:被释放或没有后继的情况自然收场,已报过的 VMA 若增长或与下一个合并,重新遇到它时从 last_vma_end 处续采,只统计还没处理的部分。旧实现为这四种情形准备的长注释,如今只剩合并续扫这一种需要特判。

RCU 加 per-VMA lock,竞争回退
┌───────────────────────────────────┐
│        RCU 保护 VMA 树遍历        │
└─────────────────┬─────────────────┘
                  ▼
┌───────────────────────────────────┐
│ per-VMA read lock 稳住 VMA 与页表 │
└─────────────────┬─────────────────┘
                  ▼
┌───────────────────────────────────┐
│ 拿不到回退 mmap_lock,竞争就暂放  │
└─────────────────┬─────────────────┘
                  ▼
┌───────────────────────────────────┐
│ 合并过的 VMA 从 last_vma_end 续采 │
└───────────────────────────────────┘

收益

竞争基准(busyduration 2 秒,20 次采样,读取 smaps_rollup)下,单位为秒:

指标 Before After 改善
中位延迟 0.174 0.007 降 96.0%
最差延迟 5.220 3.225 降 38.2%
最好延迟 0.161 0.007 降 95.7%