RCU(Read-Copy-Update)是 Linux 内核最重要的同步原语之一,他与二进制读写锁(rw_semaphore)和原子操作共同构成环境内核的三大隔离机制。本文从 rcu_read_lock 的底层实现、宽限期(Grace Period)的判定与等待、synchronize_rcu 的跨核协作,到 hlist 无锁遍历、RCU 内存回收与 SLUB 互动,再到 QSBR 与 URCU 用户态实现,逐层拆解 RCU 在 Linux 内核最多写少场景下的工程化应用。

一、从"无同步的读取"到 Read-Copy-Update

因为 rcu_read_lock 实际上只是润滑了 RCU 缓存的同步,但不是压制读取行为。在 x86 架构上,rcu_read_lock 的实现是修改 preempt_count 计数器(preempt_disable),保证当前线程不会被抢占,从而形成读取侧临界区。

include/linux/rcupdate.h\nstatic inline void rcu_read_lock(void) {\n    __acquire(RCU);\n    rcu_lock_acquire(\&rcu_lock_map);\n    RCU_LOCKDEP_WARN(!rcu_is_watching(),\n        "rcu_read_lock() used illegally while idle");\n}

关键观察:rcu_read_lock 本质上不阻止其他读者或写者。它只是延缓上下文切换和抢占的发生——即让读者"安静地"留在临界区内,直到主动退出。这种设计使得读取路径的开销极小,通常只有一条内存屏障指令。

二、宽限期(Grace Period)机制详解

synchronize_rcu 是 RCU 中阻塞写者的核心函数,它等待"宽限期"(Grace Period)结束:

  • 宽限期定义:从调用 synchronize_rcu() 开始,到所有 CPU 上曾经处于 rcu_read_lock 临界区的线程都已退出临界区为止
  • 判定方式:每当 CPU 经历一次上下文切换或进入空闲循环,内核即标记该 CPU 已完成一个"静态状态"(Quiescent State, QS)
  • 快速路径:如果调用 synchronize_rcu() 时所有 CPU 都已完成 QS,则立即返回,无需等待

三、synchronize_rcu vs call_rcu:阻塞与异步的选择

call_rcu 将回调函数注册到 rcu_head 链表,GP 结束后由 rcu_core 内核线程批量执行回调。SLAB_TYPESAFE_BY_RCU 的 kmem_cache 即利用此特性。

四、hlist RCU 无锁遍历

hlist 是内核中专门为 RCU 优化的哈希链表。其核心特点是 head 只存储一个指针(first),使得 RCU 场景下的链表更新仅需修改一个指针。

rcu_read_lock();\nhlist_for_each_entry_rcu(pos, head, member) {\n    if (pos->key == target)\n        break;\n}\nrcu_read_unlock();

五、dentry 缓存中的 RCU 工程实战

dentry 缓存是 RCU + SLAB_TYPESAFE_BY_RCU + hlist 综合运用的典型案例。

void __init dcache_init(void)\n{\n    dentry_cache = kmem_cache_create("dentry",\n        sizeof(struct dentry) + dentry_extra,\n        0,\n        SLAB_HWCACHE_ALIGN|SLAB_PANIC|SLAB_TYPESAFE_BY_RCU,\n        NULL);\n}

六、URCU 用户态 RCU(liburcu)

用户态 RCU 库 liburcu 用 QSBR(Quiescent State Based Reclamation)替代内核的上下文切换 hook:所有注册 RCU 线程必须周期性声明已通过静态点(rcu_quiescent_state),synchronize_rcu 检查所有线程计数器是否递增来判定 GP 结束。

七、生产环境常见陷阱

陷阱1:rcu_read_lock 中阻塞(scheduling while atomic)。正确做法:复杂操作移出 RCU 临界区。

陷阱2:rcu_dereference 后引用逃逸出 rcu_read_unlock 边界,导致 UAF。

陷阱3:call_rcu 风暴——百万级对象同时 call_rcu 导致 rcu_core 积压。优化方案:批处理、rcu_segcblist 分区队列。

陷阱4:synchronize_rcu 在 read-mostly 高频写入路径中使用,应优先用 call_rcu。

性能数据:在 96 路 EPYC 7763 服务器上,RCU 读取开销约 3-5ns,synchronize_rcu 延迟约 8-40ms;对比读写锁 rwsem,读取延迟 ~20ns 但有写者饥饿风险。

八、总结

RCU 的核心哲学是:将等待的成本从读者转移到写者。在读多写少的场景下(dentry cache、路由表、模块引用计数等),这是最优的同步策略。

工程师需要遵循的黄金法则是:RCU 保护的是对象生命周期,而非对象状态——读者必须将对象引用严格限定在 rcu_read_lock/unlock 边界内。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部