RCU (Read-Copy Update, 读 - 复制 - 更新) 是 Linux 内核中一种专为 读多写少 场景优化的高性能同步机制。它的核心优势是:读操作完全无锁、零开销,写操作通过延迟回收保证安全,极大提升多核并发效率。
一、核心原理
RCU 将更新操作拆分为 移除(替换) 和 回收(释放) 两个阶段,并引入 宽限期(Grace Period) 确保安全。
读端(Reader)
- 无锁访问 :读者通过
rcu_read_lock()和rcu_read_unlock()标记临界区,无需获取锁,直接访问数据。 - 可见性 :读者要么看到旧版本,要么看到新版本,永远不会读到中间状态。
写端(Writer)
- Copy(复制):不直接修改原数据,而是创建一份副本并修改。
- Modify(修改):在副本上完成所有更新。
- Assign(替换) :用原子操作(
rcu_assign_pointer)将全局指针指向新副本。此后,新读者只能看到新数据。 - Wait(等待) :进入宽限期,等待所有正在访问旧数据的读者完成读取。
- Reclaim(回收):宽限期结束后,安全释放旧数据。
关键概念:宽限期 (Grace Period)
- 定义:从指针替换完成,到所有 CPU 都退出读临界区的时间窗口。
- 作用:确保旧数据在没有任何读者引用时才被释放,避免悬空指针(Use-After-Free)。
- 静默状态(Quiescent State):内核将上下文切换、进入用户态、空闲循环,视为 CPU 已退出读临界区的标志。
二、适配PREEMPT_RT的 RCU 配置
在主线 Linux 内核中,RCU 的大量处理工作运行在软中断上下文,此过程抢占是关闭的,会造成显著延迟、损害实时性。这篇文章总结了一些相关的优化设置:RCU Configuration for Real-Time Systems, 简单总结一下就是以下的一些设定:
- RCU 回调卸载(RCU Callback Offloading)
- RCU 优先级提升(RCU Priority Boosting)
- RCU 加速宽限期(Expedited RCU Grace Periods)
- 可抢占 RCU(Preemptible RCU)
三、 可抢占 RCU(Preemptible RCU)
对于RCU的一些优化操作,我想着重介绍一下Preemptible RCU的实现机制。
c
#ifdef CONFIG_PREEMPT_RCU
static void rcu_preempt_read_enter(void)
{
WRITE_ONCE(current->rcu_read_lock_nesting, READ_ONCE(current->rcu_read_lock_nesting) + 1);
}
/*
* Preemptible RCU implementation for rcu_read_lock().
* Just increment ->rcu_read_lock_nesting, shared state will be updated
* if we block.
*/
void __rcu_read_lock(void)
{
rcu_preempt_read_enter();
if (IS_ENABLED(CONFIG_PROVE_LOCKING))
WARN_ON_ONCE(rcu_preempt_depth() > RCU_NEST_PMAX);
if (IS_ENABLED(CONFIG_RCU_STRICT_GRACE_PERIOD) && rcu_state.gp_kthread)
WRITE_ONCE(current->rcu_read_unlock_special.b.need_qs, true);
barrier(); /* critical section after entry code. */
}
#else /* #ifdef CONFIG_PREEMPT_RCU */
static inline void __rcu_read_lock(void)
{
preempt_disable();
}
#endif
通过上述代码对__rcu_read_lock的实现不难看出区别:
- Preemptible RCU对比普通RCU, 将
preempt_disable替换成current->rcu_read_lock_nesting的嵌套层级计数。 - 普通RCU判断宽限期结束通过判断当前是否处于禁止抢占 的状态,而PREEMPT_RCU则通过判断嵌套层级 来确认是否可以回收旧数据。通过标记临界区方法的改变,使
rcu_read_lock期间无须禁用抢占。