可执行文件的页被映射并首次执行,就应尽量留在内存
否则反复从磁盘重读,引发 IO thrashing
exec 代码能否留住,直接影响内存压力下的构建与运行流畅度
classical LRU 早已把映射的可执行文件页当作一等公民
首次使用后即激活它们,让 exec 代码获得更高的留内存优先级
MGLRU 的回收逻辑与 classical LRU 不同
对这类 folio 的保护并不完整
结果就是 exec 代码被过早回收,磁盘被反复读
lru_gen_look_around() 与 walk_mm
可能提前检查并清除 exec folio 的 access flag
folio_update_gen() 与 lru_gen_set_refs()
对 exec folio 只设 PG_referenced,与普通页一视同仁
shrink_folio_list() 因此忽略首次使用
exec 代码被换出,引发 IO thrashing
三次失手串成一条链:标志被清、只算被引用、回收时当冷页
根因:MGLRU 的引用计数路径没有主动提升
classical LRU 在 exec folio 首次使用时就把它激活,MGLRU 少了这一步
修复跟随 classical LRU 的逻辑
在 folio_update_gen() 与 lru_gen_set_refs() 里识别首次使用的映射 executable folio,直接提升
判定由 is_exec_file_folio() 统一完成,它要求同时满足两点
VMA 可执行
VMA_EXEC_BIT
file folio
folio_is_file_lru()
两处判定合起来,才认作「映射的可执行文件页」
两个函数都改在「首次使用」分支里动手
即 folio 既没有 PG_referenced,也不是 workingset
if (!folio_test_referenced(folio) && !folio_test_workingset(folio)) {
if (is_exec_file_folio(folio, vma_flags)) {
set_mask_bits(&folio->flags.f, LRU_REFS_FLAGS, BIT(PG_workingset));
return true;
}
set_mask_bits(&folio->flags.f, LRU_REFS_MASK, BIT(PG_referenced));
return false;
}
返回真 → 上层走 FOLIOREF_ACTIVATE 激活路径
否则只设 PG_referenced 并返回假,按普通页对待
folio_update_gen() 走的是页表扫描(aging)路径
同样的首次触达,在扫描侧也得认出来
对同样的首次使用 exec file folio
跳过「仅设 PG_referenced」的旧分支
直接落到 promote 逻辑
把它提升到目标 generation
扫描路径与引用路径,都在首次使用那一刻动手
引用计数路径
lru_gen_set_refs()
命中 exec file folio 就返回真
上层走 FOLIOREF_ACTIVATE
页表扫描路径
folio_update_gen()
跳过只设 referenced 的旧分支
直接 promote 到目标 generation
两条路径都能在首次使用时把 exec 代码留住
而不依赖后续扫描是否还看得到 access flag
这也补上了「access flag 被提前清除」那一手
即便标志已经没了,首次使用依然能认出 exec 代码
让 VMA 执行标记能传进引用计数路径
walk_update_folio() 接收 vma,并把 vma->flags 向下传递
前置改动:把 folio_referenced() 及相关逻辑
从旧的 vm_flags_t 切换到 vma_flags_t
整套引用计数都能拿到一致的 VMA 标记
is_exec_file_folio() 由此拿到可靠的 vma_flags
walk_update_folio()
vma->flags 下传
is_exec_file_folio()
作者在 32-core Arm 机器上,把 memcg 限制设为 2G 制造内存压力
用 make -j32 构建内核,观察构建耗时的变化
kernel build sys time(make -j32)
Before
9248.543 s
After
7861.579 s
改善
-15.0%
内存压力下 exec 代码不再被过早换出
减少了重读磁盘的开销,构建耗时随之下降
作者将这一结果表述为 sys time 维度的改善
空格
播放/暂停
←
→
逐动作
R
重播
⚠
1x