MGLRU 页表回收
shrink lruvec
回收一个代际
遍历 mm 页表
walk_mm()
需要稳定 mm_struct
才能安全地走
遍历的是地址空间,但要先有稳定的 mm_struct
旧做法
mmget_not_zero() 抬升 mm_users
把整个地址空间 pin 住,然后才遍历
实际只需要
稳定的 mm_struct
能拿/放 mmap_read_lock,能遍历稳定的 mm_mt
多抬一层 mm_users,正是问题的起点
旧:get_next_mm()
return mmget_not_zero(mm) ? mm : NULL;
有 mm_users 才返回,且顺手把 mm_users +1
没有 mm_users 就直接跳过这个 mm
旧:iterate_mm_list()
mmput_async(*iter);
遍历结束后异步放下
mm_users 的引用
两个计数别混
mm_users:地址空间使用者
mm_count:mm_struct 生命周期
mmget 动的是 mm_users
mm_count 没变
垂死进程的窗口
mm_users 只剩 1
MGLRU 扫描时 +1,变成 2
reaper 随后看到 2,以为地址空间还有人用
reaper 的关键检查(节选)
if (atomic_read(&mm->mm_users) <= 1)
return true;
只有引用降到 1 层,才认为进程准备释放
MGLRU 扫描期间
mm_users = 2
不是进程真的还有两个用户
只是回收器自己抬了一层
结果
task_will_free_mem() 返回 false
oom-reaper 跳过
process_mrelease 也跳过
被杀进程的内存
要等 MGLRU 放下引用
Android 上可见数秒延迟
替换引用操作
- return mmget_not_zero(mm) ? mm : NULL;
+ mmgrab(mm);
+ return mm;
- mmput_async(*iter);
+ mmdrop(*iter);
diff 节选:只动这两处引用
mmgrab()
抬升 mm_count
只 pin mm_struct
不碰 mm_users
mmdrop()
放下 mm_count
遍历结束再释放
不依赖 mm_users
只换两处引用,不改遍历逻辑
mmget 家族 -> mmgrab/mmdrop
为什么只稳结构就够
mm_mt 就在 mm_struct 内部
mmgrab 保证 mm_struct 不释放
树也不会被释放
遍历期间的保护
walk_mm(): mmap_read_trylock(mm)
持读锁遍历
拿不到读锁就不遍历
结构稳定 + 读锁
mm_mt 不会中途变化
无需 pin 整个地址空间
Android 现场
process_mrelease()
用户态 reaper
可见症状
被杀进程的 MM 被跳过
进程内存数秒不释放
生产环境可见
根因不是 reaper
mm_users 被 MGLRU 抬高
task_will_free_mem() 误判
reaper 只是照规则跳过
修复后
不再抬 mm_users
task_will_free_mem() 判定正确
垂死进程内存
不必等 MGLRU 放引用
可以及时释放
Android 的数秒延迟
现场问题得到消除
收益是机制推断 + 现场观察
没有量化 benchmark
作者只给机制与现场观察
动画不编数字
改动范围很窄
只换 MGLRU 页表遍历的 mm 引用
回收策略 / mmap_lock 不变
一句话总结
mmget/mmput -> mmgrab/mmdrop
只稳 mm_struct,不 pin 地址空间
reaper 能及时回收垂死进程
空格
播放/暂停
←
→
逐动作
R
重播
⚠
1x