转自: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函数了。

(原文首发 www.embexperts.com论坛,此处略有改动)

 

再谈Linux内核中的RCU机制的更多相关文章

  1. Linux内核中的信号机制--一个简单的例子【转】

    本文转载自:http://blog.csdn.net/ce123_zhouwei/article/details/8562958 Linux内核中的信号机制--一个简单的例子 Author:ce123 ...

  2. 浅析linux内核中的idr机制

    idr在linux内核中指的就是整数ID管理机制,从本质上来说,这就是一种将整数ID号和特定指针关联在一起的机制.这个机制最早是在2003年2月加入内核的,当时是作为POSIX定时器的一个补丁.现在, ...

  3. Linux内核中的Workqueue机制分析

    1. 什么是workqueue Linux中的workqueue(工作队列)主要是为了简化在内核创建线程而设计的.通过相应的工作队列接口,可以使开发人员只关心与特定功能相关的处理流程,而不必关心内核线 ...

  4. Linux内核中TCP SACK机制远程DoS预警通告

    漏洞描述 2019年6月18日,RedHat官网发布报告:安全研究人员在Linux内核处理TCP SACK数据包模块中发现了三个漏洞,CVE编号为CVE-2019-11477.CVE-2019-114 ...

  5. Linux内核同步:RCU

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

  6. Linux内核中锁机制之RCU、大内核锁

    在上篇博文中笔者分析了关于完成量和互斥量的使用以及一些经典的问题,下面笔者将在本篇博文中重点分析有关RCU机制的相关内容以及介绍目前已被淘汰出内核的大内核锁(BKL).文章的最后对<大话Linu ...

  7. 大话Linux内核中锁机制之RCU、大内核锁

    大话Linux内核中锁机制之RCU.大内核锁 在上篇博文中笔者分析了关于完成量和互斥量的使用以及一些经典的问题,下面笔者将在本篇博文中重点分析有关RCU机制的相关内容以及介绍目前已被淘汰出内核的大内核 ...

  8. 内核中的锁机制--RCU

    一. 引言 众所周知,为了保护共享数据,需要一些同步机制,如自旋锁(spinlock),读写锁(rwlock),它们使用起来非常简单,而且是一种很有效的同步机制,在UNIX系统和Linux系统中得到了 ...

  9. Linux内核中锁机制之原子操作、自旋锁

    很多人会问这样的问题,Linux内核中提供了各式各样的同步锁机制到底有何作用?追根到底其实是由于操作系统中存在多进程对共享资源的并发访问,从而引起了进程间的竞态.这其中包括了我们所熟知的SMP系统,多 ...

随机推荐

  1. c++中的类(class)

    c++的class(类)使用方法 这几天一直在调splay之类的东西,突然想转指针...qwq 于是,我就在沙华大佬的帮助下,学了下一顿乱指( $ -> $ ),也就是class(类) 首先:c ...

  2. kafka查看消费数据

    一.如何查看 在老版本中,使用kafka-run-class.sh 脚本进行查看.但是对于最新版本,kafka-run-class.sh 已经不能使用,必须使用另外一个脚本才行,它就是kafka-co ...

  3. metasploit利用漏洞渗透攻击靶机

    1.网络测试环境构建 首先需要先配置好一个渗透测试用的网络环境,包括如图1所示的运行Kali Linux系统的计算机,如图2所示的老师给的Windows Server 2000系统的计算机.这两台计算 ...

  4. net mvc cms

    .NET作品集:linux下的.net mvc cms   cms程序架构 本程序是主要是用于企业网站开发的,也可以做博客程序,程序是从之前上一篇的.net 博客程序改进过来的,主要技术由webfor ...

  5. 洛谷P2894 [USACO08FEB]酒店Hotel [线段树]

    题目传送门 酒店 题目描述 The cows are journeying north to Thunder Bay in Canada to gain cultural enrichment and ...

  6. oracle数据库连接错误解决办法

    ORA-28547 连接服务器失败,可能是Oracle Net 管理错误 原文地址:http://www.linuxidc.com/Linux/2014-11/109686.htm 上周去给客户培训O ...

  7. Python - 从列表中取随机数

    题目是:从一个有序列表中任取几个值组成新的列表 以下有2种思路去实现 1. 把那列表任意排列,截取尾巴上面的指定长度 import random total = 100 onetime = 7 x_l ...

  8. [转]状态压缩dp(状压dp)

    状态压缩动态规划(简称状压dp)是另一类非常典型的动态规划,通常使用在NP问题的小规模求解中,虽然是指数级别的复杂度,但速度比搜索快,其思想非常值得借鉴. 为了更好的理解状压dp,首先介绍位运算相关的 ...

  9. Pwn入坑指南

    栈溢出原理 参考我之前发的一篇 Windows栈溢出原理 还有 brant 师傅的<0day安全笔记> Pwn常用工具 gdb:Linux下程序调试 PEDA:针对gdb的python漏洞 ...

  10. Chrome导出书签瘦身,去除ADD_DATE和ICON

    Chrome等浏览器导出的书签是一个html文件,每个链接是一个a标签,由于保存着添加时间和图标显得太臃肿. 原本的样子: 我们利用sublime的正则替换,变成一个清爽的样子. 正则表达式:ADD_ ...