2. RCU核心思想:宽限期、发布-订阅与延迟回收
RCU(Read-Copy-Update)是一种针对读多写少场景设计的无锁同步机制。其核心思想在于:读者可以无锁访问共享数据,而写者通过“发布新版本”和“延迟回收旧版本”来保证一致性。理解RCU,必须掌握三个基石:宽限期(Grace Period)、发布-订阅机制(Publish-Subscribe)和延迟回收(Deferred Reclamation)。
2.1 宽限期(Grace Period)
宽限期是RCU最核心的时间概念。它定义了一个时间窗口:所有在“旧数据被替换”之前进入临界区的读者,都必须在该窗口内完成对旧数据的访问。只有当宽限期结束后,写者才能安全地回收旧数据的内存。
关键理解: 宽限期不是固定时长,而是由系统状态决定的逻辑时间段。它等待的是所有“老读者”退出,而非等待所有读者。
下图展示了宽限期的典型生命周期:
宽限期的核心规则可以总结为:
| 阶段 | 动作 | 说明 |
|---|---|---|
| 写者更新 | 写者创建新版本并发布 | 此时旧版本仍可能被读者引用 |
| 宽限期开始 | 记录当前所有活跃读者 | 系统标记一个时间点,开始等待 |
| 宽限期结束 | 所有老读者退出临界区 | 确认旧版本不再被任何读者访问 |
| 回收 | 写者安全释放旧版本内存 | 延迟回收操作在此刻执行 |
实践要点: 在Linux内核中,
synchronize_rcu() 是显式等待宽限期的API。调用该函数的写者会被阻塞,直到所有在调用前进入RCU临界区的读者完成。
2.2 发布-订阅机制
发布-订阅机制是RCU实现数据一致性的关键手段。它确保:当写者发布一个新指针时,所有后续的读者都能看到这个新指针,而不会看到过时的中间状态。这依赖于内存屏障(Memory Barrier)来保证顺序性。
下图展示了发布-订阅的典型流程:
发布-订阅机制的核心API对:
| 角色 | API | 语义 | 屏障类型 |
|---|---|---|---|
| 写者 | rcu_assign_pointer(p, v) |
发布新指针,确保之前的数据初始化完成 | 写内存屏障(smp_wmb) |
| 读者 | rcu_dereference(p) |
订阅指针,确保读取到完整初始化的数据 | 依赖序屏障(smp_read_barrier_depends) |
常见误区: 不要使用普通的指针赋值(如
p = new_data)替代 rcu_assign_pointer()。在弱内存序架构(如ARM、PowerPC)上,普通赋值可能导致读者看到未完全初始化的数据,引发数据损坏。
2.3 延迟回收(Deferred Reclamation)
延迟回收是RCU实现无锁读写的最终保障。其核心思想是:写者不立即释放旧数据的内存,而是推迟到所有可能引用该数据的读者都完成访问之后。这避免了传统锁机制中写者等待读者的问题。
延迟回收的完整流程如下:
- 写者创建新版本:分配新内存,复制旧数据并修改。
- 写者发布新版本:使用
rcu_assign_pointer()将全局指针指向新数据。 - 写者注册回调:通过
call_rcu()注册一个回收回调函数。 - 宽限期等待:系统自动检测所有老读者退出临界区。
- 回调执行:宽限期结束后,内核调用回调函数释放旧数据内存。
下图展示了延迟回收的完整生命周期:
延迟回收的两种主要实现方式:
| 方式 | API | 特点 | 适用场景 |
|---|---|---|---|
| 同步等待 | synchronize_rcu() |
阻塞写者直到宽限期结束,简单直接 | 写者可以接受阻塞的场景 |
| 异步回调 | call_rcu() |
注册回调后立即返回,不阻塞写者 | 高性能写者、实时系统 |
性能对比: 在典型的读多写少场景中,RCU的读者几乎零开销(仅需一个
rcu_read_lock() 和 rcu_read_unlock(),通常编译为屏障指令或空操作),而写者通过延迟回收避免了与读者的直接竞争。这使得RCU在读者占99%以上的系统中,性能远超读写锁(rwlock)。
2.4 三者关系总结
宽限期、发布-订阅机制和延迟回收三者共同构成了RCU的完整理论体系:
- 发布-订阅机制 保证了数据的一致性——读者看到的是完整初始化的新数据。
- 宽限期 提供了时间上的安全边界——确保旧数据不再被引用。
- 延迟回收 实现了内存的安全释放——在宽限期结束后才回收旧数据。
这三者的协作关系可以用下表概括:
| 机制 | 解决的问题 | 核心API | 对读者的影响 | 对写者的影响 |
|---|---|---|---|---|
| 发布-订阅 | 数据可见性 | rcu_assign_pointer() / rcu_dereference() |
无额外开销 | 需要内存屏障 |
| 宽限期 | 时间安全边界 | synchronize_rcu() / rcu_read_lock() |
无阻塞 | 可能阻塞(同步)或异步 |
| 延迟回收 | 内存安全释放 | call_rcu() / kfree_rcu() |
无影响 | 回收操作被推迟 |
学习建议: 在阅读Linux内核源码时,可以重点关注
kernel/rcu/ 目录下的 tree.c 和 update.c 文件。其中 synchronize_rcu() 的实现完整展示了宽限期的检测逻辑,而 call_rcu() 则展示了延迟回收的回调机制。