MGLRU 把编码塞进 folio->flags
PG_referenced
访问标记
LRU_REFS
记录访问次数
LRU_GEN
所在 generation
PG_swapbacked
anon 或 file 归属
同一套编码约定散落在十几处调用点
walker 的批量记账
页表 walker
不持 lruvec 锁与 folio 锁
cmpxchg 晋升
晋升到最年轻 generation
update_batch_size()
再读一次 live flags
按当时的 PG_swapbacked,把 delta 记进 lrugen->nr_pages 的 anon 或 file 列
散落的 open-coded 位操作
set_mask_bits
按掩码改 flags
cmpxchg 手工拼值
循环里自行合成
移位解码
再从 flags 取字段
十几处调用点,每处自行保证不越界、不串位
walker 晋升与记账之间
cmpxchg 晋升
快照先落地
type 被并发翻转
lazyfree 清 PG_swapbacked
再读 flags 记错列
批量 delta 没有对账
回收路径又会在 dirty lazyfree folio 上把 PG_swapbacked 置回
错列永久歪斜,无法对账
lru_zone_size 的紧张
unsigned long
下溢会 wrap 成巨大值
memcg shrinker
据此算出的回收量巨大
几乎空转不停
sanity check 在更新路径
正负 delta 要分开处理,还得随时准备 WARN 并纠正
收编成语义明确的 helper
lru_get/set_gen_flags()
读写 generation
lru_get/set_refs_flags()
读写访问次数 refs
folio_set_lru_refs()
全带 kdoc + VM_WARN 越界检查
散落各处的 open-coded 位操作逐处替换
几处顺序与范围收紧
PG_workingset 先置
再清 refs 位
读 tier 不看过低值
folio_migrate_lru_refs()
referenced 状态集中一处
再标目标 folio uptodate
max_seq 移进函数
walk_update_folio() 内部读
每个 folio 一次
晋升目标始终是当前最年轻 generation
read_ctrl_pos() 改用 (tier_min, tier_max) 闭区间
lru_zone_size 改为带符号 long
unsigned long
下溢 wrap 成巨大值
更新路径 WARN 并纠正
signed long
更新路径只剩一次普通加法
读侧负值 WARN 并按 0 返回
低频读侧检查,高频更新不再分支
记账与晋升共用一份快照
cmpxchg 快照 flags
generation 与 type 同字
folio_update_gen()
带出快照里的 type
update_batch_size()
按快照记账
type 只在离开 LRU 时变化,发生在 lruvec 锁内的 del/add 对之间
CAS 落点只有三种
del 之前
快照 anon/G
批量记 anon G→G'
del 随后从 anon 计数移除
del 与 add 之间
gen 为 -1
晋升直接放弃
迁移由 del/add 自己记账
add 之后
快照 file/新 generation
批量记 file 该 gen→G'
与 add 时的记账衔接
作者未提供性能数据,以上为代码逻辑推断
Before
After
lru_zone_size 更新
分支 + 下溢纠正
→
一次普通加法
max_seq 读取
每个 walk range 一次
→
每个 folio 一次
generation 计数
记错列后永久歪斜
→
账实可对
flag 越界
靠调用点人工保证
→
VM_WARN 自带检查
2022
引入 walker 批量记账时潜伏
直到收编位操作才被暴露
空格
播放/暂停
←
→
逐动作
R
重播
⚠
1x