---
title: RCU (Read-Copy-Update)
url: https://doc.liz6.com/ja/linux-kernel/05-synchronization-mechanisms/02-RCU
locale: ja
area: linux-kernel
tags:
- linux-kernel
- synchronization-mechanisms
date: 2026-06-30
modified: 2026-07-16
description: 'カバー範囲: RCUのコアメカニズム → グレース期間 → rcu_read_lock/unlock → call_rcu/kfree_rcu → SRCU → Tasks RCU → RCUデバッグ カーネルバージョン: 2.6 ~ 6.x'
---

# RCU (Read-Copy-Update)

> カバー範囲: RCUのコアメカニズム → グレース期間 → 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の保証が壊れます)。

## グレース期間

```
グレース期間 = rcu_assign_pointer から、それ以前に存在したすべての読者が完了するまでの時間窓

CPU 0 (書き込み側):  CPU 1 (読者):        CPU 2 (読者):
  rcu_assign_...                   
  |               rcu_read_lock()       
  |               |--- 読者が古いデータ上で ---|
  |                                          rcu_read_lock()
  |                                          |-- 新しいデータ上で --|
  |               rcu_read_unlock()
  |               // CPU 1 が退出
  synchronize_rcu() が返す
  // この時点: どのCPUの読者も old を見ることができない
  // → 安全な回収
```

**重要な不変条件**: グレース期間中、すべてのCPUは少なくとも1回のコンテキストスイッチ（静穏状態）を経験します。RCUはスケジューラのコンテキストスイッチを「読者が退出した」というシグナルとして利用します。これが `rcu_read_lock` がプリエンプションを無効化する理由です。

## 実装

```c
// kernel/rcu/tree.c
// 主要な実装: Tree RCU (数千CPUまで拡張可能)
// CPUの静穏状態レポートを木構造で整理

// rcu_read_lock():
//   preempt_disable()  ← 多くの場合、これがすべて
//   実際: プリエンプションを無効化 = RCUに「クリティカルセクションにいる」と伝える

// rcu_read_unlock():
//   preempt_enable()   ← これが最後の preempt_disable の場合、RCUは静穏状態を記録
```

### call_rcu / kfree_rcu

```c
// include/linux/rcupdate.h
// 非同期回収: コールバックを登録し、グレース期間後に呼び出す
call_rcu(&p->rcu_head, my_callback);
// → RCUはグレース期間後に my_callback(p) を呼び出す

// 一般的なパターン: オブジェクトの解放
kfree_rcu(p, rcu_head);  // call_rcu(..., (void(*))kfree) と同等
```

### synchronize_rcu

```c
// 同期待ち: すべての既存の読者が退出するまでブロック
synchronize_rcu();  // 数百ミリ秒スリープする可能性がある!
// 割り込みコンテキストからは呼び出せない
```

---

## RCUのバリエーション

| バリエーション | 読者のブロックタイプ | 適用シーン |
|------|-------------|---------|
| Classic RCU | プリエンプション無効化 (`rcu_read_lock`) | ほとんどのカーネルコード |
| SRCU (Sleepable RCU) | 明示的 `srcu_read_lock` (スリープ可能) | ファイルシステム, VFS |
| Tasks RCU | プリエンプション無効化 (トレース専用) | 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  # 高速グレース期間が使用されたか
cat /sys/kernel/debug/rcu/rcugp          # グレース期間の状態

# RCUストール検出 (CPUが rcu_read_lock 内で長時間経過)
#   → カーネルが "INFO: rcu_sched detected stalls on CPUs/tasks" を出力
#   → 特定のCPUが読者から長時間退出していない = ループまたは長時間のプリエンプション無効化

# ストール検出のタイムアウト
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, グレース期間, rcu_read_lock, call_rcu, kfree_rcu, SRCU, quiescent state, rcu_dereference*
