RCU简介
RCU(Read-Copy Update),顾名思义就是读-拷贝修改,是数据同步的一种方式,读者不需要获得任何锁就可以访问它,但写者在访问它时首先拷贝一个副本,然后对副本进行修改,最后使用一个回调(callback)机制在适当的时机把指向原来数据的指针重新指向新的被修改的数据。
因此RCU实际上是一种改进的rwlock,读者几乎没有什么同步开销,它不需要锁,不使用原子指令,因此不会导致锁竞争,也不存在死锁问题。写者的同步开销比较大,它需要延迟数据结构的释放,复制被修改的数据结构,它也必须使用某种锁机制同步并行的其它写者的修改操作。
RCU与rwlock的不同之处是:它既允许多个读者同时访问被保护的数据,又允许多个读者和多个写者同时访问被保护的数据(注意:是否可以有多个写者并行访问取决于写者之间使用的同步机制),即写者在访问被RCU保护的共享数据时不需要和读者竞争任何锁,读者没有任何同步开销,而写者的同步开销则取决于使用的写者间同步机制。只有在有多于一个写者的情况下需要获得某种锁以与其他写者同步。
但RCU不能替代rwlock,因为如果写比较多时,对读者的性能提高不能弥补写者导致的损失。
写者修改数据前首先拷贝一个被修改元素的副本,然后在副本上进行修改,修改完毕后它向垃圾回收器注册一个回调函数以便在适当的时机执行真正的修改操作,等待适当时机的这一时期称为宽限期(grace period)。
RCU适用于需要频繁的读取数据,而相应修改数据并不多的情景,例如在文件系统中,经常需要查找定位目录,而对目录的修改相对来说并不多,这就是RCU发挥作用的最佳场景。
原理
RCU
的基本思想是将更新Update
操作分为两个部分:1)Removal
移除;2)Reclamation
回收。
直白点来理解就是,临界资源被多个读者读取,写者在拷贝副本修改后进行更新时,第一步需要先把旧的临界资源数据移除(修改指针指向),等待宽限期结束之后,第二步把旧的数据进行回收(比如kfree
)。
为了确保没有读者正在访问要回收的临界资源,Reclaimer需要等待所有的读者退出临界区,这个等待的时间就是所谓的宽限期。
可能还有新的读者来读取临界资源(更新后的内容)。但是,在一个删除动作发生后,它必须等待所有在宽限期开始前已经开始的读线程结束,才可以进行销毁操作。在宽限期开始后的线程不可能读到已删除的元素。
注:并不是在最后一个RCU读者离开临界区后立马就返回,可能存在一个调度延迟
在Updater进行更新后,在Reclaimer进行回收之前,是会存在新旧两个版本的临界资源的,只有在宽限期返回后,Reclaimer对旧的临界资源进行回收,最后剩下一个版本。
举例说明
RCU的基本思想是这样的:先创建一个旧数据的copy,然后writer更新这个copy,最后再用新的数据替换掉旧的数据。
假设有一个单向链表,其中包含一个由指针p指向的节点:
现在,我们要使用RCU机制来更新这个节点的数据,那么首先需要分配一段新的内存空间(由指针q指向),用于存放这个copy。
然后将p指向的节点数据,也就是旧的共享资源[5,6,7]以及它和下一节点[11, 4, 8]的关系,都完整地copy到q指向的内存区域中。
接下来,writer会修改这个copy中的数据(将[5, 6, 7]修改为[5, 2, 3])。
修改完成之后,writer就可以将这个更新“发布”了(publish),对于reader来说就“可见”了。因此,pubulish之后才开始读取操作的reader(比如读节点[1, 2, 3]的下一个节点),得到的就是新的数据[5, 2, 3](图中红色边框表示有reader在引用)。
注意:指针方向修改完成后,宽限期开始,即上述的1)
Removal
移除工作完成后开始进入宽限期。
而在publish之前就开始读取操作的reader则不受影响,依然使用旧的数据[5, 6, 7]。
注:宽限期开始前已经访问到该共享资源(即[5,6,7])的进程,不影响后序的访问,而在宽限期开始后才进入到该临界区的进程,访问的便是修改过后的共享资源(即[5,2,3])。
等到所有引用旧数据区的reader(即宽限期开始前访问该共享资源的进程)都完成了相关操作,即宽限期结束后,writer才会释放由p指向的内存区域。
可见,在此期间,reader如果读取这个节点的数据,得到的要么全是旧的数据,要么全是新的数据,反正不会是「半新半旧」的数据,数据的一致性是可以保证的。重要的是,RCU中的reader不用像rwlock中的reader那样,在writer操作期间必须spin等待了。
数据的一致性的保证便是这样实现的,该机制称为发布-订阅机制(Publish-Subscribe Mechanism),在写端完成对共享数据的修改之后,需要将新的数据版本发布出去,以便读端能够获取到更新的数据版本。发布时,写端会更新一个全局的版本号,并将新的数据版本挂接到全局数据结构中。
图解总结
接下来我们通过图解的方式来解释RCU的工作原理。
1.中间的黄色部分代表的就是Grace Period,中文叫做宽限期。从Removal(移除,即修改指针指向)到Reclamation(回收),中间就隔了一个宽限期。
2.Reader-4和Reader-5是在宽限期开始前进入临界区的,Reader-1,Reader-2,是在宽限期开始后进入临界区的,而Reader-3在宽限期开始前就已经结束。
3.只有当宽限期结束后,才会触发回收的工作,宽限期的结束代表着Reader(特指宽限期开始前访问共享资源的Reader)都已经退出了临界区,因此回收工作也就是安全的操作了;
4.宽限期是否结束,与处理器的执行状态检测有关,也就是检测静止状态Quiescent Status。RCU的性能与可扩展性同样依赖于它是否能有效的检测出静止状态(Quiescent Status),并且判断宽限期是否结束。
Quiescent Status
,用于描述处理器的执行状态。当某个CPU正在访问RCU保护的临界区时,认为是活动的状态,而当它离开了临界区后,则认为它是静止的状态。当所有的CPU都至少经历过一次QS后,宽限期将结束并触发回收工作。1.在时钟tick中检测CPU处于用户模式或者idle模式,则表明CPU离开了临界区;
2.在不支持抢占的RCU实现中,检测到CPU有context切换,就能表明CPU离开了临界区;
RCU的优缺点
优点:
1)读者侧开销很少、不需要获取任何锁,不需要执行原子指令或者内存屏障;
2)没有死锁问题;
3)没有优先级反转的问题;
4)没有内存泄露的危险问题;
5)很好的实时延迟;
缺点:
1)写者的同步开销比较大,写者之间需要互斥处理;
2)使用上比其他同步机制复杂;
本文部分内容参考自网络,如有侵权请私信联系我删除