写入 memory.reclaim
64M swappiness=max
期望:只回收匿名页
swappiness=max
SWAPPINESS_ANON_ONLY
冷匿名页换出,绝不碰 page cache
两条扫描入口
get_scan_count
传统 LRU · 决定本次扫哪类页
swappiness / swap / 各 LRU 长度
get_swappiness + get_nr_to_scan
MGLRU · 另一套扫描决策
前者定语义,后者定扫描量
两条入口,同一个语义:只扫匿名页
这也是 proactive reclaim 的边界承诺
原来的判定顺序(错的)
进入扫描决策
无可回收匿名页?
无 swap 时命中
scan_balance = SCAN_FILE
swappiness=max 被静默改写
SWAPPINESS_ANON_ONLY 检查
排在文件页分支之后,来不及
顺序一错:匿名专属请求落进文件页分支
page cache 成了被回收的对象
echo '64M swappiness=max' > memory.reclaim
cgroup:64M file cache、无 swap
修复前
page cache 剩余
4K
pgsteal_proactive
16400
修复后
page cache 剩余
64M
pgsteal_proactive
0
归零
写操作返回 Resource temporarily unavailable
同一请求:修复前把 64M page cache 驱逐到 4K
修复后 page cache 原封不动,边界恢复
MGLRU 路径同样把 64M file cache 驱逐到 0
SWAPPINESS_ANON_ONLY 必须最先裁决
所有可能改写扫描目标的判断,都排在它后面
① 先判 anon-only
进入 get_scan_count
② 有可回收匿名页?
can_reclaim_anon_pages()
是
SCAN_ANON · 只扫匿名页
goto out
否
nr[] 清零 · 返回
主动放弃,保住 page cache
if (swappiness == SWAPPINESS_ANON_ONLY) {
if (!can_reclaim_anon_pages(...)) { memset(nr, 0, sizeof(*nr) * NR_LRU_LISTS); return; }
scan_balance = SCAN_ANON; goto out; }
get_swappiness()
旧:无 swap 检查把该值归零
匿名专属语义丢失
新:先认出 SWAPPINESS_ANON_ONLY
直接返回该值,绕过归零检查
get_nr_to_scan()
语义传到下游后,再决定扫多少
无可回收匿名页 → 返回 0
不对文件页做无用功
有可回收匿名页 → 按匿名页扫描
MGLRU 与 LRU 语义对齐
同一条请求,两套扫描器都只看匿名页
以前
无可回收内存 → -EAGAIN
接口被并发占用 → -EAGAIN
两种情况,一个错误码
用户态无法判断该等还是该重试
现在
无可回收内存 → 真实错误码
接口被并发占用 → 真实错误码
两种情形可以区分
与 memcg 路径保持一致
__node_reclaim() 的 gfp_mask 参数
已经没有任何使用者 → 删除,接口不再留死参数
per-node proactive reclaim 不再把错误折叠成 -EAGAIN
调用方能区分「已无可回收内存」与「接口被并发占用」
① page cache 不被误伤
无可回收匿名页时
直接放弃本次回收
避免重新读入的额外 IO
② 语义始终成立
有 swap / 无 swap
swappiness=max 只扫匿名页
回收边界恢复精确
③ 错误码可诊断
per-node 不再统一 -EAGAIN
与 memcg 路径一致
用户态可决策
作者未提供性能数据 · 以下为逻辑推断的预期收益
先把 anon-only 硬约束放到最前
MGLRU 保留完整语义,扫描量归零就停
64M page cache 不再被 swappiness=max 误伤
空格
播放/暂停
←
→
逐动作
R
重播
⚠
1x