RCU是linux系统的一种读写同步机制,说到底他也是一种内核同步的手段,本问就RCU概率和实现机制,给出笔者的理解。

【RCU概率】

我们先看下内核文档中对RCU的定义:

RCU is a synchronization mechanism that was added to the Linux kernel during the 2.5 development effort that is optimized for read-mostly situations.

翻译:RCU是在2.5版本内核引入的一种同步机制,目的在于优化数据读取较多之场景下的效率。

说道读读多写少的场景,我们能自然联想到读写锁,不错,RCU正是和读写锁相似的一种提高读多写少场景下代码执行效率的机制,它的核心思想就是“订阅发布”机制。

实际上,我们使用锁来保护互斥资源,无非就是防止这两种情况:

1)读者在读取数据时,写者对数据同时进行改写,导致读者读到不完整的数据
2)写者在写数据时,有另一写者同时写数据,导致数据被写脏
由此我们很早久已经使用了各种锁机制来保护互斥资源,而且针对读多写少的情况,我们还专门优化出读写锁,使得在没有写者的情况下,多个读者可以并行持锁,从而可以并行读取数据,提高效率。那么有没有一种去锁的办法实现对互斥资源的保护呢?所以这里RCU机制就登场了。它的核心思想是:互斥数据采用指针来访问,当写者想要更新数据时,先将数据复制一份,对复制的数据进行修改,这样可以不干扰同一时间正在读取数据的读者。当修改完毕后,通过指针赋值,将旧数据指针更新指向到新的数据。最后再完成对旧数据的释放,释放时需要等待正在使用之前旧数据的读者退出临界区,而等待的这段时间在RCU机制中被称作“宽限期”。这里几个重要的概念就是“写时复制”、“指针赋值”、以及“宽限期”。它就像杂志订阅和发布,读者读取数据就好比订阅杂志,写者
复制并修改数据好比杂志的编辑,最后通过指针赋值更新数据久好比杂志的发布,而宽限期等待就好比期刊的发布周期,所以这是一个形象的比喻。通过这种机制,我们可以实现读者的去锁,它有如下几个特点:

1)读者读取数据不需要枷锁,因为数据时通过指针赋值更新的,而现代CPU处理器基本都可以保证指针赋值的原子性,另外写者保证在指针赋值前数据已经修改好,所以读者读到的数据始终是完整的,无需加锁
2)写者必须通过“写时复制”和“指针赋值”的方式更新数据,而对旧数据释放前需要等待数据更新前已经读取了旧数据的读者完成对旧数据的使用。
3)写者和写者直接仍然需要锁来互斥同步,但由于RCU的使用场景时多读写少,所以开销是可以接受的。

内核文档明确指出了一个RCU数据更新的典型步骤:

a. Remove pointers to a data structure, so that subsequent
readers cannot gain a reference to it.

b. Wait for all previous readers to complete their RCU read-side
critical sections.

c. At this point, there cannot be any readers who hold references
to the data structure, so it now may safely be reclaimed
(e.g., kfree()d).

翻译:

a. (通常是从链表中)移除指向数据结构(通常是链表节点)的指针, 使得后续读者无法再(通过链表)引用这个数据

b. 等待移除数据之前已经读取并正在使用该数据的读者退出临界区

c. 此时,已经没有读者在使用这个数据结构了,因此它可以被安全的回收

举个例子,比如有如下这样一个链表:

____       ____       ____
-->|__A_|-->|__B_|-->|__C_|-->...

现需要将B链表回收,那么:

a. 先将B节点从链表中移除,此后则不会再有读者能访问到B节点了,移除后情况如下:

____       ____                       ____
-->|__A_|-->|__C_|-->...     N-->|__C_|

其中“N”表示此时正在使用C节点的N个读者,虽然C已经不在链表当中,但仍有读者持有指向C的指针,所以暂时C的内存还不能回收

b. 等待所以正在使用C节点的读者使用完毕,即退出临界区,此时情况如下:

____       ____                      ____ 
-->|__A_|-->|__C_|-->...     0-->|__C_|

“0”表示已经没有读者使用C节点了,因此可以安全回收

c. 销毁C节点,回收内存:

____       ____            
-->|__A_|-->|__C_|-->...

d. 如果不想删除B,而只是想更新B的内容,那么此时便以安全的修改,修改完毕后果再将B节点以原子的方式插回队列中,如下:

