什么是 RCU 锁

原始问题:什么是RCU锁 · 2026-08-05

什么是 RCU 锁

📋 问题定位

🎯 原理说明

1. 核心思想:把”同步”全部推给写者

传统读写锁中,读者必须与写者互斥;而 RCU 的读者完全不与写者直接同步:

传统读写锁:  读者 --互斥--> 写者(读者要加锁,写者要等所有读者)

RCU:         读者 ---无同步---> 写者
              读者只标记"我在读"(几乎零成本)
              写者发布新版本后,等所有在读读者退出,再回收旧版本

2. 三种视角理解 RCU(来自您提供的 LWN 系列文章)

视角 说明
读写锁替代品 读者不阻塞写者,读多写少场景无锁竞争
受限/批量引用计数 把”每个读者持有一份引用”合并为”所有读者共享一个宽限期计数”
穷人版垃圾回收器 旧对象延迟释放,由内核保证”没人再用时才回收”
存在性保证 只要在临界区内,被解引用的对象一定存活
等待事物完成 synchronize_rcu() 本质是”等待所有已开始的读操作结束”

3. 关键 API 家族

读者侧(快路径)——在 master 分支 include/linux/rcupdate.h 中可验证:

#ifdef CONFIG_PREEMPT_RCU
void __rcu_read_lock(void);   /* 可抢占变体:计数器加减 */
#else
static inline void __rcu_read_lock(void)
{
    preempt_disable();        /* 经典变体:仅关抢占,几乎零开销 */
}
#endif

写者侧(慢路径)——kernel/rcu/tree.c 中的 synchronize_rcu():

void synchronize_rcu(void)
{
    ...
    if (rcu_gp_is_expedited())
        synchronize_rcu_expedited();   /* 快速宽限期(IPI 轰击) */
    else
        synchronize_rcu_normal();      /* 常规宽限期:挂起一个回调 */
}
API 语义 开销
synchronize_rcu() 阻塞等待一个宽限期(所有在读读者退出) 毫秒级,可睡眠
synchronize_rcu_expedited() 通过 IPI 强制各 CPU 报告静止状态,加速宽限期 更快但干扰大
call_rcu(callback) 异步:宽限期结束后在软中断中回调,不阻塞写者 适合批量回收
rcu_assign_pointer(p, v) 发布新指针,带写侧屏障 屏障开销
rcu_dereference(p) 读者读取指针,带读侧屏障 屏障开销

4. 宽限期(Grace Period)的运作机制

宽限期是 RCU 的基石。内核通过静止状态(quiescent state,QS)判断读者是否退出:

写者调用 synchronize_rcu()
        │
        ▼
检测所有 CPU 是否都经历了 QS(内核态、用户态、空闲态切换点)
        │
        ├─ CPU 在 idle/dynticks 状态 → 天然是 QS(无需唤醒,节省能耗)
        ├─ CPU 在内核态但未在 RCU 读临界区 → 也是 QS
        └─ CPU 在 RCU 读临界区 → 必须等它退出
        │
        ▼
所有 CPU 报告 QS → 宽限期结束 → 旧对象可安全 kfree()

💡 适用场景

典型用法(读多写极少、指针替换型数据结构):

  1. 链表遍历:list_for_each_entry_rcu() 遍历 + 写者 list_replace_rcu() 替换节点,延迟回收(如路由表、struct file 的路径缓存)
  2. 指针型配置更新:rcu_assign_pointer 发布新配置,读者 rcu_dereference 读取
  3. 模块卸载/对象销毁:等待所有使用者退出后再释放(如网络协议栈的 inet_hashinfo、nf_conntrack 等大量使用)
  4. 内核内部基础设施:percpu_ref(块设备生命周期)、srcu(睡眠型读者,如文件系统)、rcu_sync(kernel/rcu/sync.c,percpu-rwsem 的快慢路径切换,正如源码注释所述 “RCU-based infrastructure for lightweight reader-writer locking”)

限制:

💡 收益效果

指标 传统读写锁 RCU
读者路径开销 原子操作 + 可能的自旋/睡眠 仅 preempt_disable()(或计数器加一)
读者并发度 可被写者阻塞/饿死 完全无阻塞,与写者并行执行有用工作
写者延迟 低(等锁即走) 高(必须等宽限期)
扩展性 读锁本身是共享缓存行,多核竞争 读者完全无共享写,天然可扩展
能耗 无关 空闲 CPU 可深度睡眠(dynticks),宽限期不打扰(2008 年文章的核心主题)

真实代价:

💡 应用前景

RCU 是 Linux 内核并发设计的”招牌”:从 2002 年的经典实现,到 2008 年可抢占变体与 dynticks 适配(您提供的第二篇材料即讲述这段历史:Paul 用 Promela/Spin 形式化验证宽限期与中断/NMI 嵌套的正确性),再到 2019 年 API 演化(rcu_read_lock_bh/rcu_read_lock_sched 合并、srcu 独立、Tasks RCU 用于 trampoline 卸载)。其”读者零开销、写者集中等待”的思想已被 percpu-refcount、hlist_bl、lwq 等机制广泛吸收。

对内核开发者而言,掌握 RCU 意味着:判断场景是否为”读多写极少 + 指针/链表替换”——是,则用 RCU 获得无锁读的性能;否(写频繁或读者需睡眠),则 SRCU 或传统锁更合适。普通运维层面,理解 RCU 有助于解读 rcu_sched 软中断的 stall 告警(INFO: rcu_sched detected stalls)——通常意味着某 CPU 长期处于内核态不经过静止状态,往往是死循环或长临界区导致宽限期无法结束。

参考资料:您提供的 6 篇 LWN 文章引言(Paul McKenney:《What is RCU, Really?》《Preemptable RCU》《The RCU API, 2010/2014/2019》系列);内核源码:include/linux/rcupdate.h(__rcu_read_lock 定义)、kernel/rcu/tree.c(synchronize_rcu 实现)、kernel/rcu/sync.c(rcu_sync 轻量读写锁设施)。

参考来源

⚠️ 免责声明

本站内容均由 AI 基于公开知识辅助生成,仅供学习参考,请勿直接引用作为依据。作者不对信息的准确性、完整性及适用性作保证,亦不对因使用本站内容产生的任何损失承担责任。