RCU 的本质是用空间换时间,是对读写锁的一种优化加强。
简介
是 Read Copy Update, RCU 的缩写,是内核从 2.5 开始引入的锁机制,主要用于在无锁的情况下并行访问内核中的部分结构体,也即 “随意读,但更新数据的时候,需要先复制一份副本,在副本上完成修改,再一次性地替换旧数据”,这是 Linux 内核实现的一种针对 “读多写少” 的共享数据的同步机制。
简单来说,读取无需加锁,写入处理成本要高于其它同步方案,适用于读多写少的场景。内核中有很多的依赖,常见的如网络路由表的查询、fs/dcache.c
实现,还有一个比较典型的是链表,对应了 include/linux/rculist.h
文件。
基本概念
将更新分成了两个阶段:A) removal
替换原有的引用 (一般是指针),此时允许并发读;B) reclaimation
用于回收,会等待所有的读完成,然后可以安全回收老的资源。
注意,其实这里有个基础的假设,对于内存对齐的指针,CPU 可以保证替换不会出现中间状态。
API
注意,RCU
保护的是一个指针,定义时需要通过 __rcu
宏定义,可以用于 Sparse 静态工具的检查。
读取通过如下几个函数实现。
rcu_read_lock/unlock()
读取进入以及退出临界区。rcu_dereference()
获取对象的指针,需要确保在上述临界区保护范围内。
写入过程需要有类似 spinlock
的临界区保护,读取一般就通过 rcu_dereference_protected()
函数获取:
rcu_assign_pointer()
用来保存新对象的指针。synchronize_rcu()
call_rcu()
等待所有读退出,然后可以释放老的内存,分别对应了同步和异步方式。
其中 call_rcu()
允许注册一个回调函数,用于一些不允许阻塞的场景,例如在 softirq
上下文中,这也就允许 removal
和 reclaimation
在不同的线程中。
参考
- 一系列从基础开始介绍 RCU 的系列文章 What is RCU, Really?、What is RCU? Part 2: Usage、The RCU API,最后一篇因为 API 在不断变化,所以会有多个年份的版本,整体可以参考 kernel.org 中的介绍。
- 很多 RCU 相关的文档保存在 Documentation/RCU 目录下,Paul E. McKenney 是内核中 RCU 源码的主要实现者,一些相关的论文和文章可以参考 Introduction to RCU 。