tmpfs 文件尾部的一页
≤ i_size
有效数据
> i_size
浪费的尾部
整个 huge folio 被占住,有效数据只有一小段
unused huge shrinker
收编这些 inode
split 尾部的 huge folio
把浪费收回来
per-superblock 列表
一个 superblock 一条 inode 链
全局回收这样够用
同一个 superblock 上的 inode
列表不感知 memcg
memcg A 内存紧张
触发回收
扫描整个 superblock
所有 inode 都看一遍
不区分归属
谁记账都照拆
同一个 superblock 的 inode 链
A
B
B
split 掉记账给 B 的 huge folio
B 的浪费与 A 的紧张无关
误伤无关 memcg
最坏情况
A 自己没有任何可回收的 shmem
回收完全无效
延迟已经付了,A 的账没解
生产上还观测到:split 时页锁竞争,卡顿数百毫秒
nr_cached_objects 门控
memcg 级回收整体跳过这个 shrinker
误伤消失
不再碰别的 memcg
但自己的候选也回收不到
本 memcg 的 shmem 浪费一直留着
过于保守
整体跳过 = 宁可什么都不做
整体跳过
误伤没了,自己也收不到
按 memcg 精确回收
只碰记账到本 memcg 的候选
这一版要做的,正是这一步
per-superblock inode 列表
不感知 memcg
memcg 感知 list_lru
按 memcg 与 NUMA 节点分链
入队:看尾部 huge folio 的归属
尾部 huge folio
memcg + NUMA node
对应的 list_lru 子链表
全局 / root memcg
走全部子链表
非 root memcg
只走本 memcg 的子链表
shmem quota 回收
保持全局语义
memcg A 回收时看得到的范围
A
A 的子链表
B
B 的子链表不动
shmem_inode_info 里存的是
obj_cgroup 引用
不是 mem_cgroup 引用
为什么不用 mem_cgroup
长生命周期的 CSS 会把 dying memcg 钉住
增删路径
在 RCU 下从 objcg 解析当前 memcg
解析出当下的归属
据此找到对应的子链表
与 list_lru 自身的行为对齐
memcg offline 时,list_lru 会把条目迁往父 memcg 的子链表
删除时总能找到正确的子链表
list_lru 记录的是 inode
split 的目标是它当前的尾部 huge folio
两者可能不同步
入队之后,尾部 folio 可能已经变了
扫描时校验 folio 的 memcg 与节点
不再匹配回收上下文,或暂时无法 split 的,都不留在原位
按当前尾部 folio 重新排队
条目留着,等下一次匹配
inode 已无浪费可收
从列表摘除
列表始终指向正确的子链表
charge 路径陷进 shrinker
split 持页锁曾达
182ms
数百毫秒的卡顿消除
回收只处理记账到本 memcg 的候选
shrinker debugfs 验证
memcg A · 64M tmpfs 尾部 THP
按 A 的 cgroup id 扫描
memcg B · 64M tmpfs 尾部 THP
不在 A 的回收范围里
现在:A 的 64M 全部回收
B 的 64M 原样保留
门控前作下
连 A 自己的 64M 也回收不到
空格
播放/暂停
←
→
逐动作
R
重播
⚠
1x