RCU 详解

2022-12-05 language

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 上下文中,这也就允许 removalreclaimation 在不同的线程中。

参考