このページの目次
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 がプリエンプションを無効化する理由です。
実装
// 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
// include/linux/rcupdate.h
// 非同期回収: コールバックを登録し、グレース期間後に呼び出す
;
// → RCUはグレース期間後に my_callback(p) を呼び出す
// 一般的なパターン: オブジェクトの解放
; // call_rcu(..., (void(*))kfree) と同等
synchronize_rcu
// 同期待ち: すべての既存の読者が退出するまでブロック
; // 数百ミリ秒スリープする可能性がある!
// 割り込みコンテキストからは呼び出せない
RCUのバリエーション
| バリエーション | 読者のブロックタイプ | 適用シーン |
|---|---|---|
| Classic RCU | プリエンプション無効化 (rcu_read_lock) | ほとんどのカーネルコード |
| SRCU (Sleepable RCU) | 明示的 srcu_read_lock (スリープ可能) | ファイルシステム, VFS |
| Tasks RCU | プリエンプション無効化 (トレース専用) | ftrace, kprobes |
SRCU
// include/linux/srcu.h
// 読者はスリープ可能 (mutexを取得する状況でRCUデータを保護するのに適している)
;
// ... スリープ可能 ...
;
// 書き込み側:
;
// synchronize_rcu よりオーバーヘッドが高い
デバッグ
# RCU統計
# RCUストール検出 (CPUが rcu_read_lock 内で長時間経過)
# → カーネルが "INFO: rcu_sched detected stalls on CPUs/tasks" を出力
# → 特定のCPUが読者から長時間退出していない = ループまたは長時間のプリエンプション無効化
# ストール検出のタイムアウト
参考
- カーネルドキュメント:
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