---
title: RCU (Read-Copy-Update)
url: https://doc.liz6.com/linux-kernel/05-synchronization-mechanisms/02-RCU
locale: zh
area: linux-kernel
tags:
- linux-kernel
- 同步机制
date: 2026-06-30
modified: 2026-06-30
description: '覆盖: RCU 核心机制 → grace period → rcu_read_lock/unlock → call_rcu/kfree_rcu → SRCU → Tasks RCU → RCU 调试 内核版本: 2.6 ~ 6.x'
---

# RCU (Read-Copy-Update)

> 覆盖: RCU 核心机制 → grace period → rcu_read_lock/unlock → call_rcu/kfree_rcu → SRCU → Tasks RCU → RCU 调试
> 内核版本: 2.6 ~ 6.x

## 概述

RCU 是一种"等所有人都走了再回收"的同步机制。它允许读者在不拿锁的情况下访问共享数据——读者只需要 `rcu_read_lock()` 和 `rcu_read_unlock()`（这两个操作在大多数配置下是零开销的）。写者复制数据、修改副本、然后等所有读者都完成旧数据的访问后回收旧数据。

RCU 是 Linux 中最独特的同步原语，大量用于核心数据结构：VFS (dentry)、网络 (路由表)、文件系统 (inode)、cgroup 等。

## 核心思想

```
读者 (无锁, 非阻塞):
  rcu_read_lock();
  p = rcu_dereference(ptr);  // 读指针
  // ... 使用 p ...
  rcu_read_unlock();

写者:
  old = ptr;
  new = copy(old);           // 复制
  modify(new);               // 修改副本
  rcu_assign_pointer(ptr, new);  // 原子切换指针
  synchronize_rcu();         // 等所有读者离开 (阻塞)
  // 或: call_rcu(&old->rcu, callback);  // 异步回收
```

关键约束：读者在 `rcu_read_lock/unlock` 之间**不能睡眠**（`rcu_read_lock` 关抢占，睡眠意味着其他任务可能在临界区中，破坏 RCU 的保证）。

## Grace Period

```
Grace Period = 从 rcu_assign_pointer 到所有之前存在的 reader 都完成的时间窗口

CPU 0 (writer):  CPU 1 (reader):        CPU 2 (reader):
  rcu_assign_...                   
  |               rcu_read_lock()       
  |               |--- reader 在旧数据上 ---|
  |                                          rcu_read_lock()
  |                                          |-- 在新数据上 --|
  |               rcu_read_unlock()
  |               // CPU 1 离开了
  synchronize_rcu() 返回
  // 此时: 任何 CPU 上的读者都看不到 old 了
  // → 安全回收
```

**关键不变量**: 一个 grace period 内，所有 CPU 都经历了一次上下文切换 (quiescent state)。RCU 利用调度器的 context switch 作为"读者离开了"的信号——这就是为什么 `rcu_read_lock` 必须关抢占。

## 实现

```c
// kernel/rcu/tree.c
// 主要实现: Tree RCU (可扩展到数千 CPU)
// 以树状结构组织 CPU 的 quiescent state 报告

// rcu_read_lock():
//   preempt_disable()  ← 大多情况下的全部内容
//   实际: 关抢占 = 告诉 RCU "我在临界区"

// rcu_read_unlock():
//   preempt_enable()   ← 如果这是最后一个 preempt_disable，RCU 记录 quiescent state
```

### call_rcu / kfree_rcu

```c
// include/linux/rcupdate.h
// 异步回收: 注册回调, 在 grace period 后调用
call_rcu(&p->rcu_head, my_callback);
// → RCU 在 grace period 后调用 my_callback(p)

// 常见模式: 释放对象
kfree_rcu(p, rcu_head);  // 等价于 call_rcu(..., (void(*))kfree)
```

### synchronize_rcu

```c
// 同步等待: 阻塞直到所有现有 reader 离开
synchronize_rcu();  // 可能睡眠数百毫秒!
// 不能从中断上下文调用
```

---

## RCU 变体

| 变体 | 读者阻塞类型 | 适用场景 |
|------|-------------|---------|
| Classic RCU | 关抢占 (`rcu_read_lock`) | 大多内核代码 |
| SRCU (Sleepable RCU) | 显式 `srcu_read_lock` (可睡眠) | 文件系统, VFS |
| Tasks RCU | 关抢占 (trace 专用) | ftrace, kprobes |

### SRCU

```c
// include/linux/srcu.h
// 读者可以睡眠 (适合在拿 mutex 的场景下保护 RCU 数据)
srcu_read_lock(&ss);
// ... 可以睡眠 ...
srcu_read_unlock(&ss, idx);

// 写者:
synchronize_srcu(&ss);
// 开销比 synchronize_rcu 高
```

---

## 调试

```bash
# RCU 统计
cat /sys/kernel/debug/rcu/rcu_expedited  # 是否使用了快速 grace period
cat /sys/kernel/debug/rcu/rcugp          # grace period 状态

# RCU stall 检测 (CPU 在 rcu_read_lock 中太久)
#   → 内核打印 "INFO: rcu_sched detected stalls on CPUs/tasks"
#   → 说明某 CPU 长时间没离开 reader → 可能是死循环或长时间关抢占

# 触发 stall 检测的超时
cat /sys/module/rcupdate/parameters/rcu_cpu_stall_timeout
```

---

## 参考

- **内核文档**: `Documentation/RCU/` 目录 (最全面的 RCU 文档)
- **LWN**: "What is RCU?" 系列, "The RCU API"
- **源码**: `kernel/rcu/tree.c`, `include/linux/rcupdate.h`, `kernel/rcu/srcutree.c`

---

*关键词: RCU, grace period, rcu_read_lock, call_rcu, kfree_rcu, SRCU, quiescent state, rcu_dereference*
