当前位置:首页>Linux>还在手动写 call_rcu 回调?Linux 7.0 合入 kmalloc_nolock + kfree_rcu,原子上下文分配不再头疼

还在手动写 call_rcu 回调?Linux 7.0 合入 kmalloc_nolock + kfree_rcu,原子上下文分配不再头疼

  • 2026-09-18 11:27:37
还在手动写 call_rcu 回调?Linux 7.0 合入 kmalloc_nolock + kfree_rcu,原子上下文分配不再头疼

如果你写过 Linux 内核模块,一定对 RCU(Read-Copy-Update) 不陌生。它让读端几乎零开销,写端优雅延迟回收——代价是"请等一个 grace period"。

但等等,你有没有遇到过这种情况:

你调了 synchronize_rcu_expedited() 来快速推进 GP(Grace Period),可那些用 call_rcu() 注册的回调,根本不管 expedited GP 有没有完成,死等下一个普通 GP。你说急不急?

更头疼的是,在内核编程中,有些上下文不能睡眠、不能拿锁——NMI handler、BPF 程序、某些 RCU 回调。在这些地方你想分配内存?传统 kmalloc() 直接罢工。

今年 7 月初的 2026 Linux Storage, Filesystem, Memory-Management, and BPF Summit 上,两个演讲正好戳中这两个痛点。LWN 的 daroc 把两场合并成一篇报道,因为它们拼起来,刚好是一条从分配到回收的完整无锁内存管线。

下面我们逐一拆解。


一、RCU 回调也能搭 expedited GP 的便车

开发者:Puranjay Mohan补丁系列:[PATCH v1 00/11] RCU: Enable callbacks to benefit from expedited grace periods

痛点

Linux RCU 有两种 grace period:

  • 普通 GP
    :定期触发,保守、节能。
  • Expedited GP
    :通过 synchronize_rcu_expedited() 主动触发,速度快但能耗高。

逻辑上,不管哪种 GP 完成,都意味着"所有读者已经退出临界区",回调可以安全执行了。然而内核之前的实现是:call_rcu() 注册的回调只跟踪普通 GP 的序列号。即使 expedited GP 已经跑完,回调仍然纹丝不动,必须等到下一个普通 GP 到来。

这就好比你要等公交车,明明旁边有一辆直达快车到了,但因为你的票是普通票,只能看着快车开走,继续等下一趟普通车。

方案

Puranjay 的修改很直观:在回调跟踪结构体 struct rcu_segcblist 里同时记录普通 GP 和 expedited GP 的序列号,然后改造推进逻辑——只要任意一种 GP 完成,回调就前进。

涉及的修改路径包括:

  • rcu_segcblist_advance()
    :回调分段列表的推进函数
  • rcu_core()
     和 rcu_pending():RCU 核心处理与挂起检查
  • NOCB(rcuog kthread)唤醒路径

效果立竿见影:synchronize_rcu_expedited() 的调用者不再是唯一受益者,整个系统的回调回收延迟都降低了。

顺便一提:SRCU-fast 在 arm64 上的优化

同一位开发者在今年 3 月还有一个相关优化。他发现 arm64 上 this_cpu_inc() 会插入不必要的 preempt_disable/enable 指令——SRCU-fast 的 per-CPU 计数器最终会被汇总,中途被迁移也没关系,根本不需要迁移保护。

于是引入 srcu_percpu_counter_inc(),在 arm64 上直接使用 atomic_long_fetch_add_relaxed()(编译为 ldadd 指令,L1 cache 内原子完成)。在 72 核 Neoverse-V2 系统上,SRCU-fast 读端耗时从 9.273 ns 降到 8.275 ns,提升约 11%。不换硬件,纯靠优化指令选择,白捡 11% 的性能。


二、kmalloc_nolock():原子世界的 malloc

开发者:Harry Yoo(Oracle)、Alexei Starovoitov合入版本:Linux 7.0

痛点

内核里有些地方绝对不允许睡眠或拿锁:

  • NMI handler(非可屏蔽中断处理)
  • BPF 程序的某些钩子
  • RCU 读侧临界区内的特定路径
  • 早期启动代码

这些场景下,传统 kmalloc() 指望不上——它可能需要拿 slab 分配器的锁,或者触发 page reclaim 导致睡眠。开发者要么提前预分配缓冲区,要么写丑陋的静态池,要么干脆绕道走。

kmalloc_nolock() 就是为了填这个坑而生的:它使用 try_cmpxchg 原子操作(x86 上是 cmpxchg16b)直接操作 per-CPU freelist,全程不碰任何锁。

