插入 page cache:先把必须串行的两件事放进锁内
以 __filemap_add_folio() 为例,mapping 的 i_pages 由 xa_lock 保护
xa_lock_irq(&mapping->i_pages)
关中断,进入临界区
XArray 插入
把 folio 挂到 i_pages 的槽位
mapping->nrpages 更新
页数计数与 XArray 必须一起串行
xas_unlock_irq()
解锁、开中断
这两件事必须受 xa_lock 保护,否则并发插入互相破坏。
问题:临界区里还夹着第三类工作
XArray 插入与 nrpages 更新之外,lruvec 统计也在这里
xa_lock_irq() · 关中断临界区
持锁期间,其他插入者进不来
XArray 插入
必须串行
nrpages 更新
必须串行
lruvec 统计
不碰 XArray
也不需要 xa_lock
xas_unlock_irq()
解锁、开中断
统计本身不碰 XArray,却实打实拉长着关中断临界区。
CONFIG_MEMCG 下,一次统计是一串级联更新
每次 lruvec 统计都要写 per-cpu 计数器,并通知 cgroup rstat
lruvec 统计
NR_FILE_PAGES;PMD-mappable 再加一次 NR_FILE_THPS
per-cpu memcg 计数器
memcg 侧计数更新
per-cpu lruvec 计数器
lruvec 侧计数更新
cgroup rstat
通知 rstat 记录这次变化
普通 folio 更新一次,PMD-mappable folio 更新两次;统计越重,临界区越长。
方案:把 lruvec 统计挪到锁外
只把必须串行的留在 xa_lock 里
锁内 · 关中断
XArray 插入 + nrpages 更新
解锁 · 开中断
xas_unlock_irq();确认插入真正成功
锁外 · lruvec 统计
NR_FILE_PAGES;PMD-mappable 再加 NR_FILE_THPS
统计不碰 XArray,放到锁外不改变串行性。
安全:统计挪出去,为什么仍然安全?
两重保证,缺一不可
page cache 引用已钉住
插入成功后,page cache 持有 folio 引用
folio 不会被释放
调用方仍持 folio lock
folio->mapping
保持稳定
truncate / reclaim 拿不到锁
无法把它移出 page cache
统计读取的 mapping 归属,不会在读取途中变化。
收益:30 秒锁竞争采样
生产主机,系统级 perf lock contention;归因到 __filemap_add_folio() 的最热调用栈
指标
数值
竞争次数
20,867
总等待时间
557.930 ms
采样内全部锁等待
3.998 s
占总锁等待比例
14%
普通 folio 省一次统计的锁内时间,PMD-mappable folio 再多省一次。
空格
播放/暂停
←
→
逐动作
R
重播
⚠
1x