背景
readahead 在预读一批页时,会在最后一页设一个 PG_readahead 标记。这个标记的意思是"下次访问到这页时,再触发一次异步预读",通常用来让顺序访问的下一批页提前进缓存。
┌────────────────────────────┐
│ 预读拉满文件剩余页到达末尾 │
└─────────────┬──────────────┘
▼
┌────────────────────────────┐
│ 仍在最后一页设预读标记 │ 标记触发异步预读,但末尾无页可读,纯属空转
└─────────────┬──────────────┘
▼
┌────────────────────────────┐
│ 还连累 mmap 的缺页预映射 │ 带标记的页被跳过,访问时多一次小缺页
└────────────────────────────┘
但当 readahead 已经把文件剩余的页全部拉进来、到达 EOF 时,再设这个标记就适得其反了。第一,它仍会触发一次异步预读,可文件已经到尾,这次预读几乎注定是空转的 no-op。第二,更隐蔽的影响在 mmap 路径:mmap 的缺页处理有 fault around 机制,会把周围已读入缓存的页批量映射进页表,但在挑选可映射页时会跳过带 PG_readahead 标记的页。结果末尾那页被排除在 fault around 之外,等进程真正访问它时,得多走一次 minor fault。
问题
- readahead 到 EOF 仍在末页设 PG_readahead
- 该标记触发几乎空转的异步预读
- 标记让末页被 fault around 跳过
- mmap 访问末页多一次 minor fault
方案
核心是一句话:到尾了就别再设这个标记。
readahead 检测到已经拉完文件剩余页、抵达 EOF 时,跳过 PG_readahead 的设置。
旧:到末尾仍设预读标记
┌──────────────────────────────────────┐
│ 末尾页带预读标记 │
└──────────────────┬───────────────────┘
▼
┌──────────────────────────────────────┐
│ 触发空转的异步预读,还挡住 mmap 预映 │
│ 射 │
└──────────────────────────────────────┘
新:到末尾不再设预读标记
┌───────────────────────────────────┐
│ 预读到末尾不设标记 │
└─────────────────┬─────────────────┘
▼
┌───────────────────────────────────┐
│ 无空转预读,mmap 预映射覆盖末尾页 │
└───────────────────────────────────┘
这样末页上不再有标记,那条空转的异步预读不会被触发;mmap 的 fault around 也能把末页纳入批量映射,进程访问它时不再多一次 minor fault。作者通过 /sys/kernel/tracing/events/readahead 的 trace 观察一个简单程序,应用 patch 后 page_cache_ra_unbounded 的调用次数明显减少,印证了空转预读被消除。
收益
作者未提供 runtime benchmark(吞吐/延迟数据)。从机制与 tracing 观察推断的预期收益:
- EOF 处不再触发空转的异步预读,减少无谓的 readahead 路径开销
- mmap 的 fault around 重新覆盖末页,访问末页不再多一次 minor fault,直接改善 mmap 访问延迟
- 整体减少 readahead 与缺页路径的无效工作