背景

dropbehind 是「用完即弃」的 page cache 语义:RWF_DONTCACHE 的写入或读取会给 folio 打上 dropbehind 标记,writeback 完成时立刻淘汰,让 streaming 工作负载不挤占 page cache。正常的 page cache 查找命中带标记的 folio 时会清掉标记:有人在用,就取消淘汰。

dropbehind folio 的生命周期
┌───────────────────────────────────────────┐
│ RWF_DONTCACHE 写入或读取打上 dropbehind   │
│ 标记,意图用完即弃                        │
└─────────────────────┬─────────────────────┘
                      ▼
┌───────────────────────────────────────────┐
│ 正常 page cache 查找命中则清标记,有      │
│ 人在用就取消淘汰                          │
└─────────────────────┬─────────────────────┘
                      ▼
┌───────────────────────────────────────────┐
│ writeback 完成时淘汰,unmap 并 invalidate │
│ 出 page cache                             │
└───────────────────────────────────────────┘

mmap 一侧的 fault-around 是这条「清标记」路径的缺口:filemap_map_pages() 为了让一次 fault 顺带映射相邻的就绪 folio,直接在 xarray 里按序取 uptodate folio 装进页表,全程不经过正常查找,dropbehind 标记也就不会被清掉。被 fault-around 捎带映射的 dropbehind folio 因此进入 mapped 与 dropbehind 共存的状态。

问题

  • fault-around 直接映射相邻就绪 folio,不经正常查找,标记不会被清
  • mapped 意味着另有缓存用户在用,folio 却仍带着用完即弃的标记
  • writeback 完成时无条件 unmap 并 invalidate,仍被映射的 folio 被强行淘汰
  • 后续 mmap 访问必须重新 fault-in,带来额外 fault 与 I/O

方案

让仍被映射的 folio 在 writeback 完成后免于淘汰。

filemap_end_dropbehind() 里让 folio 失效的调用加上 folio_mapped() 条件:仍被映射的 folio 只清 dropbehind 标记,映射与 page cache 中的位置原样保留;未被映射的照旧 unmap 并 invalidate。mapped 本身就是「还有人在用」的信号,这与正常查找命中就清标记的既有约定同向,dropbehind 的 streaming 语义不受影响。

修复后 writeback 完成时按是否被映射分流
                   ┌─────────────────────────────────────┐
                   │ writeback 完成,folio 带 dropbehind │
                   │ 标记                                │
                   └──────────────────┬──────────────────┘
                 ┌────────────────────┴────────────────────┐
                 ▼                                         ▼
┌──────────────────────────────────┐     ┌───────────────────────────────────┐
│ 仍被映射:只清标记并保留,映射用 │     │ 未被映射:unmap 并 invalidate,照 │
│ 户不受打扰                       │     │ 旧淘汰                            │
└──────────────────────────────────┘     └───────────────────────────────────┘

跳过这条让 folio 失效的路径还有一处附带收益:folio_unmap_invalidate() 里的 unmap_mapping_folio() 要拿 i_mmap_rwsem,可能睡眠;folio_end_dropbehind() 可能在不可抢占的 task 上下文运行(threaded IRQ handler 里 in_task() 依然为真),mapped folio 不再走这条路,该上下文也就不会在此睡眠。

收益

作者未提供性能数据,从代码逻辑推断的预期收益:

  • 仍被映射的 dropbehind folio 不再被强制淘汰,后续 mmap 访问免去额外 fault-in 与重读 I/O
  • 不可抢占 task 上下文的 writeback 完成路径不再可能因 unmap_mapping_folio() 拿 i_mmap_rwsem 而睡眠
  • 未被映射的 dropbehind folio 淘汰行为不变,streaming 语义不回归