关键突破:可以用 kfree_rcu() 回收了

kmalloc_nolock() 不是今年才引入的,但它之前有个很烦的限制:分配出来的对象只能用 kfree_nolock() 释放。

这意味着如果你想把一个对象推迟到所有读者都退出后再回收——RCU 的经典用法——你得手动写 call_rcu() 回调,绕一大圈。BPF 社区对此叫苦连天。

Harry Yoo 的补丁移除了这个限制。现在:

  • kmalloc_nolock()
     分配的对象 可以用 kfree() 或 kfree_rcu() 直接释放
  • kmemleak 等调试工具也做了适配
  • 相关文档已更新到 include/linux/rcupdate.h

这对 BPF 程序员来说是一大福音——BPF 程序经常在原子上下文中分配内存,现在可以无缝对接 RCU 回收机制,无需自建回收逻辑。

另一大变化:Sheaves 简化实现

Vlastimil Babka 的 Sheaves(谷束) 补丁系列(21 个补丁,2026 年 1 月)彻底重写了 slab 分配器的 per-CPU 部分,用更简洁的 sheaves 机制替代了旧的 per-CPU partial slabs。

这个改动对 kmalloc_nolock() 意味着什么?巨大的简化。旧的实现需要一套复杂的无锁快速路径(this_cpu_try_cmpxchg128/64),sheaves 上线后这些全都不要了。代码量缩减,维护负担降低,正确性更容易验证。

架构约束

  • 需要硬件支持 __CMPXCHG_DOUBLE(x86 的 cmpxchg16b、arm64 的 CAS 对),否则无法使用
  • 在 PREEMPT_RT 内核上,不允许从 hardirq/NMI 上下文调用(因为底层机制依赖的 local_lock 在 RT 内核中是可睡眠锁)

三、交叉映射:两条线拼出无锁内存管线

把上面两件事放在一起看,意义更大:

阶段
传统方式
无锁方式
分配kmalloc()
 → 可能拿锁、可能睡眠
kmalloc_nolock()
 → cmpxchg 无锁操作
使用
正常 RCU 读侧
同上
回收kfree_rcu()
 → 等待 GP
kfree_rcu()
 + expedited GP 感知 → 更快回收

Puranjay 的 RCU 改进解决了回收端的问题——回调更快被调度执行;Harry Yoo 的 kmalloc_nolock() + kfree_rcu() 解决了分配端的问题——原子上下文也能分配,还能用 RCU 回收。

两条线汇合,就有了从分配到回收的完整无锁内存管线。

对于 BPF 程序、网络 fast path、存储栈等性能敏感子系统,这意味着:

  • 更少的锁争抢
  • 在更多上下文中能分配内存
  • 内存回收更及时,不会因为"等普通 GP"而积压

四、展望:还有哪些拼图?

目前 kmalloc_nolock() 已合入 Linux 7.0,但工作并没有结束:

  • kvfree_rcu_nolock() 已在 RFC 阶段
    ——Harry Yoo 和团队正在推进更专用的无锁回收路径,进一步减少对 RCU 核心机制的依赖。
  • KUnit 测试用例
    已有人提交,用于捕捉 {kmalloc,kfree}_nolock 的微妙 bug。
  • Sheaves 重写
    仍在持续,RCU sheaves 处理中的 batching 优化还在 TODO 列表上。

Linux 内核的并发基础设施在过去二十年里走过了"大内核锁 → spinlock → RCU → 无锁数据结构"的演进路径。2026 年的这两项工作,标志着内核在极端上下文(NMI、BPF)中的内存管理这个最后的堡垒上又攻克了一大块阵地。


以上就是今年 LSM+BPF Summit 上关于 RCU 和 kmalloc_nolock() 的核心内容。如果你在内核模块或 BPF 程序中碰到过"这里不能分配内存"的窘境,kmalloc_nolock() + kfree_rcu() 的组合值得关注。

你对哪个部分最感兴趣?RCU 回调的 GP 感知优化,还是无锁内存分配的工程实现?欢迎留言讨论。


参考来源:

  • LWN: Faster RCUs and lockless memory allocation (付费)
  • Puranjay Mohan: RCU: Enable callbacks to benefit from expedited grace periods
  • Vlastimil Babka: Sheaves v3 — slab: replace cpu (partial) slabs with sheaves
  • Harry Yoo: slab updates for 7.0 part 2 (kfree_rcu support)

最新文章

随机文章