RWF_DONTCACHE 读 / 写
给 folio 打上 dropbehind 标记
dropbehind folio
语义:用完即弃
writeback 完成
立刻从 page cache 淘汰
目的:streaming 工作负载不挤占 page cache
只访问一次的数据,不该长期占着缓存
但有一条既有约定:有人在用,就取消淘汰
正常 page cache 查找命中带标记的 folio 时,会把标记清掉
清掉标记 = 这块数据重新被当成正常缓存
正常查找命中:清标记
page cache 查找
命中带标记 folio
清 dropbehind
标记撤销
继续留在 cache
不淘汰
查找命中说明有真实的缓存用户
这个信号与「用完即弃」相反,所以标记必须撤掉
这条清理路径一直工作正常
问题出在另一条映射路径上
下一页:mmap 缺页时的 fault-around
mmap 缺页:fault-around 顺手多映射几页
缺页 fault
实际只缺一页
filemap_map_pages()
按序取相邻的 uptodate folio
直接装进页表
一次 fault 映射多页
全程不经过正常查找
所以 dropbehind 标记不会被清
mapped + dropbehind 共存
映射着,却还带着「用完即弃」的标记
这是正常查找那条清理路径覆盖不到的状态
writeback 完成时,folio 仍带 dropbehind
无条件 unmap + invalidate
旧逻辑不看映射状态
结果
映射被拆掉
页表里的映射没了
cache 里被清掉
位置也不再保留
再次访问重新 fault-in
还要重读 I/O
淘汰后再访问,就要重新 fault-in、重读 I/O
mapped 明明意味着还有别的缓存用户
folio 却被当成一次性数据淘汰
writeback 完成
folio_mapped() 为真
只清 dropbehind 标记
映射与 cache 位置原样保留
未被映射
照旧 unmap 并 invalidate
streaming 语义不变
filemap_end_dropbehind(): + folio_mapped()
失效调用多一个条件:仍被映射就不失效
mapped 本身就是「还有人在用」的信号
与「查找命中就清标记」的既有约定同向
跳过失效,还顺带避开一段会睡眠的代码
folio_unmap_invalidate()
内部调用 unmap_mapping_folio()
拿 i_mmap_rwsem
这个过程可能睡眠
folio_end_dropbehind() 可能运行在不可抢占的 task 上下文
threaded IRQ handler 里 in_task() 依然为真
mapped folio 不走这条失效路径
这个上下文也就不会在此睡眠
未映射的 folio 照旧走,问题只在原本会被误淘汰的那批
① 保留 mapped folio
映射与 cache 位置保留
不再被强制淘汰
② 后续 mmap 访问
免去额外 fault-in
不必重读 I/O
③ 不可抢占上下文
不再可能睡在
i_mmap_rwsem 上
未被映射的 dropbehind folio:unmap + invalidate 照旧
streaming 语义不回归
mapped 就是还在用的信号:保留 folio 与映射,而不是把它清出 page cache
作者未提供性能数据,收益由机制推出
空格
播放/暂停
←
→
逐动作
R
重播
⚠
1x