____       ____       ____ 
-->|__A_|-->|__B_|-->|__C_|-->...

那么,这里有几个关键点没有讲清楚:

1. 如何知道当前有那些读者进程正在使用C节点呢?

2. 读者全部退出临界区的时候,如果通知出来呢?

所以,内核要给我们提供API去完成这些事情,请继续往下看。

【RCU的核心API】

内核文档列出了如下几个核心API函数:

a. rcu_read_lock()
b. rcu_read_unlock()
c. synchronize_rcu() / call_rcu()
d. rcu_assign_pointer()
e. rcu_dereference()
就是说这5个API时最基本的,还有其他一些API,但是都可以通过这5个API的组合来实现,下面一一讲解:

a. void rcu_read_lock(void);
翻译:用于通知回收者当前读者已进入临界区,在读者的临界区里时不允许阻塞的。

b. void rcu_read_unlock(void);
用于通知回收者当前读者已经退出临界区。

c. void synchronize_rcu(void);
synchronize_rcu用于等待在synchronize_rcu调用之前通过rcu_read_lock进入临界区的读者(在synchronize_rcu调用之后进入临界区的并不关心),在此之前函数会一直阻塞,当返回时,旧数据可以被安全的释放。

内核文档还给了一个例子,自己体会:
      CPU 0                            CPU 1                             CPU 2
-----------------           -------------------------              ---------------
1. rcu_read_lock()
2.                         enters synchronize_rcu()
3.                                                                       rcu_read_lock()
4. rcu_read_unlock()
5.                          exits synchronize_rcu()
6.                                                                      rcu_read_unlock()

d.typeof(p) rcu_assign_pointer(p, typeof(p) v);

这是一个宏实现,也只能是宏,自己体会下(提示:typeof。。。)
引用一段内核文档原话:The updater uses this function to assign a new value to an RCU-protected pointer, in order to safely communicate the change in value from the updater to the reader. This function returns the new value, and also executes any memory-barrier instructions required for a given CPU architecture.
这个函数就是用来完成前面提到的“指针赋值”的动作的,它会处理一些内存屏障的情况,否则我们直接赋值就是了,何必用这个宏呢?

e. typeof(p) rcu_dereference(p);
同样时通过宏实现的, 内核文档的解释:
The reader uses rcu_dereference() to fetch an RCU-protected pointer, which returns a value that may then be safely dereferenced. Note that rcu_deference() does not actually dereference the pointer, instead, it protects the pointer for later dereferencing. It also executes any needed memory-barrier instructions for a given CPU architecture.

这段话比较难懂,但说白了就是,当你想获取一个指向某个RCU数据时,rcu_dereference能返回一个安全的引用。 这里dereference是个很有意思的词,大家可以查下reference和dereference的区别,很好玩。

【总结】

理解RCU机制的关键点就是如何去理解“订阅发布”,确实如此,我们在APP商店购买应用的时候,用户得到的都是一个完整可用的APK,即最终产品的样子,而应用的开发过程是不会让用户看到的。作者要更新软件时,会线下修改,改好之后推送更新,即发布。同理,RCU机制在更新数据时,先将数据从链表中移除(类似商品下架),然后等待正在使用该数据的读者使用完毕,这段时间我们叫“宽限期”(类似以下架应用仍然继续提供客服,但会有一个期限),等宽限期过后,便修改跟新,然后重新插回链表中(类似应用重新上架)。这是一个非常巧妙的设计,需要花些时间去理解,但是一旦理解, 就很容易掌握这些概念了,甚至不需要任何记忆。

