转载自:再谈Linux内核中的RCU机制-MagicBoy2010-ChinaUnix博客 http://blog.chinaunix.net/uid-23769728-id-3080134.html

RCU的设计思想比较明确,通过新老指针替换的方式来实现免锁方式的共享保护。但是具体到代码的层面,理解起来多少还是会有些困难。在《深入Linux设备驱动程序内核机制》第4章中,已经非常明确地叙述了RCU背后所遵循的规则,这些规则是从一个比较高的视角来看,因为我觉得过多的代码分析反而容易让读者在细节上迷失方向。最近拿到书后,我又重头仔细看了RCU部分的文字,觉得还应该补充一点点内容,因为有些东西不一定适合写在书里。

RCU读取侧进入临界区的标志是调用rcu_read_lock,这个函数的代码是:

  1. <include/linux/rcupdate.h>
  2. static inline void rcu_read_lock(void)
  3. {
  4. __rcu_read_lock();
  5. __acquire(RCU);
  6. rcu_read_acquire();
  7. }

该实现里面貌似有三个函数调用,但实质性的工作由第一个函数__rcu_read_lock()来完成,__rcu_read_lock()通过调用 preempt_disable()关闭内核可抢占性。但是中断是允许的,假设读取者正处于rcu临界区中且刚读取了一个共享数据区的指针p(但是还没有访问p中的数据成员),发生了一个中断,而该中断处理例程ISR恰好需要修改p所指向的数据区,按照RCU的设计原则,ISR会新分配一个同样大小的数据区new_p,再把老数据区p中的数据拷贝到新数据区,接着是在new_p的基础上做数据修改的工作(因为是在new_p空间中修改,所以不存在对p的并发访问,因此说RCU是一种免锁机制,原因就在这里),ISR在把数据更新的工作完成后,将new_p赋值给p(p=new_p),最后它会再注册一个回调函数用以在适当的时候释放老指针p。因此,只要对老指针p上的所有引用都结束了,释放p就不会有问题。当中断处理例程做完这些工作返回后,被中断的进程将依然访问到p空间上的数据,也就是老数据,这样的结果是RCU机制所允许的。RCU规则对读取者与写入者之间因指针切换所造成的短暂的资源视图不一致问题是允许的

接下来关于RCU一个有趣的问题是:何时才能释放老指针。我见过很多书中对此的回答是:当系统中所有处理器上都发生了一次进程切换。这种程式化的回答常常让刚接触RCU机制的读者感到一头雾水,为什么非要等所有处理器上都发生一次进程切换才可以调用回调函数释放老指针呢?这其实是RCU的设计规则决定的: 所有对老指针的引用只可能发生在rcu_read_lock与rcu_read_unlock所包括的临界区中,而在这个临界区中不可能发生进程切换而一旦出了该临界区就不应该再有任何形式的对老指针p的引用。很明显,这个规则要求读取者在临界区中不能发生进程切换,因为一旦有进程切换,释放老指针的回调函数就有可能被调用,从而导致老指针被释放掉,当被切换掉的进程被重新调度运行时它就有可能引用到一个被释放掉的内存空间。

现在我们看到为什么rcu_read_lock只需要关闭内核可抢占性就可以了,因为它使得即便在临界区中发生了中断,当前进程也不可能被切换除去。 内核开发者,确切地说,RCU的设计者所能做的只能到这个程度。接下来就是使用者的责任了,如果在rcu的临界区中调用了一个函数,该函数可能睡眠,那么RCU的设计规则就遭到了破坏,系统将进入一种不稳定的状态。

这再次说明,如果想使用一个东西,一定要搞清楚其内在的机制,象上面刚提到的那个例子,即便现在程序不出现问题,但是系统中留下的隐患如同一个定时炸弹, 随时可能被引爆,尤其是过了很长时间问题才突然爆发出来。绝大多数情形下,找到问题所花费的时间可能要远远大于静下心来仔细搞懂RCU的原理要多得多。 

