Linux RCU (Read-Copy-Update) 机制深度分析
1. 快速概览
RCU(Read-Copy-Update)是 Linux 内核中针对"读多写少"场景设计的高性能并发原语。核心思想很直接:
- 读者可以在无锁的情况下并发读取共享数据;
- 更新通过发布新版本并延迟回收旧版本来保证安全;
- 回收旧对象必须等待一个宽限期 (Grace Period),确保所有可能正在访问旧对象的读者完成。
这种设计带来极低的读延迟和良好的可扩展性,适合网络协议、缓存层、驱动等读密集型内核子系统。
2. 核心原理
2.1 三个基本要素
-
发布/订阅(Publish/Subscribe)
更新者发布新版本,读者在自己的临界区内直接读取当前版本指针,不阻塞。
-
宽限期(Grace Period)
系统需要确认所有可能的读者都已离开旧版本的临界区,才可以回收旧数据。
-
延迟回调(Deferred Callback)
回收工作通过回调(
call_rcu()
)登记,等到宽限期结束再执行实际释放。
这些要点合起来,就是 RCU 能做到读无锁、写延迟回收的根本原因。
rcu_read_lock rcu_read_lock rcu_read_lock rcu_read_unlock Update Data Yes No Reader Thread 1 RCU Critical Section Reader Thread 2 Reader Thread 3 Exit Critical Section Writer Thread call_rcu Register Callback Wait Grace Period Execute Callback Free Old Data Grace Period Detection All CPUs passed QS? GP Complete Wait for QS
2.2 常见的 RCU 变体(选型要点)
- TREE_RCU(CONFIG_TREE_RCU):面向多核、大规模系统,使用分层节点做可扩展聚合和跟踪。
- TINY_RCU(CONFIG_TINY_RCU):面向单核或资源受限设备,实现精简、占用少。
- PREEMPT_RCU(CONFIG_PREEMPT_RCU):支持内核抢占,适用于实时/低延迟需求的系统。
选型原则:系统规模、是否需要抢占、内存/CPU 受限程度 ------ 根据这些条件选合适的 RCU 实现。
3. 关键数据结构
下面列出内核中常见的 RCU 数据结构,阅读时把重点放在"它们在 GP(宽限期)与回调管理中承担的角色"。
3.1 struct rcu_state
(全局状态)
该结构管理 RCU 的树状节点、宽限期序列和用于调度 GP 的内核线程等。关键字段包括 CPU 数量、GP 序列号、GP 内核线程、以及屏障和快速 GP 的相关控制字段。
c
struct rcu_state {
struct rcu_node node[NUM_RCU_NODES]; /* 分层树状结构 */
struct rcu_node *level[RCU_NUM_LVLS + 1]; /* 层级指针数组 */
int ncpus; /* CPU 数量 */
int n_online_cpus; /* 在线 CPU 数量 */
/* Grace Period 管理字段 */
unsigned long gp_seq; /* Grace Period 序列号 */
unsigned long gp_max; /* 最大 GP 持续时间 */
struct task_struct *gp_kthread; /* GP 内核线程 */
struct swait_queue_head gp_wq; /* GP 等待队列 */
short gp_flags; /* GP 命令标志 */
short gp_state; /* GP 状态 */
/* 屏障同步字段 */
struct mutex barrier_mutex; /* 屏障互斥锁 */
atomic_t barrier_cpu_count; /* 等待的 CPU 数量 */
struct completion barrier_completion; /* 屏障完成信号 */
/* 快速 Grace Period 字段 */
struct mutex exp_mutex; /* 快速 GP 互斥锁 */
unsigned long expedited_sequence; /* 快速 GP 序列号 */
atomic_t expedited_need_qs; /* 需要 QS 的 CPU 数量 */
};
3.2 struct rcu_node
(树节点)
rcu_node
用于把 CPU 分组为层级树,便于在大系统中聚合各组的 QS(quiescent state)信息。它也保存与回调、阻塞任务及优先级提升相关的管理结构。
c
struct rcu_node {
raw_spinlock_t lock; /* 节点锁 */
unsigned long gp_seq; /* Grace Period 序列号 */
unsigned long gp_seq_needed; /* 需要的 GP 序列号 */
unsigned long completedqs; /* 完成的 QS 掩码 */
unsigned long qsmask; /* QS 掩码 */
unsigned long rcu_gp_init_mask; /* GP 初始化掩码 */
/* 树状结构字段 */
u8 level; /* 树中的层级 */
u8 grplo; /* 组内最低 CPU */
u8 grphi; /* 组内最高 CPU */
u8 grpnum; /* 组号 */
struct rcu_node *parent; /* 父节点 */
/* 阻塞任务管理 */
struct list_head blkd_tasks; /* 阻塞任务列表 */
struct list_head *gp_tasks; /* GP 任务指针 */
struct list_head *exp_tasks; /* 快速 GP 任务指针 */
/* 优先级提升 */
struct task_struct *boost_kthread_task; /* 提升内核线程 */
unsigned long boost_time; /* 提升时间 */
struct mutex boost_mutex; /* 提升互斥锁 */
};
3.3 struct rcu_data
(每 CPU 的 RCU 数据)
每个 CPU 保留一份 rcu_data
,用于跟踪该 CPU 的 QS、回调队列(segmented callback list)、以及与无回调(no-CBs)相关的锁和统计信息。
c
struct rcu_data {
/* Grace Period 相关 */
unsigned long gp_seq; /* GP 序列号 */
unsigned long gp_seq_needed; /* 需要的 GP 序列号 */
union rcu_noqs rcu_qs_ctr_snap; /* QS 计数器快照 */
/* 回调管理 */
struct rcu_segcblist cblist; /* 分段回调列表 */
long qlen_last_fqs_check; /* 上次 FQS 检查时的队列长度 */
/* CPU 状态 */
bool rcu_urgent_qs; /* 紧急 QS 标志 */
bool rcu_forced_tick; /* 强制时钟中断标志 */
bool rcu_forced_tick_exp; /* 快速 GP 强制时钟标志 */
/* 节点关联 */
struct rcu_node *mynode; /* 关联的 rcu_node */
unsigned long grpmask; /* 组内掩码 */
/* 无回调 CPU 支持 */
raw_spinlock_t nocb_lock; /* 无回调锁 */
atomic_t nocb_lock_contended; /* 锁竞争计数 */
/* 统计信息 */
unsigned long n_cbs_invoked; /* 调用的回调数量 */
unsigned long n_force_qs_snap; /* 强制 QS 快照 */
long blimit; /* 批处理限制 */
};
4. 分段回调列表(segmented callback list)
RCU 将回调按段保存(NEXT、WAIT、DONE 等),便于在不同的宽限期阶段进行移动和处理,从而避免回调在单一链表中长期堆积,利于批量执行与延迟释放。
c
struct rcu_segcblist {
struct rcu_head *head; /* 回调列表头 */
struct rcu_head **tails[RCU_CBLIST_NSEGS]; /* 分段尾指针数组 */
unsigned long gp_seq[RCU_CBLIST_NSEGS]; /* 各段的 GP 序列号 */
long len; /* 总长度 */
long seglen[RCU_CBLIST_NSEGS]; /* 各段长度 */
u8 flags; /* 标志位 */
};
5. RCU 层次与回调流(图示保留原样)
5.1 树状层次(CPU → rcu_node → root)
Grace Period Flow Initialize Tree GP Start Wait for QS from all CPUs GP Complete Execute Callbacks Root rcu_node Level 0 rcu_node Level 1-1 rcu_node Level 1-2 rcu_node Level 1-3 CPU 0-7 rcu_data CPU 8-15 rcu_data CPU 16-23 rcu_data CPU 24-31 rcu_data CPU 32-39 rcu_data CPU 40-47 rcu_data
5.2 回调分段管理(阶段迁移)
New Callbacks NEXT Segment NEXT_TAIL Segment WAIT Segment WAIT_TAIL Segment DONE Segment Execute & Free Grace Period N Grace Period N+1 Move WAIT to DONE
(图说明:分段移动实现了"等待至少两个 GP 以后才真正释放"的策略,从而避免竞争窗口内的回收风险。)
6. API 快速参考(读者 / 更新者 / 快速 GP)
6.1 读者 API(必须尽量短的临界区)
rcu_read_lock()
------ 进入 RCU 读临界区rcu_read_unlock()
------ 退出读临界区rcu_dereference()
------ 安全读取 RCU 保护的指针(带内存屏障/读取语义)
注意:读临界区不可睡眠(除非使用相应的可睡眠 RCU API),要尽量短以加快 GP 完成。
6.2 更新者 API(发布与回收)
rcu_assign_pointer()
------ 原子发布新指针(写者使用)call_rcu()
------ 异步登记回调,在 GP 完成后执行释放synchronize_rcu()
------ 阻塞直到当前 GP 完成(同步等待,代价较大)
6.3 快速(Expedited)GP
call_rcu_expedited()
/synchronize_rcu_expedited()
------ 用于紧急情形,强制快速推进 GP,但开销更高,应该谨慎使用。
7. 与其他同步机制比较(实用角度)
同步机制 | 读性能 | 写性能 | 内存开销 | 典型适用场景 |
---|---|---|---|---|
RWLock | 中等 | 中等 | 低 | 读写均衡 |
Spinlock | 高(短临界区) | 高 | 低 | 短临界区、高一致性需求 |
Mutex | 低 | 中等 | 低 | 可能睡眠的长期操作 |
RCU | 极高(读) | 中等(写) | 中等 | 读多写少、低延迟读 |
Synchronization Methods Reader Performance Writer Performance Memory Overhead RCU: O(1) RWLock: O(n) Spinlock: O(1) RCU: O(n) RWLock: O(1) Spinlock: O(1) RCU: Medium RWLock: Low Spinlock: Low
实践提示:若读操作占主导且对延迟敏感,优先考虑 RCU;否则按传统锁方案选择。
8. 最佳实践
8.1 读者端准则
- 一律用
rcu_read_lock()
/rcu_read_unlock()
包围读操作;尽量把临界区缩短到只包含必须的读取/解引用。 - 读取指针时用
rcu_dereference()
,以确保正确的内存序语义。 - 切忌在 RCU 读临界区做长时间阻塞或可能睡眠的操作。
8.2 写者端准则
- 用
rcu_assign_pointer()
发布新数据;旧对象的释放放到call_rcu()
的回调里。 - 避免在 RCU 回调里做复杂或阻塞的工作;回调只做释放或极轻量的清理。
- 只有确实需要同步等待时才用
synchronize_rcu()
,否则尽量用异步回调以保留写者并发。
8.3 数据结构与性能优化
- 有回收需求的结构内嵌
struct rcu_head
,便于统一回调管理。 - 若回收对象数量大,优先批量回收而不是一个对象一个回调;可显著降低回调开销。
- 选择合适的 RCU 变体:TREE_RCU(大系统)、TINY_RCU(嵌入式)、PREEMPT_RCU(实时/抢占)。
示例:批量释放的回调模式(示意)
c
// 批量释放而不是单个释放
void batch_free_callback(struct rcu_head *head) {
struct batch_data *batch = container_of(head, struct batch_data, rcu);
// 批量处理多个对象
}
9. 总结
RCU 的价值在于把"低延迟读"与"安全延迟回收"巧妙结合:读者几乎零开销,写者通过宽限期+回调来完成不破坏并发安全的回收工作。
典型适用场景:网络协议栈、页缓存/文件系统缓存、设备驱动、内核中需要高并发读访问的数据结构。