memcg 回收:三条既有路径,只有一条主动
memcg 回收
分配越过阈值
memory.max / memory.high
分配者上下文同步回收
全局内存压力
kswapd 或 direct reclaim
沿 memcg 树扫描
memory.reclaim
用户态写入目标字节数
写入者上下文同步回收
唯一的主动入口
但决策仍留给用户态
守护进程轮询统计 → 算差额 → 写接口
回收同步占住写入者
CPU 开销记在写入者 cgroup
缺口:BPF 能观测,却不能发起回收
BPF 已有
bpf_mem_cgroup_page_state()
读 memcg 统计
bpf_wq 异步回调
还缺一环
观测到压力后
无法对指定 memcg
发起回收
结果
厂商私有异步
回收实现各自为政
主线缺少统一承接
本系列补的就是中间这一环
异步、带外的主动回收:BPF 观测信号 → 对指定 memcg 回收
与在 charge 路径内联决策的 memcg struct_ops 钩子互补
承接厂商私有实现,减少内核碎片化
sleepable kfunc + bpf_wq:把回收搬到 workqueue
bpf_proactive_reclaim()
sleepable kfunc
size / 返回值均以字节计
对指定 memcg 发起回收
bpf_wq
workqueue 异步回调
每个目标 memcg 一个 item
CPU 开销暂随上下文记账
一次调用只走一趟
size 钳到 MEMCG_CHARGE_BATCH(32 页)
不重试凑 size;实际扫描不封顶
回调只回收一批
随后重新入队同一 work item
不在回调内循环
长回调会拖住共享 workqueue;回收持 lru_lock 时中断关闭,争用会拖延 IPI
要更多就逐批再调,BPF 随时可停
是否提交下一批,由 BPF 程序决定
互不串行堆积;目标 cgroup 开始 dying 就停
参数语义对齐 memory.reclaim
size / 返回值:字节
GFP_KERNEL | MEMCG_RECLAIM_MAY_SWAP | MEMCG_RECLAIM_PROACTIVE
→ try_to_free_mem_cgroup_pages()
与 memory.reclaim 同参;size 不足一页 → -EINVAL
返回值与 bpf_mem_cgroup_usage() 等统计 kfunc 同单位
swappiness 三态
0–200
显式回收倾向
201
只回收 anon
-1
跟随 cgroup 设置
越界 → -EINVAL;三个常量从宏改为 enum 导出到 BTF,BPF 经 vmlinux.h 直接引用
只走一趟,不为凑 size 重试
size 钳到 MEMCG_CHARGE_BATCH(32 页);实际扫描不封顶
要更多就逐批再调,而不是传入更大的 size
回收不能嵌套
嵌套会踩坏外层状态
try_to_free_mem_cgroup_pages() 进入时覆盖
current->reclaim_state,退出时清空
MGLRU 会解引用 mm_walk
结构上排除
kfunc 不设运行时检查
只注册在 BPF_PROG_TYPE_SYSCALL 下
fentry 等通用 sleepable 程序调不到
KF_SLEEPABLE 带来的锁约束
回收过程允许睡眠
可能进入文件系统 shrinker
调用方不能持有 inode_lock 等 fs 锁
回收路径重取该锁即死锁
干净进程上下文
SYSCALL 程序 + bpf_wq / task_work
回调仍属同一程序类型,仍可调用
仍运行在进程上下文
嵌套回收与 fs 锁死锁在结构上不可能发生
无需运行时检查;调用范围由注册类型限定
配套 selftest:定时观测,逐批回收
bpf_timer
2ms 周期触发
bpf_wq 回调
读 WORKINGSET_REFAULT_FILE 增量
判断 workingset 被挤 → 回收低优先级 cgroup
一轮最多 32 批 × 128 KiB
每个回调只跑一批,再重新入队
剩余额度记在 map 跨回调传递
ringbuf 上报
按超时等待回收事件
只断言 reclaim_calls / reclaimed_bytes
单凭计时区分不了真回收与空跑;计时只记日志,不作失败依据
目标 cgroup dying 即停
回收前检查自身 css 的 CSS_ONLINE
rmdir 清标志;CSS_DYING 由 kill_css_sync() 设置,自身 css 不置位
之后 bpf_cgroup_from_id() 失败,上报 TARGET_GONE,计数不再增长
QEMU 8 GiB · 10 vCPU · 10 轮;TEST_MEMCG_ASYNC_RECLAIM_BENCH=1
受压高优先级负载完成时间中位数
baseline
BPF 异步回收
改善
15.3s
2.0s
-86.9%
被收割低优先级负载完成时间中位数
baseline
BPF 异步回收
改善
12.2s
7.5s
-38.5%
中位数来自 10 轮;受压负载提前结束后,被收割负载独享父级 memory.max
10 轮受压完成时间:baseline 5.45–18.20s,BPF 0.86–3.06s;单轮提速 62.7%–94.7%
真回收由 reclaim_calls / reclaimed_bytes 断言,计时只作基准记录
空格
播放/暂停
←
→
逐动作
R
重播
⚠
1x