缺页路径只锁一条 VMA
page fault
目标 VMA
lock_vma_under_rcu()
mmap_lock
进程地址空间全局读锁
RCU 下找到 VMA,再给它加读锁
mmap、munmap、mprotect 的写者
读者与写者如何互斥
fast path
只对目标 VMA 加读锁
不与整个地址空间的写者争锁
读锁拿不到才回退 mmap_lock
依赖的原语
RCU · maple tree · refcount
没有 SMP / MMU 也能工作
三个条件同时满足才可用
ARCH_SUPPORTS_PER_VMA_LOCK
架构显式 select
SMP
对称多处理
MMU
内存管理单元
per-VMA lock 可用
Kconfig 名字与实际含义
名不副实
标记的是「架构是否可能受益」
不是「架构能否支持」
这些原语不挑配置
RCU · maple tree · refcount
在 !SMP / !MMU 下同样能工作
限制来自 Kconfig,不来自硬件能力
配置受限的连锁反应
通用代码不能依赖
想用 per-VMA lock 的调用点
自备 fallback
额外一条 mmap_lock 路径
同一段查找逻辑
维护两份实现
受影响的调用点
proc maps
自备 fallback
binder
自备 fallback
TCP zerocopy
自备 fallback
本系列的直接动机
x86 shadow stack
通用路径要规避
mmap_lock 递归加锁
持读锁期间缺页
per-VMA lock
理想工具
配置受限,通用代码里用不了
线程 A:全程持有 mmap_lock 读锁
mmap_read_lock()
读锁不放
vma_lookup()
找到 VMA
操作触发 fault
如 copy_from_user 缺页
读锁一直没放,fault 又走进同一把锁
fault handler 再次申请读锁
线程 A 的 fault
mmap_read_lock()
线程 B
mmap_write_lock()
B 已排队,等 A 放读锁
公平读写锁:后来的读者排在 writer 后面
A · 持有读锁
mmap_read_lock 未释放
B 等 A 放锁
B · 等写锁
mmap_write_lock 排队
A 的 fault · 等读锁
排在 B 后面
A 等 fault 返回
环闭合:谁都不放锁
删除两项配置
ARCH_SUPPORTS_PER_VMA_LOCK
CONFIG_PER_VMA_LOCK
配置不再是可用性的前提
锁字段转为无条件编译
vm_lock_seq · vm_refcnt
vma_writer_wait · mm_lock_seq
vm_area_struct / mm_struct 的锁字段
唯一代价
!MMU / !SMP 构建多出几个字段
全树只留 per-VMA 版本
proc task_mmu
userfaultfd
只保留 per-VMA 版本
删除 #ifdef 双份实现
NOMMU 的保守处理
vma_mark_attached()
空操作,VMA 永远 detached → per-VMA 读锁失败 → 落回 mmap_lock
stack_map_lock_vma()
!MMU 下继续持有 mmap_lock
bpf_iter_task_vma_new()
!MMU 下直接返回 -EOPNOTSUPP
配置删掉了,行为仍然可预期
struct vm_area_struct *vma_start_read_unlocked(
struct mm_struct *mm, unsigned long address)
{
struct vm_area_struct *vma;
vma = lock_vma_under_rcu(mm, address);
if (vma) return vma;
mmap_read_lock(mm);
vma = vma_lookup(mm, address);
if (vma && !vma_start_read_locked(vma))
vma = NULL;
mmap_read_unlock(mm);
return vma;
}
fast path
成功即返回
完全不碰 mmap_lock
内部等 writer
mmap_read_lock 只用于等
锁上 VMA 立即放锁
fault-safe
返回时不持 mmap_lock
缺页不会成环
常见惯用法整体替换
mmap_read_lock()
vma_lookup()
mmap_read_unlock()
vma_start_read_unlocked()
…
vma_end_read()
查找与加锁一体完成,不留窗口
lock_vma_under_rcu(mm, address)
失败
mmap_read_lock(mm) 等 writer
fast path
slow path
vma_lookup + vma_start_read_locked
mmap_read_unlock;返回持锁 VMA
锁上 VMA
调用方不持 mmap_lock
调用方拿到的要么是持锁 VMA,要么是 NULL
为什么它 fault-safe
调用方持 per-VMA 读锁
mmap_lock 已释放
操作触发 fault
copy_from_user 缺页
fault 取 mmap_read_lock
只排在 writer 之后
没人长持 mmap_lock,环合不上
helper 的返回值
持读锁的 VMA
调用方全程不持 mmap_lock
NULL
地址没有 VMA 覆盖
两种情况都不需要调用方准备 fallback
binder shrinker:fallback 为何徒劳
lock_vma_under_rcu() 失败
主因:writer 正持 mmap_write_lock
mmap_read_trylock()
同样失败 → fallback 徒劳
shrinker 回收路径不能等待
一次 trylock 失败即放弃
删除整条 fallback
锁拿不到时
per-VMA 锁失败
不等待
binder_alloc_is_mapped()
确认映射状态
VMA 仍在
放弃回收
已解除映射
无须持锁释放页
拿不到锁,就先问映射还在不在
binder page insert
C 实现
Rust 实现
vma_start_read_unlocked()
删除 mmap_lock fallback
Rust 侧同步提供 helper 绑定
TCP zerocopy receive
find_tcp_vma()
旧的 fallback 版本
vma_start_read_unlocked()
同一套查找与加锁
mmap_locked 状态回传
连「当前持的是哪把锁」也不再回传
三处调用点共用一套加锁路径
预期收益(代码逻辑推断)
fast path
完全避开 mmap_lock
降低锁竞争
fault-safe
操作 VMA 期间可安全缺页
消除与 writer 的死锁环
无条件可用
通用代码免去 #ifdef
不再维护双份实现
清理调用点
binder 与 TCP 删除
mmap_lock fallback 路径
作者未提供性能数据
以上为代码逻辑推断的预期
per-VMA lock 的地位
从可选优化变成通用基础设施
通用代码从此可以无条件依赖它
空格
播放/暂停
←
→
逐动作
R
重播
⚠
1x