浅谈linux读写同步机制RCU的更多相关文章

  1. 浅谈Linux内存管理机制

    经常遇到一些刚接触Linux的新手会问内存占用怎么那么多?在Linux中经常发现空闲内存很少,似乎所有的内存都被系统占用了,表面感觉是内存不够用了,其实不然.这是Linux内存管理的一个优秀特性,在这 ...

  2. 【转载】浅谈Linux内存管理机制

    经常遇到一些刚接触Linux的新手会问内存占用怎么那么多? 在Linux中经常发现空闲内存很少,似乎所有的内存都被系统占用了,表面感觉是内存不够用了,其实不然.这是Linux内存管理的一个优秀特性,在 ...

  3. 浅谈Java多线程同步机制之同步块(方法)——synchronized

    在多线程访问的时候,同一时刻只能有一个线程能够用 synchronized 修饰的方法或者代码块,解决了资源共享.下面代码示意三个窗口购5张火车票: package com.jikexueyuan.t ...

  4. 浅谈Linux中的信号处理机制(二)

    首先谢谢 @小尧弟 这位朋友对我昨天夜里写的一篇<浅谈Linux中的信号处理机制(一)>的指正,之前的题目我用的“浅析”一词,给人一种要剖析内核的感觉.本人自知功力不够,尚且不能对着Lin ...

  5. [内核同步]浅析Linux内核同步机制

    转自:http://blog.csdn.net/fzubbsc/article/details/37736683?utm_source=tuicool&utm_medium=referral ...

  6. Linux内核同步机制--转发自蜗窝科技

    Linux内核同步机制之(一):原子操作 http://www.wowotech.net/linux_kenrel/atomic.html 一.源由 我们的程序逻辑经常遇到这样的操作序列: 1.读一个 ...

  7. Linux内核同步机制

    http://blog.csdn.net/bullbat/article/details/7376424 Linux内核同步控制方法有很多,信号量.锁.原子量.RCU等等,不同的实现方法应用于不同的环 ...

  8. Linux内核同步机制之(五):Read Write spin lock【转】

    一.为何会有rw spin lock? 在有了强大的spin lock之后,为何还会有rw spin lock呢?无他,仅仅是为了增加内核的并发,从而增加性能而已.spin lock严格的限制只有一个 ...

  9. 浅析Linux内核同步机制

    非常早之前就接触过同步这个概念了,可是一直都非常模糊.没有深入地学习了解过,最近有时间了,就花时间研习了一下<linux内核标准教程>和<深入linux设备驱动程序内核机制>这 ...

随机推荐

  1. [Android] Volley源代码分析(五岁以下儿童)Q \\ u0026一个

    Volley源代码分析系列那里一段时间,告诉我,有许多私人留言,同时一些问题抛出.对于一些简单的问题,我们跳,这两天被连接到朋友@smali提出的问题.告诉我你不得不赞叹查看源代码时的详细程度,大家一 ...

  2. Android-异步图像装载机

    在ListView加载图像是非常常见的场景,图像加载几个要求满足以下的: (1)是否画面位于网络或本地上,装载不应同步.但应该异步加载,例如,使用AsyncTask. (2)为了避免重复下载图片和网页 ...

  3. ASP.NET MVC项目里创建一个aspx视图

    先从控制器里添加视图 视图引擎选"ASPX(C#)",使用布局或模板页不要选. 在Views\EAV目录里,生成的aspx是个单独的页面,没有代码文件,所以代码也要写在这个文件里. ...

  4. js urlencode , encodeURIComponent

    js 对文字进行编码涉及3个函数:escape,encodeURI,encodeURIComponent,相应3个解码函数:unescape,decodeURI,decodeURIComponent ...

  5. Reactive Extensions

    Rx提供了一种新的组织和协调异步事件的方式,极大的简化了代码的编写.Rx最显著的特性是使用可观察集合(Observable Collection)来达到集成异步(composing asynchron ...

  6. 【WCF系列一】WCF入门教程(图文) VS2012

    WCF的全称是Windows Communication Foundation,从英文名称上看,WCF就是基于Windows下一种通讯的基础架构.利用WCF能够实现基于Windows下的各种通讯技术的 ...

  7. C#事件与委托的区别

    C#事件与委托的区别 1. 委托 事件是利用委托来定义的,因此先解释委托.委托是一个类,它与其他类如int,string等没有本质区别,int代表的是所有的整形,而string代表的是字符串,委托则代 ...

  8. C++ multimap容器访问同一键值元素的不同方法

    multimap是一种多元map容器,允许一个键对应多个值. 本文介绍了 multimap访问同一键值元素的三种不同方法,详细看下面代码: typedef multimap<string,int ...

  9. D8

    =-=昨天被老师拉去吃点心了就没有发题解...忧伤..昨天的T2貌似都没有调完嗯 今天脑洞是大啊.. T1模拟写挂..呵呵我一面 T2数学题..刚开始只会求素数表的那种方法暴力..不过后面他们都知道一 ...

  10. duilib开源界面库

    官网: https://code.google.com/p/duilib/ 基于:http://www.viksoe.dk/code/windowless1.htm 教程: http://www.cn ...