dying memory cgroup
cgroup 已删除,元数据仍被引用
无法释放,长期占着内存
页回收效率被这些元数据拖累
持续多年的顽疾,不是新问题
封面信的问题描述
"memory leaks or inefficiencies"
"memory wastage"
"significantly reducing the
efficiency of page reclamation"
目标:让 LRU folio 不再钉住 memcg
改对 object cgroup 记账
cgroup 死亡后,不必等页面释放
垂死 memcg 才能及时消失
作者未提供量化 benchmark
复现脚本验证:修复后垂死 memcg 数量不再显著增长
旧:页面记账
folio->memcg_data = (unsigned long)memcg
charge_memcg() 里 css_get()
页面一直持有 memcg 引用
直到 uncharge 才 css_put()
cgroup 删除时
css offline -> 等引用归零
页面还在,memcg 就不能释放
元数据滞留,越堆越多
文件页生命期长,尤其明显
其他类型早已解耦
slab 对象 / 非 slab 内核分配 / per-CPU 对象
都改对 object cgroup 记账,不 pin 原 memcg
只剩 LRU 页还在用 memcg 指针
所以问题集中在 LRU 页
匿名页与文件页都在分配时记账
直到回收才松手
同一作业重启进新 cgroup
实例 A
memcg A
文件页留在
page cache
实例 B
memcg B 再次访问
页面仍指向 A;A 被 B 的访问钉住
文件页比匿名页更棘手
可跨 cgroup 共享
生命期可能超过所属 memcg
同一作业重启后仍反复访问
A 无法释放 -> 垂死 memcg 堆积
内存浪费 + 页回收效率下降
清理的不是页面本身,而是它背后的 cgroup 引用
旧
memcg_data -> mem_cgroup
页面 pin 住原 cgroup
css 引用直到回收
cgroup 不能走
新
memcg_data -> obj_cgroup
objcg->memcg -> mem_cgroup
页面不再持有 memcg css 引用
objcg 可以改指向父 cgroup
object cgroup 是可迁移的中间层
每个 memcg/node 有对应的 objcg
cgroup 死亡时,objcg->memcg 可在锁内切到父 cgroup
folio 继续指向同一个 objcg,记账关系不丢
统一后的指针形态
LRU folio / kmem folio -> objcg
slab folio -> objcg 向量;都落到 object cgroup
临时访问者
只需要访问期间 memcg 不释放
不要求 folio 与 memcg 绑定稳定
get_mem_cgroup_from_folio()
mem_cgroup_swap_full() 等
RCU 读锁
rcu_read_lock();
memcg = folio_memcg(folio);
/* 使用 memcg */
rcu_read_unlock();
需要引用时:短暂 css_tryget()
rcu_read_lock();
do {
memcg = folio_memcg(folio);
} while (!css_tryget(&memcg->css));
rcu_read_unlock();
解绑后 memcg 可能正在释放
用完 mem_cgroup_put();RCU/引用保证访问期间还活着
这只解决临时读,不解决锁内绑定
但 LRU 维护要求更强
持 lruvec 锁时,folio 必须属于这个 lruvec
reparent 可能把 folio 移到父 lruvec
锁拿在旧 lruvec 上,后续操作就错了
folio_lruvec_lock_irqsave()(节选)
rcu_read_lock();
retry:
lruvec = folio_lruvec(folio);
spin_lock_irqsave(&lruvec->lru_lock, *flags);
if (lruvec_memcg(lruvec) != folio_memcg(folio)) {
spin_unlock_irqrestore(&lruvec->lru_lock, *flags);
goto retry;
}
锁内校验
lruvec 属于哪个 memcg
folio 当前指向哪个 memcg
不一致 -> 解锁重取
一致 -> 安全操作
重新拿锁后,绑定已稳定
依赖 lruvec 锁的路径都要过这道校验
普通只读访问仍走 RCU / 引用,成本更低
cgroup 死亡时
mem_cgroup_css_offline()
-> memcg_reparent_objcgs(memcg)
逐个 node 处理,父 cgroup 接收
reparent 的关键步骤
加锁
objcg_lock
child/parent lru_lock
搬 LRU
lru_reparent_memcg()
链表移到父 lruvec
切 objcg
objcg->memcg
= parent
收尾
percpu_ref_kill()
旧 objcg
锁顺序:objcg_lock -> child lru_lock -> parent lru_lock
搬完链表后,folio 仍指向同一个 objcg,但 objcg 已指向父 memcg
两种 LRU 各自迁移
传统 LRU:lru_reparent_memcg()
MGLRU:lru_gen_reparent_memcg();都在同一把 objcg_lock 下
及时释放
页面不再 pin
原 memcg
文件页解耦
重启作业不再
钉住旧 cgroup
回收更轻
不再被垂死
memcg 元数据拖累
复现脚本的结果
修复后垂死 memcg 数量不再显著增长
作者未提供量化 benchmark;收益是机制性的
不是吞吐数字的胜利,而是让 cgroup 能真正消失
一句话记住这套修复
指针改 objcg:页面不再 pin 原 memcg
临时访问:RCU 或短暂引用
稳定绑定:lruvec 锁内校验后重取
死亡迁移:objcg 与 LRU 链表一起交给父 cgroup
空格
播放/暂停
←
→
逐动作
R
重播
⚠
1x