仅凭 RCU 的页表遍历
walker
RCU read lock
PTE 页表
无锁读取
不加页表锁,只靠 RCU 保护
前提:页表只能在宽限期后释放
宽限期后释放
walker 安全
提前释放
walker 踩到已释放页表
整条链路都压在这条前提上
用 RCU 遍历时,提前释放就是事故
正在遍历的页表
walker 仍持有引用
中途被释放
或被复用改写
读到不属于自己的内容
页表项已失效
一次释放,walker 看到的就是别人的页表
此前已经铺好的三步
Hugh Dickins · 2023
pte_free_defer()
khugepaged 页表回收
按 RCU 延迟释放
Qi Zheng
x86 semi-RCU → RCU
tlb_remove_ptdesc()
多架构转换
Lance Yang
批量分配失败回退
IPI → synchronize_rcu()
回退路径也 RCU 安全
架构支持现状
多数架构已支持
CONFIG_MMU_GATHER_RCU_TABLE_FREE
仍有架构未转换
页表释放行为不统一
配置长期分裂,死代码残留
THP collapse 的 deposit
PMD 映射的 PTE 页表
当前正在使用
collapse 成 THP
2MB 大页
deposit 进 PMD
留待日后 split
deposit 的是当前这张正在用的页表
今天的做法为何无害
mmap / VMA 写锁
collapse 全程持有
anon rmap 锁
锁住反向映射
GUP-fast
由 IPI 兜底
普通遍历被写锁挡住,GUP-fast 由 IPI 兜底
改用仅凭 RCU 的遍历之后
walker 仍在遍历旧表
RCU 不提供锁
旧表被 deposit 出去
在无关 PMD 的 split 中复用
改写页表内容
根因:deposit 的正是可能被遍历的那张表
collapse 改为 deposit 新表
新分配 PTE 页表
清零,未被遍历过
deposit 进 PMD
供日后 split
旧表 pte_free_defer()
宽限期后释放
deposit 的始终是一张没被遍历过的新表
两条时间线
walker 这一侧
旧表只是延迟消失
不会被 deposit 改写
collapse 这一侧
deposit 的新表
从未被遍历过
代价
collapse 本就要分配 huge folio
分配大头已经在这里
多分配一张 PTE 表
其他 deposit 路径本就如此
这点开销可以忽略
多数架构:机械切换
现成机制 + 配置
或小改即可
自定义释放入口
CONFIG_HAVE_ARCH_TLB_REMOVE_TABLE
三个架构需要额外处理
sh-X2:PMD 从 slab 分配
PMD 来自 slab
不能套通用释放
__tlb_remove_table()
自定义实现
指针最低位编码层级
按位走不同释放路径
m68k-motorola:改动最大
call_rcu 释放
可能发生在 softirq
get/free_pointer_table()
改用禁下半部的 spinlock
add_pointer_table()
分配逻辑单独拆出
softirq 是唯一异步持锁上下文,禁下半部即足够
sparc32:页表建在共享后备页
共享后备页
指针最低位编码层级
softirq 释放路径
没有 mm,旧锁非 IRQ 安全
新 IRQ 安全 spinlock
取代 mm->page_table_lock
nocache pool 的 bit_map 锁同样改为 IRQ 安全
共享页表互斥全面换成 IRQ 安全锁
全部转换完成后
CONFIG_MMU_GATHER_RCU_TABLE_FREE
死代码删除
CONFIG_HAVE_ARCH_TLB_REMOVE_TABLE
自定义释放只剩这一个入口
更新后的契约
mmgrab()
保活 mm 时,遍历正在拆除的页表范围是安全的
page table lock
读到的条目在拿锁后仍须重新校验
mmap lock / VMA lock
改条目仍须页表锁,并持其一排除并发拆除
本系列只保证:页表不会在遍历中被释放
预期收益(代码逻辑推断)
仅凭 RCU 即安全
减少锁竞争
避免锁序顾虑
后续改动的前提
为完全 RCU 页表遍历
铺平道路
释放行为统一
各架构私有路径删除
死配置代码删除
作者未提供性能数据
以上为代码逻辑推断的预期
先保证页表活着,再谈无锁遍历
空格
播放/暂停
←
→
逐动作
R
重播
⚠
1x