回收侧 · shrink_inactive_list()
从 inactive LRU 尾部隔离一批 folio
写回侧 · async swap
每写完一个就 folio_rotate_reclaimable()
并发执行
中间没有任何同步
inactive LRU · 尾部最冷
F1
F2
F3
F4
当前批次 · F3–F4
→ 尾部
排进写回队列
shrink_folio_list()
swap folio 只排队写回,继续下一个
写完即调用
folio_rotate_reclaimable()
要求 folio 已经被放回 LRU
写回
回收批次
写回完成时 folio 尚未放回,rotate 落空
放回之后 · 默认逻辑把 F4 放回 LRU 头部
头部 · 最近使用
尾部 · 回收先扫
F4
F1
F2
F3
…
已写回 · 干净
更热的 folio
回收方向
隔离进批次
F4 被 shrink_inactive_list() 取出
写回先完成
folio_rotate_reclaimable() 落空
放回头部
错过本应得到的尾部位置
冷热反转
干净 folio 挡在更热的 folio 前面
回收效率下降
要绕一整圈才再访问它
MGLRU 已用 commit 359a5e1416ca 重试隔离期间写回的 folio;传统 LRU 一直没有对应处理。
重新回收这些 folio
会丢掉 swap cache
将来命中还要重新读盘
改为
补上错过的轮转
只改它在 LRU 上的位置
swap cache 原样保留
move_folios_to_lru(..., bool do_rotate)
放回时甄别:命中就 lruvec_add_folio_tail()
只在放回路径开启
不改回收决策,只补轮转
回收批次结束
逐一放回 folio
条件全中 · 放到 LRU 尾部
lruvec_add_folio_tail()
否则 · 留在 LRU 头部
被引用或有意外 pin
下一次回收先扫到它
甄别条件 · 六条全部成立
01
在 inactive LRU 上
02
未映射(unmapped)
03
干净(clean)
04
不在写回中
05
未被引用
06
引用计数等于预期值
被引用
留在头部
照样拿二次机会
意外 pin(如 GUP)
现在回收不了
也留在头部
判据全部偏保守:只有确凿无疑的候选才放尾部。
拿不准就留在头部,不动链表顺序。
do_rotate 只在放回路径开启
shrink_inactive_list()
do_rotate = true
隔离批次放回时,补一次轮转
shrink_active_list()
do_rotate = false
升降级顺序是刻意安排
evict_folios() · MGLRU
do_rotate = false
已有 clean-list 重试机制
两种 LRU 的放回路径暂不统一
后续可用同一套 do_rotate 取代 MGLRU 的重试通道,统一两种 LRU。
测试环境 · 1.6Gi 内存 / 1Gi swap 的 cgroup
空闲内存 · free -h Mem
Before
299Mi
After
322Mi
+7.7%
Swap 已用 · free -h Swap
Before
678Mi
After
328Mi
−51.6%
memory.usage_in_bytes
1073700864
→
1071140864
−0.24%
同一时点:memsw − memory 与 Swap 已用
时点
memsw − memory
Swap 已用
对比
Before
324Mi
678Mi
严重偏离
After
326Mi
328Mi
基本吻合
空格
播放/暂停
←
→
逐动作
R
重播
⚠
1x