1. RCU 概述:基本概念、历史背景与内核地位
1.1 什么是 RCU?
RCU(Read-Copy-Update)是一种同步机制,它允许多个读者(Reader)与一个写者(Writer)并发访问共享数据,而读者几乎不需要任何锁开销。其核心思想是:读者可以无锁读取,写者通过“复制—修改—延迟回收”的方式更新数据,并等待所有正在进行的读操作完成后才释放旧版本。
RCU 的三大核心阶段:
- Read(读):读者通过 rcu_dereference() 获取指针,无需加锁,仅需内存屏障保证可见性。
- Copy(复制):写者分配新内存,复制旧数据,在新副本上执行修改。
- Update(更新):写者使用 rcu_assign_pointer() 原子替换指针,旧版本进入“宽限期(Grace Period)”等待回收。
1.2 历史背景:从 DYNIX/ptx 到 Linux 主线
| 时间 | 事件 | 贡献者 |
|---|---|---|
| 1990 年代初 | RCU 概念诞生于 DYNIX/ptx 操作系统 | Richard Rashid 团队 |
| 2002 年 | RCU 首次进入 Linux 内核(2.5.43) | Dipankar Sarma、Paul McKenney |
| 2006 年 | Linux 2.6.17 引入可抢占 RCU(Preemptible RCU) | Paul McKenney |
| 2012 年 | Linux 3.4 引入 Tree RCU,替代旧式 Classic RCU | Paul McKenney |
| 2015 年 | Linux 4.4 引入 Tiny RCU(用于嵌入式系统) | Paul McKenney |
| 2020 年至今 | RCU 持续优化,支持 NUMA 感知、低延迟变体 | 内核社区 |
关键人物:Paul McKenney 是 RCU 在 Linux 内核中的主要推动者和维护者,他撰写了大量 RCU 相关论文和内核文档,被誉为“RCU 之父”。
1.3 RCU 在 Linux 内核中的地位
RCU 已成为 Linux 内核中使用最广泛的同步原语之一,其应用范围覆盖从核心调度器到文件系统、网络协议栈、内存管理等几乎所有子系统。
1.4 RCU 与读写锁的对比
| 特性 | 读写锁(rwlock) | RCU |
|---|---|---|
| 读者开销 | 原子操作 + 内存屏障(约 20-40 ns) | 几乎为零(仅需编译器屏障) |
| 写者开销 | 获取写锁(可能阻塞) | 复制 + 指针替换 + 宽限期等待 |
| 读者并发度 | 多读者可同时读,但受缓存行 bouncing 影响 | 完全无锁,无限并发 |
| 写者饥饿 | 可能被读者持续抢占 | 写者不会被读者阻塞 |
| 适用场景 | 读写比例均衡,写操作频繁 | 读远多于写,写操作可容忍延迟 |
| 内存开销 | 锁变量(通常 4-8 字节) | 需要额外副本和宽限期跟踪结构 |
性能陷阱:RCU 并非万能。当写操作非常频繁(如每秒数万次更新)时,RCU 的复制开销和宽限期等待可能超过读写锁。RCU 最适合读多写少的场景,典型比例建议读:写 > 100:1。
1.5 RCU 的核心数据结构
Linux 内核中 RCU 的实现基于以下关键数据结构:
- rcu_state:全局 RCU 状态,管理宽限期和 CPU 位图。
- rcu_node:树形 RCU 节点,用于 NUMA 感知的宽限期传播。
- rcu_data:每个 CPU 的 RCU 数据,记录该 CPU 的 quiescent state。
- callback list:每个 CPU 上待回收的 RCU 回调函数链表。
// Linux 内核 RCU 核心数据结构(简化版)
struct rcu_state {
struct rcu_node node[NUM_RCU_NODES]; // 树形节点
struct rcu_data __percpu *rda; // 每 CPU 数据
unsigned long gpnum; // 当前宽限期编号
unsigned long completed; // 已完成宽限期编号
};
struct rcu_node {
spinlock_t lock; // 节点锁
unsigned long gpnum; // 节点级宽限期编号
unsigned long completed; // 节点级已完成编号
unsigned long qsmask; // 子节点 quiescent state 位图
};
struct rcu_data {
unsigned long gpnum; // 该 CPU 看到的宽限期编号
unsigned long completed; // 该 CPU 完成的宽限期编号
struct rcu_head *nxtlist; // 待处理回调链表
bool cpu_no_qs; // 该 CPU 是否尚未报告 quiescent state
};
1.6 RCU 的变体与选择
| RCU 变体 | 引入版本 | 特点 | 适用场景 |
|---|---|---|---|
| Classic RCU | 2.5.43 | 原始实现,全局宽限期 | 已废弃,仅用于兼容 |
| Tree RCU | 3.4 | 树形结构,NUMA 感知,可扩展 | 通用服务器、大型 SMP 系统 |
| Tiny RCU | 4.4 | 极简实现,代码量小 | 嵌入式系统、单核设备 |
| Preemptible RCU | 2.6.17 | 允许读者被抢占 | 实时内核、低延迟需求 |
| Sleepable RCU (SRCU) | 2.6.19 | 读者可在临界区睡眠 | 文件系统、设备驱动 |