RWF_DONTCACHE
写入页标 dropbehind
回收优先丢弃
避免大写污染 page cache
落盘前仍是脏页
writeback 写回
IOCB_DONTCACHE 每次写
generic_write_sync()
filemap_flush_range()
写者热路径
遍历 page tree
提交 I/O
阻塞写者数毫秒
尾延迟膨胀
问题
回写提交不在后台
而是在写者上下文内联
旧路径 · 每次写都 flush
generic_write_sync()
filemap_flush_range()
写者自己提交 writeback
内联做什么
遍历 page tree
提交 I/O
等待 / 排队回写
写者热路径被拉长
perf 结论
瓶颈不是锁竞争
是 writeback submission
提交工作本身
毫秒级阻塞
症状
尾延迟膨胀
大写时尤其明显
新路径 · 只发 kick
filemap_dontcache_kick_writeback()
generic_write_sync()
return count
WB_start_dontcache
test_and_set_bit()
test_and_clear_bit()
已有位就不再唤醒
多次写合并一次
回写转到后台
wb_wakeup()
flush worker
写者不再等待提交
仍不保证立即落盘
关键
只 kick,不内联
后台 flusher 接手
per-bdi 脏页计数
WB_DONTCACHE_DIRTY
per bdi_writeback
只数 dropbehind 脏页
何时增加
folio_account_dirtied()
folio_test_dropbehind()
wb_stat_mod(wb, WB_DONTCACHE_DIRTY, nr)
与 WB_RECLAIMABLE 并列
何时减少
folio_clear_dirty_for_io()
folio_account_cleaned()
非 DONTCACHE lookup 清 flag
inode 切换 wb 时迁移
作用
告诉 flusher 该写多少
不是全 BDI flush
kick 后做什么
wb_check_start_dontcache()
wb_stat_sum(wb, WB_DONTCACHE_DIRTY)
nr_pages = 计数
构造 work
sync_mode = WB_SYNC_NONE
range_cyclic = 1
reason = WB_REASON_DONTCACHE
wb_writeback(wb, &work)
最老脏 inode
从 oldest dirty inode 回写
写够计数就停
不限定 dontcache inode
保留 I/O 批量
范围
只写计数这么多页
不扫全 BDI 脏页
bit 合并多次 kick
WB_start_dontcache
一次只置一位
多次写 → 一次唤醒
消费与重置
test_and_clear_bit()
回写前原子消费
并发写可重新置位
下一轮 flusher
per-CPU 聚合
wb_stat_sum()
不是 wb_stat()
低于 percpu batch 也可见
小写不会被漏掉
结果
不每次写都唤醒
计数仍准确可见
单流
seq 298 → 897 MB/s
+201%
rand 131 → 236 MB/s (+80%)
尾延迟
p99 135,266 → 23,986 us
-82%
p99.9 8,925,479 → 28,443 us
-99.7%
4 job 顺序写
2,529 → 4,532 MB/s
+79%
buffered 4,616 MB/s
基本平价
测试
T6F_SKL_1920GBF / 251 GiB RAM
xfs on NVMe / fio io_uring
32 文件
1,548 → 3,499 MB/s (+126%)
p99 10,170 → 602 us
峰值脏页 1,837 → 213 MB
竞争写者
buffered 868 → 433 MB/s
dontcache 415 → 433 MB/s
聚合 1,284 → 866 MB/s(基线 863)
此前 buffered 2:1 饿死
外推边界
仅 dontcache 路径
buffered / direct 不受影响
同机同盘,不外推
作者 benchmark
竞争尾延迟
119 ms → 33 ms
p99.9,作者数据
空格
播放/暂停
←
→
逐动作
R
重播
⚠
1x