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. weblogic生产、开发模式互转

    生产模式与开发模式转换: 1.生产模式-->开发模式     将%DOMAIN_HOME%\config\config.xml文件中<production-mode-enabled> ...

  2. 【jar包】图片的异步加载--【 Imageloader】

    Android Imageloader图片异步加载 Imageloader是一个在android平台下简单的下载.显示.缓存空间的图片加载库. 异步下载网络图片并可以在UI线程更新View,使用二级缓 ...

  3. 【转】Android官方下拉刷新控件 SwipeRefreshLayout

    今天在Google+上看到了SwipeRefreshLayout这个名词,遂搜索了下,发现竟然是刚刚google更新sdk新增加的一个widget,于是赶紧抢先体验学习下. SwipeRefreshL ...

  4. A/B测试评测

    A/B测试评测 A/B测试在各类网站设计中已经是比较常见的,本文着重讲讲A/B测试在应用推送领域的作用. 目前国外开通A/B测试的推送服务商只有swrve,而国内的个推也在前不久发布的smart pu ...

  5. 学习GDI+ (1)

    using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; usin ...

  6. Fitnesse集成TestLink

    TestLink作为开源测试管理工具,可以进行测试工程.测试计划以及执行计划的管理,而且TestLink团队提供了XML-PRC的接口供第三方工具调用,接口支持程度也比较好. Fitnesse作为开源 ...

  7. Linq无聊练习系列4--join练习

    /**************join 练习*******************/            //对于1对多关系            var list =from c in ctx.T ...

  8. UVA 10391 Compound Words

    Problem E: Compound Words You are to find all the two-word compound words in a dictionary. A two-wor ...

  9. Robots惊恐记

    昨天发现在百度上搜索不到网站krely.cn的关键词(季小鱼),我记得之前的排名是第四位.到底是哪里的错误导致这个问题呢. 百度排名丢失,那么360会不会也出现同样的错误呢. 可以看到,360提示是我 ...

  10. 历年noip复赛试题整合

    早晨打算把历年的试题都过一遍,整理一下大概会往哪个方向考,考什么,不说太多,开始吧 2013: Day1: T1 转圈游戏 : 快速幂(关键在于要会打 快速幂) 思路:因为每次都进m位,相当于每次x加 ...