RCU(Read-Copy Update),顾名思义就是读-拷贝-修改,它是基于其原理命名的。对于被RCU保护的共享数据结构,读者不需要获得任何锁就可以访问它,但写者在访问它时首先拷贝一个副本,然后对副本进行修改,最后使用一个回调
(callback)机制在适当的时机把指向原来数据的指针替换为新的被修改的数据。这个时机
就是所有引用该数据的CPU都退出对共享数据的访问。
在面对内核中需要频繁读但是不需要频繁写的共享数据时,RCU是一种非常成功的同步机制。可以实现多线程无阻塞地读取数据,就算有线程在修改数据时读者也不会被阻塞
,且读取数据的时候几乎没有额外的同步开销,同时也能排除当我们在读的时候某人正在修改数据的可能(否则的话会导致读取数据的线程可能读到更新一半的数据或者是读到一个无效的指针等等)。
RCU的缺点在于,(1)RCU的读者可能访问旧数据,或者发现数据不一致。RCU不保证在写线程开始之后的读线程可以读取到更新后的数据,只能保证读取到的数据是旧数据或新数据,而不是修改一半的错误数据。(2)写者的性能比较糟糕。
这是RCU实现的关键。难点就在于如何判断所有的读者已经完成访问。通常把写者开始更新,到所有读者完成访问这段时间叫做宽限期(Grace Period)。
static inline void __rcu_read_lock(void) { preempt_disable(); } static inline void __rcu_read_unlock(void) { preempt_enable(); }
线程在进入RCU读临界区时关闭了抢占,在离开读临界区时打开抢占, 这时是否度过宽限期的判断就比较简单:每个CPU都经过一次抢占。
每个CPU在时钟中断的处理函数中,都会判断当前CPU是否完成了抢占。
void rcu_check_callbacks(int cpu, int user) { ...... if (user || rcu_is_cpu_rrupt_from_idle()) { /*在用户态上下文,或者idle上下文,说明已经发生过抢占*/ rcu_sched_qs(cpu); rcu_bh_qs(cpu); } else if (!in_softirq()) { /*仅仅针对使用rcu_read_lock_bh类型的rcu,不在softirq, *说明已经不在read_lock关键区域*/ rcu_bh_qs(cpu); } rcu_preempt_check_callbacks(cpu); if (rcu_pending(cpu)) invoke_rcu_core(); ...... }
然后向RCU汇报,该CPU已经离开了读临界区。