背景

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 与缺页路径的无效工作