k[v]free_rcu()
把释放推迟到 RCU grace period 之后
调用方不用等,先返回
但释放路径内部要拿锁
在持锁状态不明的上下文里调用
可能撞上自己持有的锁
锁在不在手里,自己不知道
unknown context 的连锁反应
调用方持锁状态不明
无法判断能不能安全调用
撞上自己持有的锁
在同一个 CPU 上等自己
自我死锁
于是只能绕开不用
unknown context:kfree_rcu() 只能绕开
约束不解决,调用方就得自己找替代方案
SLUB 的 sheaf:每个 CPU 成捆缓存待释放对象
per-CPU rcu sheaf
对象成捆塞进本 CPU 的捆里
攒满一整捆才提交给 RCU
攒满 → call_rcu()
一次 call_rcu() 交一整捆
捆没满就不提交
kfree_rcu_sheaf() 快路径原本挑上下文
!allow_spin
不允许 spin 的上下文
PREEMPT_RT
实时内核的锁语义
unknown context
持锁状态不明
sheaf 快路径对这三类上下文都失效
!allow_spin、PREEMPT_RT、unknown context 都进不去
kfree_rcu_nolock()
任意上下文可调用的新入口
sheaf 快路径
无锁批量塞进每 CPU 的 rcu sheaf
能走就走,不碰全局锁
攒满时按 IRQ 状态提交
兜底:挂 irq work
延后走 kvfree_rcu 批处理
不改调用上下文的执行流
不占 sheaf,也不阻塞
先看对象来源:不适合 sheaf 的直接走兜底
再看调用上下文:进了 sheaf 怎么拿到空捆
两关都过才走快路径,否则 irq work 延后
① 不适合 sheaf 的对象,先筛掉
vmalloc 对象
走 vfree 语义
large kmalloc
超出 kmalloc 上限
跨 NUMA 节点
slab 对象跨节点
直接走兜底路径
irq work → kvfree_rcu 批处理
② slab 小对象,交给 sheaf
__kfree_rcu_sheaf()
带上 SLAB_FREE_NOLOCK 标记
塞进本 CPU 的 rcu sheaf
无锁批量释放
不拿全局锁
筛完的对象各走各的:一条无锁快路径,一条不阻塞的兜底
第一关看对象来源,第二关看调用上下文
上下文这一关,就是下面要解决的无锁取 sheaf
① 用 free_flags 描述上下文
free_flags
告诉 free 路径:现在能不能 spin
可 spin
不可 spin
不可 spin:空 sheaf 从 spare 或 barn 取
spare sheaf
预留的备用捆
barn
整捆仓库
拿锁只用 trylock
失败就把这个空 sheaf 直接释放
不硬等,不阻塞调用上下文
对象入捆这一步本来就不需要锁
改动只发生在「怎么拿到空捆」,没动「怎么放对象」
② 攒满提交 · IRQ 开着
IRQ 没禁
per-CPU 数据没有重入
call_rcu()
内部用 local_irq_save()
保护 per-CPU 数据
IRQ 已禁用时
IRQ 关着
不能就地提交满 sheaf
满 sheaf 挂到 irq work
延后到能安全提交的时点
调用上下文不用等
③ PREEMPT_RT:按不可 spin 处理
local_trylock 与 spinlock 只要不真正 spin
且不在 NMI 或 hardirq 里,就安全
RT 也能走 sheaf 快路径
不必退回兜底
快路径覆盖实时内核
旧的 kvfree_rcu 批处理
struct rcu_head
func 指针
next 指针
回调固定三选一,func 指针纯浪费
新的 kvfree_rcu_head
struct kvfree_rcu_head
next 指针,仅一个
对象起始地址在慢路径重算,KASAN tag 不丢
回调固定为三选一,按对象来源直接选
kfree()
vfree()
free_large_kmalloc()
省下的那个指针,就是每对象一份的元数据
2 → 1
每对象元数据:指针数
① 任意上下文
拿空 sheaf 只 trylock,不硬等
unknown context 不再绕开
释放不再受死锁约束
② 快路径覆盖
多数 slab 小对象走 sheaf
无锁批量,攒满才提交
PREEMPT_RT 也能走
③ 元数据减半
2 → 1
rcu_head 两指针 → 一指针
KASAN tag 不丢
能走 sheaf 就走,走不了挂 irq work 延后
快的无锁批量释放,慢的也不堵调用上下文
作者未提供性能数据,收益由机制推出
空格
播放/暂停
←
→
逐动作
R
重播
⚠
1x