读数据居然不需要加锁?Linux RCU 的魔法

想象你在一间图书馆里,读者可以自由取书阅读,管理员想换掉某本书时,不会直接抢走读者手里的旧版——而是放一本新版到书架上,等所有拿着旧版的读者都读完了,再回收旧版。这就是 Linux 内核里 RCU(Read-Copy-Update)的核心思路。

传统的锁思路是:共享数据要么用互斥锁让读者排队,要么用读写锁让写着独占。但读写锁有个让人头疼的问题——写着等读者、读者等写着,互相拖慢。RCU 说:我干脆让读者完全不加锁、不计数、不等待,直接读。那写着怎么办?写着先把数据整体拷贝一份,修改副本,然后用原子操作把指针换过去。旧的读者还在读旧数据,没关系,它们会自然结束。等所有"旧读者"都退出了——这个等待窗口叫"宽限期"(grace period)——再回收旧数据。

这听起来是不是有点像 Copy-on-Write?确实有点亲戚关系。但 RCU 精妙的地方在于,它不需要读者做任何事。不递增计数器,不获取锁,什么都没有。读者就像读一个普通指针一样,零开销。那内核怎么知道旧读者都退出了?这正是 RCU 和硬件架构配合最深的地方——在 x86 上,一次上下文切换意味着 CPU 离开了旧读端,所以只要每个 CPU 都至少经历过一次上下文切换,宽限期就结束了。

RCU 从 2000 年代初进入主线内核,到今天已经从几个辅助函数演变成内核里最核心的同步原语之一。在 6.x 内核里,你能在网络栈、文件系统、进程调度这些最热的路径上看到 rcu_read_lock() / rcu_read_unlock() 的身影。它不是银弹——写着仍然要拷贝、要等宽限期,所以写频繁的场景并不适合。但读多写少恰好是操作系统里最常见的模式:路由表更新频率远低于查表,进程描述符读取远高于创建销毁。

所以下次看到代码里 rcu_dereference() 的时候,别以为只是又一个指针解引用。那一行的背后,是一整套让"读者零成本"成为可能的精妙机制。比起加锁再优化,不如从数据结构层面让冲突本身消失——这大概就是 Linus 说的"好品味"了。

#CS