1. RCU 基础概念:定义、设计哲学与核心优势
1.1 什么是 RCU?
RCU(Read-Copy-Update) 是 Linux 内核中一种高性能的同步机制,专为读多写少的场景设计。它允许读者在几乎不加锁的情况下并发访问共享数据,而写者通过“复制-修改-延迟回收”的方式完成更新,从而大幅降低读路径的开销。
核心定义:RCU 是一种基于“宽限期(Grace Period)”的同步技术,读者无需显式加锁即可安全访问共享对象,写者通过发布更新并等待所有在读读者退出后,再安全回收旧数据。
| 维度 | 传统读写锁 (rwlock) | RCU |
|---|---|---|
| 读者开销 | 原子操作 + 内存屏障(高) | 几乎零开销(仅编译器屏障) |
| 写者开销 | 直接修改(低) | 复制 + 替换指针 + 延迟回收(中高) |
| 并发度 | 读者-写者互斥 | 读者与写者完全并发 |
| 适用场景 | 读写比例均衡 | 读远多于写(99%+ 读) |
1.2 设计哲学:三阶段模型
RCU 的设计遵循一个清晰的三阶段流水线,每个阶段都有明确的语义和操作原语:
- Read(读):读者通过
rcu_read_lock()/rcu_read_unlock()标记临界区,直接读取指针指向的数据,无需任何原子操作。 - Copy(复制):写者分配新内存,将旧数据复制到新内存中,并在新副本上执行修改。
- Update(更新):写者使用
rcu_assign_pointer()原子地更新全局指针,使后续读者看到新数据。旧数据不会立即释放,而是等待所有在读读者退出后,通过synchronize_rcu()或call_rcu()延迟回收。
关键洞察:RCU 将“更新”与“回收”解耦。读者看到的要么是旧数据,要么是新数据,绝不会看到中间状态。这种“发布-订阅”语义保证了读路径的无锁安全。
1.3 核心优势
RCU 之所以成为内核中不可或缺的同步机制,源于以下四大核心优势:
| 优势 | 说明 | 典型收益 |
|---|---|---|
| 1. 极低读者开销 | 读者路径仅需编译器屏障,无原子操作、无锁竞争、无 cacheline bouncing | 读路径延迟降低 90% 以上 |
| 2. 读者-写者完全并发 | 写者更新时,读者仍可安全访问旧数据或新数据,无需互斥等待 | 写操作不阻塞读吞吐 |
| 3. 无死锁风险 | 读者不获取任何锁,写者使用非阻塞或可睡眠原语,彻底消除死锁可能性 | 系统稳定性显著提升 |
| 4. 可扩展性极强 | 随着 CPU 核心数增加,RCU 读路径性能线性扩展,不受锁竞争影响 | 适合 100+ 核的大规模系统 |
适用边界:RCU 并非万能。写者需要复制整个对象,如果对象体积巨大(如 MB 级结构体),复制开销会抵消读路径的收益。此外,RCU 要求读者临界区不能睡眠(除可睡眠 RCU 变体外),在实时性要求极高的场景需谨慎评估。
1.4 RCU 与经典锁机制的对比
为了更直观地理解 RCU 的定位,下面从多个维度对比 RCU 与内核中其他常见同步机制:
| 特性 | 自旋锁 (spinlock) | 读写锁 (rwlock) | 互斥锁 (mutex) | RCU |
|---|---|---|---|---|
| 读者是否阻塞写者 | 是(互斥) | 是(读时写阻塞) | 是(互斥) | 否(完全并发) |
| 读者是否可能睡眠 | 否 | 否 | 是 | 否(标准 RCU) |
| 写者是否需要等待读者 | 否(直接获取锁) | 是(等待读锁释放) | 否(直接获取锁) | 是(等待宽限期) |
| 内存开销 | 锁变量(4/8 字节) | 锁变量(4/8 字节) | 锁变量(40 字节) | 几乎为零(无额外锁变量) |
| 适用核心数 | 1~8 核 | 1~16 核 | 1~32 核 | 8~512+ 核 |
1.5 内核中的 RCU 应用场景
RCU 在 Linux 内核中无处不在,以下是一些典型应用:
- 路由表(FIB):网络子系统使用 RCU 保护路由缓存,读者(数据包转发)无锁查找,写者(路由更新)通过 RCU 安全替换。
- 文件系统超级块:VFS 层使用 RCU 保护 dentry 和 inode 的查找路径,显著提升文件系统元数据操作性能。
- 内核事件通知链:notifier chain 的遍历使用 RCU,避免在注册/注销通知时阻塞读者。
- per-CPU 数据结构:许多 per-CPU 计数器(如网络统计)使用 RCU 实现无锁读取。
- 动态内核模块加载:模块列表的遍历使用 RCU,允许在模块加载/卸载时并发查询。