RCU中的读取者相对rwlock的读取者而言,自由度更高。因为RCU的读取者在访问一个共享资源时,不需要考虑写入者的感受,这不同于rwlock的写入者,rwlock reader在读取共享资源时需要确保没有写入者在操作该资源。两者之间的差异化源自RCU对共享资源在读取者与写入者之间进行了分离,而rwlock的 读取者和写入者则至始至终只使用共享资源的一份拷贝。这也意味着RCU中的写入者要承担更多的责任,而且对同一共享资源进行更新的多个写入者之间必须引入某种互斥机制,所以RCU属于一种"免锁机制"的说法仅限于读取者与写入者之间。所以我们看到:RCU机制应该用在有大量的读取操作,而更新操作相对较少的情形下。此时RCU可以大大提升系统系能,因为RCU的读取操作相对其他一些有锁机制而言,在锁上的开销几乎没有。

实际使用中,共享的资源常常以链表的形式存在,内核为RCU模式下的链表操作实现了几个接口函数,读取者和使用者应该使用这些内核函数,比如 list_add_tail_rcu, list_add_rcu,hlist_replace_rcu等等,具体的使用可以参考某些内核编程或者设备驱动程序方面的资料。 

在释放老指针方面,Linux内核提供两种方法供使用者使用,一个是调用call_rcu,另一个是调用synchronize_rcu。前者是一种异步 方式,call_rcu会将释放老指针的回调函数放入一个结点中,然后将该结点加入到当前正在运行call_rcu的处理器的本地链表中,在时钟中断的 softirq部分(RCU_SOFTIRQ), rcu软中断处理函数rcu_process_callbacks会检查当前处理器是否经历了一个休眠期(quiescent,此处涉及内核进程调度等方面的内容),rcu的内核代码实现在确定系统中所有的处理器都经历过了一个休眠期之后(意味着所有处理器上都发生了一次进程切换,因此老指针此时可以被安全释放掉了),将调用call_rcu提供的回调函数。
synchronize_rcu的实现则利用了等待队列,在它的实现过程中也会向call_rcu那样向当前处理器的本地链表中加入一个结点,与 call_rcu不同之处在于该结点中的回调函数是wakeme_after_rcu,然后synchronize_rcu将在一个等待队列中睡眠,直到系统中所有处理器都发生了一次进程切换,因而wakeme_after_rcu被rcu_process_callbacks所调用以唤醒睡眠的 synchronize_rcu,被唤醒之后,synchronize_rcu知道它现在可以释放老指针了。

所以我们看到,call_rcu返回后其注册的回调函数可能还没被调用,因而也就意味着老指针还未被释放,而synchronize_rcu返回后老指针肯定被释放了。所以,是调用call_rcu还是synchronize_rcu,要视特定需求与当前上下文而定,比如中断处理的上下文肯定不能使用 synchronize_rcu函数了。

