mprotect 的调用链(简化)
mprotect()
用户改保护位
change_protection()
按 VMA 范围处理
change_pte_range()
逐 PTE 热循环
最终都落到逐 PTE 的紧密循环里
处理规模
逐 PTE
成百上千次
甚至 10 万次
为什么值得抠
极紧密循环
一点小低效被放大
微优化也能看见
本文的两步
先把 softleaf 代码移出主函数
再给 order-0 单页开 fast path
两步都围绕同一个热循环
最常见的 order-0
单个 base page
却仍走通用大循环
批量逻辑原本为 large folio 准备
通用大循环的负担
循环逻辑相当庞大
很多分支
CPU 处理不佳
成本被循环次数放大
但不能简单删掉
large folio 批量处理
删掉会伤大页
批量逻辑本身有价值
目标:给 order-0 一条更轻的路
order-0 走 fast path
large folio 保留批量路径
第一步:移出 softleaf 处理
change_softleaf_pte(vma, addr, pte, oldpte, cp_flags)
从 change_pte_range() 主函数搬到独立函数
softleaf 分支不再占据主循环
主函数只留调用(节选)
pages += change_softleaf_pte(...)
softleaf 细节被隔离
主循环变短
效果
change_pte_range() 更短
可读性更好
为 fast path 腾出空间
仅搬代码,不改语义
先把主循环瘦下来
这一步不改变行为
只是让下一步的 order-0 分支更清楚
第二步:order-0 单独分支
if (likely(nr_ptes == 1))
change_present_ptes(..., 1, ...);
else
change_present_ptes(..., nr_ptes, ...);
pages += nr_ptes;
节选:省略了函数参数和提交细节
order-0 路径
likely(nr_ptes == 1)
nr_ptes 是常量 1
走更轻的提交逻辑
large folio 路径
nr_ptes > 1
保留批量提交
不牺牲大页
不是删掉批量路径
而是让最常见的情况少走弯路
让编译器看见常量(节选)
static __always_inline void
prot_commit_flush_ptes(...)
static __always_inline void
change_present_ptes(...)
关键
nr_ptes == 1
常量传播
分支在编译期消解
热路径更短
结果
批量 PTE 逻辑内联
生成代码更紧凑
热循环更短
减少函数调用
__always_inline 要求编译器内联
不是运行时开关
目标仍是让热循环更紧凑
测试条件
google/benchmark · g++ -O2
mprotect_bench
每次 mprotect 耗时
85,967 ns
Baseline
70,684 ns
Patched
~-18%
变化
作者结论:约 18% speedup
对 mprotect 密集负载是端到端加速
这是单基准的数字,不外推所有 workload
复测
libmicro/mprot_tw4m
Luke Yang 复测 · 更贴近真实场景
提升范围
5%
最低
~25%
多数
55%
最高
怎么读这组数
多数 workload 约 25%
范围从 5% 到 55%
幅度依场景变化,不是固定值
下一个瓶颈
modify_prot_start_ptes
x86 的 xchg()
本系列未解决
为什么贵
ptep_get_and_clear()
依赖 D 位
xchg() 代价高
一句话总结
softleaf 移出 + order-0 快路径 + 内联
保留 large folio 批量处理
基准约 18%,复测多数约 25%
空格
播放/暂停
←
→
逐动作
R
重播
⚠
1x