memory.min / memory.low 的保护水位
memory.min / memory.low
用户声明的保护水位
page_counter_calculate_
protection()
自顶向下逐级换算
emin / elow
每个 memcg 的有效保护值
mem_cgroup_below_min()
低于 emin:直接跳过
mem_cgroup_below_low()
低于 elow:放缓扫描
判断消费的是换算后的 emin/elow
两条全局回收路径的换算时机
kswapd
lru_gen_age_node()
树遍历时逐节点计算
emin/elow 新鲜
每次遍历都更新
非 kswapd
lru_gen_shrink_node()
→ shrink_many() → shrink_one()
emin/elow 陈旧或零
全程无人计算保护值
直接回收与根级 memory.reclaim 都走这条路
复现:a/b 两级 memcg
min 各 100M · 写 200M 文件 · 根 memcg 写 500M memory.reclaim
a/b memory.current
约 212M
memory.min
100M
回收后
6M
100M 硬保护完全没起作用
shrink_one() 消费的 emin/elow 停在陈旧值或零
kswapd 算过的值到回收时已经过时
emin/elow 派生自祖先设置与 children_min_usage
沿祖先链现算保护值
root memcg
memcg a
memcg a/b
cgroup 核心维护 ancestors[]
从根到目标,自顶向下
父先于子,满足换算顺序
每级 O(1),总开销 O(depth)
层级通常 3 到 5 层
shrink_one() 在判断前补算
if (!current_is_kswapd())
mem_cgroup_calculate_protection_path(NULL, memcg);
kswapd 路径跳过
lru_gen_age_node() 已算过
effective_protection() 的整数溢出
先乘后除
unsigned long 宽度
≥16TB 时乘积超 2^64 回绕
mul_u64_u64_div_u64()
128 位中间值完成乘除
保护值不再回绕
比例钳到 1 以内
子先于父计费时分子可大于分母
避免 #DE
新增调用路径也经过这里,所以溢出修复排在最前
作者未提供性能数据,以上为代码逻辑推断
Before
After
非 kswapd 路径
memory.min/low 失效
→
保护恢复生效
保护值换算
没有计算
→
O(depth) 现算
祖先链访问
树遍历
→
ancestors[] 直接可查
≥16TB 机器
乘法溢出回绕
→
128 位乘除
保护值必须在回收时点现算
空格
播放/暂停
←
→
逐动作
R
重播
⚠
1x