1. RCU 概述:基本概念、历史背景与内核地位

1.1 什么是 RCU?

RCU(Read-Copy-Update)是一种同步机制,它允许多个读者(Reader)与一个写者(Writer)并发访问共享数据,而读者几乎不需要任何锁开销。其核心思想是:读者可以无锁读取,写者通过“复制—修改—延迟回收”的方式更新数据,并等待所有正在进行的读操作完成后才释放旧版本。

RCU 的三大核心阶段:
  1. Read(读):读者通过 rcu_dereference() 获取指针,无需加锁,仅需内存屏障保证可见性。
  2. Copy(复制):写者分配新内存,复制旧数据,在新副本上执行修改。
  3. Update(更新):写者使用 rcu_assign_pointer() 原子替换指针,旧版本进入“宽限期(Grace Period)”等待回收。
RCU 核心流程:Read → Copy → Update → Reclaim 阶段 1:Read rcu_dereference() 无锁读取指针 阶段 2:Copy 分配新内存 复制旧数据 阶段 3:Update rcu_assign_pointer() 原子替换指针 Reclaim 同步回收 宽限期(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 内核中使用最广泛的同步原语之一,其应用范围覆盖从核心调度器到文件系统、网络协议栈、内存管理等几乎所有子系统。

RCU 在 Linux 内核中的主要应用领域 RCU 同步机制 进程调度器 cgroup、负载均衡 内存管理 SLUB 分配器、MMU notifier 网络协议栈 路由表、Netfilter 文件系统 dcache、inode 缓存 设备驱动 PCI、USB 热插拔 安全模块 LSM、SELinux

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 读者可在临界区睡眠 文件系统、设备驱动