读者0额外成本是怎么做到的?
在典型的服务器内核配置(非抢占内核配置并使用gcc编译)时进入临界函数,内存屏障并不会生成任何汇编代码,只是通知编译器对临界区内外代码不要做乱序并且进入临界区后会刷新寄存器(防止CPU乱序的内存屏障操作在其他API中添加),离开临界区的代码同样最后只有一个内存屏障,因此可以看做是0额外成本。
static inline void rcu_read_lock(void) {
__rcu_read_lock(); __acquire(RCU); rcu_lock_acquire(&rcu_lock_map); rcu_lockdep_assert(rcu_is_watching(), "rcu_read_lock() used illegally while idle"); } static inline void __rcu_read_lock(void) {
preempt_disable(); } #define preempt_disable() barrier() #define barrier() __asm__ __volatile__("": : :"memory")
在RCU机制下写数据成本如何?
需要等到此前读者完成,需要等待,可以通过注册异步执行的函数的形式处理。
<include/linux/rcupdate.h> static inline void rcu_read_lock(void) {
__rcu_read_lock(); __acquire(RCU); rcu_read_acquire(); }
该函数实现里面有三个函数调用,但是实质性的工作由第一个函数__rcu_read_lock()来完成,__rcu_read_lock通过调用,preempt_disable关闭内核可抢占性,但是中断是允许的,假设读取者正处于rcu临界区中且刚读取了一个共享数据区的指针p(但是还没有访问p的数据成员),发生了一个中断,而该中断例程ISR恰好需要修改p所指向的数据区,按照RCU的设计原则,ISR会新分配一个同样大小的数据区new_p,再把老数据区的数据拷贝过来,接着在new_p的基础上做修改,不存在对p的并发访问,因此RCU是一种免锁机制。ISR在把数据更新的工作做完之后,将new_p的值重新赋给p,最后注册一个回调函数用以在适当的时候释放老指针。因此老指针p上的所有引用就结束了,释放p不会有问题。当中断处理例程做完这些工作之后,返回,被中断的例程将依然访问到p空间上的数据,也就是老数据,这样的结果是RCU允许的。RCU规则造成的资源短暂性不一致问题是允许的。
只需要通过rcu_read_lock关闭内核可抢占性就行,因为他使得当前读者进程即使在临界区发生了中断,也不会切换到其他进程。如果在rcu的临界区中调用了一个函数,该函数可能睡眠,那么RCU的设计规则就遭到了破坏,系统将进入一种不稳定的状态。
syschronize_rcu的实现则利用了等待队列,在它的实现过程中也会像call_rcu那样像之前处理器链表中加入节点,其回调函数是rcu_process_callbacks会检查当前处理器是否经历了一个休眠期,直到系统中的所有处理器都发生一次进程切换,因而wakeme_after_rcu被rcu_process_callbacks所调用以唤醒睡眠syschronize_rcu,被唤醒后,可以释放老指针了。
所以我们看到,call_rcu返回后其注册的回调函数可能还没被调用,因而也就意味着老指针还未被释放,而synchronize_rcu返回后老指针肯定被释放了。所以,是调用call_rcu还是synchronize_rcu,要视特定需求与当前上下文而定,比如中断处理的上下文肯定不能使用 synchronize_rcu函数了。
发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/225157.html原文链接:https://javaforall.net
