PG_readahead
预读窗口尾部设标记
下次访问触发异步预读
顺序读提前拉下一批
标记在哪
page_cache_ra_unbounded()
i == mark 时置位
mark 在 lookahead 区
不是每页都有
正常作用
访问命中标记页
page_cache_async_ra()
提前读下一窗口
减少同步等待
前提
标记页之后还有数据
EOF 时这个前提不成立
旧行为 · 撞到 EOF
do_page_cache_ra()
nr_to_read 截到文件尾
旧代码仍保留 lookahead_size
小 folio 路径
nr_to_read > end_index - index
只读剩余页
lookahead_size 未清零
mark 仍可能落在尾部
大 folio 路径
page_cache_ra_order()
mark = index + size - async
limit 被文件尾截断
mark 仍可能 <= limit
结果
EOF 窗口仍可能带标记
标记页之后已无数据
代价一 · 空转异步
访问带标记的页
page_cache_async_ra()
但文件已到尾
异步路径
ra->async_size > 0
page_cache_ra_unbounded()
读请求越过 EOF
几乎注定 no-op
trace 现象
/sys/kernel/tracing/events/readahead
page_cache_ra_unbounded 调用
打 patch 后明显减少
作者观察,不是吞吐
代价
多走一次 readahead 路径
没有实际 I/O 收益
代价二 · fault around
mmap 缺页
do_fault_around()
filemap_map_pages() 批量映射
挑选页
next_uptodate_folio()
folio_test_readahead()
命中就 skip
标记页被排除
访问标记页
fault around 没映射
再走一次 minor fault
延迟多一跳
不是数据错误
原因
PG_readahead 本为顺序读
在 EOF 反而挡路
修复一 · 清零 lookahead
do_page_cache_ra()
nr_to_read 截到 end_index
lookahead_size = 0
触发条件
nr_to_read > end_index - index
读完剩余页
即已到文件尾
才清零
标记计算
page_cache_ra_unbounded()
mark = 窗口内偏移
lookahead_size 为 0
mark 落在读取范围外
效果
EOF 窗口不再 set 标记
其他窗口不受影响
修复二 · 大 folio 路径
page_cache_ra_order()
limit = min(EOF, _max_index)
limit 落在窗口内就改 mark
旧算法
mark = index + size - async
limit 截到窗口尾
mark 可能落在末页
ra_alloc_folio() 置位
新算法
if (limit > index + size -1)
mark 照旧计算
else mark = ULONG_MAX
round_down 后永不相等
边界
limit 也可能来自 _max_index
不只文件尾
结果 · 标记页纳入映射
标记页无 PG_readahead
next_uptodate_folio 不跳过
fault around 覆盖标记页
mmap 路径
do_fault_around()
filemap_map_pages()
批量建立 PTE
少一次 minor fault
readahead 路径
不再触发空转异步
page_cache_ra_unbounded 调用减少
标记页只读一次
没有额外 I/O
结果
省掉无效工作
延迟收益是机制预期
证据与边界
trace 观察
page_cache_ra_unbounded 调用减少
作者未给 runtime benchmark
证据
/sys/kernel/tracing/events/readahead
简单程序
打 patch 前后对比
只说明空转减少
边界
仅影响窗口截断
不改 fault around 本身
大 folio 受 _max_index 影响
收益随访问模式
结论
机制明确,数字有限
不编吞吐 / 延迟
空格
播放/暂停
←
→
逐动作
R
重播
⚠
1x