2. RCU核心思想:宽限期、发布-订阅与延迟回收

RCU(Read-Copy-Update)是一种针对读多写少场景设计的无锁同步机制。其核心思想在于:读者可以无锁访问共享数据,而写者通过“发布新版本”和“延迟回收旧版本”来保证一致性。理解RCU,必须掌握三个基石:宽限期(Grace Period)、发布-订阅机制(Publish-Subscribe)和延迟回收(Deferred Reclamation)。

2.1 宽限期(Grace Period)

宽限期是RCU最核心的时间概念。它定义了一个时间窗口:所有在“旧数据被替换”之前进入临界区的读者,都必须在该窗口内完成对旧数据的访问。只有当宽限期结束后,写者才能安全地回收旧数据的内存。

关键理解: 宽限期不是固定时长,而是由系统状态决定的逻辑时间段。它等待的是所有“老读者”退出,而非等待所有读者。

下图展示了宽限期的典型生命周期:

宽限期(Grace Period)生命周期 写者更新 宽限期开始 宽限期结束 读者A 读者B 读者C 宽限期等待所有在“写者更新”之前开始的读者(A、B)完成,但不等待之后开始的读者(C)

宽限期的核心规则可以总结为:

阶段 动作 说明
写者更新 写者创建新版本并发布 此时旧版本仍可能被读者引用
宽限期开始 记录当前所有活跃读者 系统标记一个时间点,开始等待
宽限期结束 所有老读者退出临界区 确认旧版本不再被任何读者访问
回收 写者安全释放旧版本内存 延迟回收操作在此刻执行
实践要点: 在Linux内核中,synchronize_rcu() 是显式等待宽限期的API。调用该函数的写者会被阻塞,直到所有在调用前进入RCU临界区的读者完成。

2.2 发布-订阅机制

发布-订阅机制是RCU实现数据一致性的关键手段。它确保:当写者发布一个新指针时,所有后续的读者都能看到这个新指针,而不会看到过时的中间状态。这依赖于内存屏障(Memory Barrier)来保证顺序性。

下图展示了发布-订阅的典型流程:

发布-订阅机制流程 写者(Writer) 分配新数据 初始化数据 smp_mb() 内存屏障 rcu_assign_pointer() 发布新指针 读者(Reader) rcu_dereference() 读取指针 隐含的依赖序屏障 关键保证:读者通过 rcu_dereference() 读取指针时, 一定能看到写者在发布前完成的所有初始化操作(数据一致性)

发布-订阅机制的核心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实现无锁读写的最终保障。其核心思想是:写者不立即释放旧数据的内存,而是推迟到所有可能引用该数据的读者都完成访问之后。这避免了传统锁机制中写者等待读者的问题。

延迟回收的完整流程如下:

  1. 写者创建新版本:分配新内存,复制旧数据并修改。
  2. 写者发布新版本:使用 rcu_assign_pointer() 将全局指针指向新数据。
  3. 写者注册回调:通过 call_rcu() 注册一个回收回调函数。
  4. 宽限期等待:系统自动检测所有老读者退出临界区。
  5. 回调执行:宽限期结束后,内核调用回调函数释放旧数据内存。

下图展示了延迟回收的完整生命周期:

延迟回收(Deferred Reclamation)生命周期 分配新数据 kmalloc() 复制并修改 memcpy() + 更新 发布新指针 rcu_assign_pointer() 注册回调 call_rcu() 宽限期(Grace Period) 等待所有在发布前进入临界区的读者退出 执行回调:释放旧数据 kfree(old_ptr) 或自定义回收函数 延迟回收的关键优势:读者从不等待写者,写者也不阻塞读者。 回收操作被推迟到绝对安全的时刻执行。

延迟回收的两种主要实现方式:

方式 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() 则展示了延迟回收的回调机制。