1. RCU 基础概念:定义、设计哲学与核心优势

1.1 什么是 RCU?

RCU(Read-Copy-Update) 是 Linux 内核中一种高性能的同步机制,专为读多写少的场景设计。它允许读者在几乎不加锁的情况下并发访问共享数据,而写者通过“复制-修改-延迟回收”的方式完成更新,从而大幅降低读路径的开销。

核心定义:RCU 是一种基于“宽限期(Grace Period)”的同步技术,读者无需显式加锁即可安全访问共享对象,写者通过发布更新并等待所有在读读者退出后,再安全回收旧数据。
维度 传统读写锁 (rwlock) RCU
读者开销 原子操作 + 内存屏障(高) 几乎零开销(仅编译器屏障)
写者开销 直接修改(低) 复制 + 替换指针 + 延迟回收(中高)
并发度 读者-写者互斥 读者与写者完全并发
适用场景 读写比例均衡 读远多于写(99%+ 读)

1.2 设计哲学:三阶段模型

RCU 的设计遵循一个清晰的三阶段流水线,每个阶段都有明确的语义和操作原语:

  1. Read(读):读者通过 rcu_read_lock() / rcu_read_unlock() 标记临界区,直接读取指针指向的数据,无需任何原子操作。
  2. Copy(复制):写者分配新内存,将旧数据复制到新内存中,并在新副本上执行修改。
  3. Update(更新):写者使用 rcu_assign_pointer() 原子地更新全局指针,使后续读者看到新数据。旧数据不会立即释放,而是等待所有在读读者退出后,通过 synchronize_rcu() 或 call_rcu() 延迟回收。
关键洞察:RCU 将“更新”与“回收”解耦。读者看到的要么是旧数据,要么是新数据,绝不会看到中间状态。这种“发布-订阅”语义保证了读路径的无锁安全。
RCU 三阶段模型:Read → Copy → Update 阶段 1:Read rcu_read_lock() 直接读取指针 阶段 2:Copy 分配新内存 复制旧数据并修改 阶段 3:Update rcu_assign_pointer() 延迟回收旧数据 宽限期(Grace Period)—— 安全回收的保障 • 写者更新指针后,不能立即释放旧数据,必须等待所有在读读者退出临界区。 • synchronize_rcu():阻塞等待宽限期结束,然后回收。 • call_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+ 核
RCU 在同步机制中的定位:读多写少场景的最优解 写频率 → 低(1% 写) 高(50%+ 写) 读并发度 → 高 低 RCU 读多写少 最优选择 rwlock 读写均衡 spinlock/mutex 写密集场景

1.5 内核中的 RCU 应用场景

RCU 在 Linux 内核中无处不在,以下是一些典型应用:

  • 路由表(FIB):网络子系统使用 RCU 保护路由缓存,读者(数据包转发)无锁查找,写者(路由更新)通过 RCU 安全替换。
  • 文件系统超级块:VFS 层使用 RCU 保护 dentry 和 inode 的查找路径,显著提升文件系统元数据操作性能。
  • 内核事件通知链:notifier chain 的遍历使用 RCU,避免在注册/注销通知时阻塞读者。
  • per-CPU 数据结构:许多 per-CPU 计数器(如网络统计)使用 RCU 实现无锁读取。
  • 动态内核模块加载:模块列表的遍历使用 RCU,允许在模块加载/卸载时并发查询。