rcu机制的更多相关文章

  1. linux 内核 RCU机制详解

    RCU(Read-Copy Update)是数据同步的一种方式,在当前的Linux内核中发挥着重要的作用.RCU主要针对的数据对象是链表,目的是提高遍历读取数据的效率,为了达到目的使用RCU机制读取数 ...

  2. RCU 机制 [转IBM]

    2005 年 7 月 01 日 本文详细地介绍了 Linux 2.6 内核中新的锁机制 RCU(Read-Copy Update) 的实现机制,使用要求与典型应用. 一.引言 众所周知,为了保护共享数 ...

  3. 深入理解 Linux 的 RCU 机制

    欢迎大家前往腾讯云社区,获取更多腾讯海量技术实践干货哦~ 作者:梁康 RCU(Read-Copy Update),是 Linux 中比较重要的一种同步机制.顾名思义就是"读,拷贝更新&quo ...

  4. Linux RCU 机制详解

    1.简介: RCU(Read-Copy Update)是数据同步的一种方式,在当前的Linux内核中发挥着重要的作用. RCU主要针对的数据对象是链表,目的是提高遍历读取数据的效率,为了达到目的使用R ...

  5. 再谈Linux内核中的RCU机制

    转自:http://blog.chinaunix.net/uid-23769728-id-3080134.html RCU的设计思想比较明确,通过新老指针替换的方式来实现免锁方式的共享保护.但是具体到 ...

  6. LINUX中的RCU机制的分析

    RCU机制是Linux2.6之后提供的一种数据一致性访问的机制,从RCU(read-copy-update)的名称上看,我们就能对他的实现机制有一个大概的了解,在修改数据的时候,首先需要读取数据,然后 ...

  7. linux内核 RCU机制详解【转】

    本文转载自:https://blog.csdn.net/xabc3000/article/details/15335131 简介 RCU(Read-Copy Update)是数据同步的一种方式,在当前 ...

  8. 从自旋锁、睡眠锁、读写锁到 Linux RCU 机制讲解

    ​    同步自我的 csdn 博客 6.S081 从自旋锁.睡眠锁.读写锁到 Linux RCU 机制讲解_我说我谁呢 --CSDN博客 总结一下 O/S 课程里面和锁相关的内容. 本文是 6.S0 ...

  9. Linux设备驱动程序 之 RCU机制

    读取-复制-更新(read-copy-update,RCU)是一种高级的互斥机制,在正确的条件下,可以获得高的性能: RCU对它保护的数据结构做了一些限定,它针对经常发生读而很少发生写的情况做了优化, ...

随机推荐

  1. 1-linux系统管理(一)之安装RHEL 6.4

    linux系统管理之安装RHEL 6.4 进入安装界面 这里选择跳过 点击下一步 选择安装语言 选择键盘 选择系统储存方式 选择是否格式化储存设备 给安装的系统一个计算机名 选择时区 给root一个密 ...

  2. SCNU 2015ACM新生赛初赛【1001~1011】个人解题思路

            题目1001:       大意:已知$n$个角色,$m$种怪物种族,$k$个怪物,给出一组角色编号,编号$P_{i}$的角色能肝死编号$i$的怪物,对于给定的一组怪物编号,为了打通关 ...

  3. Linux 内核版本命名

    Linux 内核版本命名在不同的时期有其不同的规范,我们熟悉的也许是 2.x 版本奇数表示开发版.偶数表示稳定版,但到 2.6.x 以及 3.x 甚至将来的 4.x ,内核版本命名都不遵守这样的约定. ...

  4. VPN 部署方案

    VPN 对比: OpenVPN: 客户端连接太麻烦,放弃 PPTP VPN:版本较高的苹果手机没有 PPTP VPN 的连接方式,放弃 L2TP VPN:支持所有平台,客户端连接容易,最终选择部署 L ...

  5. Maven学习

    http://www.cnblogs.com/sprinng/p/5141233.html 生成项目jar包失败 maven安装出现解决:http://blog.csdn.net/kjfcpua/ar ...

  6. webgl动画小测试

    // MultiPoint.js (c) 2012 matsuda // Vertex shader program var VSHADER_SOURCE = 'attribute vec4 a_Po ...

  7. 1366分辨率其实是1368分辨率,firefox a标签点击有虚线

    1,通过intel 集成显卡的软件自定义一个1366分辨率,发现是1368的分辨率,@media screen and (max-deivce-width:1368px)才有效果,并且在同事电脑默认分 ...

  8. 当div有边框图片的时候,怎么实现内部的p标签的水平和垂直居中

    <!-- 这里a.png必须是四边的框都有,限制,这个时候做里边文字的居中,首先在这个里边在套一个div悬浮(absolute或者float:left),然后在这个div(必须设宽高和margi ...

  9. 单独使用Mybatis的配置文件

    <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC & ...

  10. iOS学习-圆形进度条

    效果: #import <UIKit/UIKit.h> @interface HsProfitRatePieWidgets : UIView { UILabel *_textLabel; ...