浅谈linux读写同步机制RCU
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的更多相关文章
- 浅谈Linux内存管理机制
经常遇到一些刚接触Linux的新手会问内存占用怎么那么多?在Linux中经常发现空闲内存很少,似乎所有的内存都被系统占用了,表面感觉是内存不够用了,其实不然.这是Linux内存管理的一个优秀特性,在这 ...
- 【转载】浅谈Linux内存管理机制
经常遇到一些刚接触Linux的新手会问内存占用怎么那么多? 在Linux中经常发现空闲内存很少,似乎所有的内存都被系统占用了,表面感觉是内存不够用了,其实不然.这是Linux内存管理的一个优秀特性,在 ...
- 浅谈Java多线程同步机制之同步块(方法)——synchronized
在多线程访问的时候,同一时刻只能有一个线程能够用 synchronized 修饰的方法或者代码块,解决了资源共享.下面代码示意三个窗口购5张火车票: package com.jikexueyuan.t ...
- 浅谈Linux中的信号处理机制(二)
首先谢谢 @小尧弟 这位朋友对我昨天夜里写的一篇<浅谈Linux中的信号处理机制(一)>的指正,之前的题目我用的“浅析”一词,给人一种要剖析内核的感觉.本人自知功力不够,尚且不能对着Lin ...
- [内核同步]浅析Linux内核同步机制
转自:http://blog.csdn.net/fzubbsc/article/details/37736683?utm_source=tuicool&utm_medium=referral ...
- Linux内核同步机制--转发自蜗窝科技
Linux内核同步机制之(一):原子操作 http://www.wowotech.net/linux_kenrel/atomic.html 一.源由 我们的程序逻辑经常遇到这样的操作序列: 1.读一个 ...
- Linux内核同步机制
http://blog.csdn.net/bullbat/article/details/7376424 Linux内核同步控制方法有很多,信号量.锁.原子量.RCU等等,不同的实现方法应用于不同的环 ...
- Linux内核同步机制之(五):Read Write spin lock【转】
一.为何会有rw spin lock? 在有了强大的spin lock之后,为何还会有rw spin lock呢?无他,仅仅是为了增加内核的并发,从而增加性能而已.spin lock严格的限制只有一个 ...
- 浅析Linux内核同步机制
非常早之前就接触过同步这个概念了,可是一直都非常模糊.没有深入地学习了解过,最近有时间了,就花时间研习了一下<linux内核标准教程>和<深入linux设备驱动程序内核机制>这 ...
随机推荐
- jquery+ligerform三级联动下拉框
如下为ligerform里的三级联动下拉框: var formData=[ {display:,width:,space:,type:"select",group:"区域 ...
- OCP-1Z0-051-题目解析-第9题
9. Which statement is true regarding the INTERSECT operator? A. It ignores NULL values. B. Reversing ...
- C语言身份证信息查询系统(修改版)
很久以前写了一个<C语言身份证信息查询系统>,如果你点击链接进去看了. 估计也会被我那磅礴大气的代码震惊到的,最近复习/学习文件操作,把代码改了改,算是对以前还不会文件操作的时候的愿望,哈 ...
- Spring IOC 之Bean定义的继承
一个Bean的定义可以包含大量的配置信息,包括构造器参数.属性值以及容器规范信息,比如初始化方法.静态工厂方法名字等等.一子bean的定义可以从父bean的定义中继承配置数据信息.子bean定义可以覆 ...
- Effective C++(18) 让接口更容易被正确使用,不易被误用
问题聚焦: 从这个条款开始,我们把注意力转移到软件设计和声明上来,具体的说就是,C++接口的设计和声明. 所谓软件设计,就是以一般习惯的构想开始,演变成细节的实现,最终开发针对性的特殊 ...
- REDGATE又一好用的脚本工具
REDGATE又一好用的脚本工具 REDGATE又一好用的脚本工具 先说明一下:这个工具是免费的 下载地址:http://www.red-gate.com/products/dba/sql-scr ...
- Real-Rime Rendering (1) - 渲染管线(Rendering Pipeline)
提要 渲染管线是实时渲染中最重要的部分,它的最主要的任务就是在给定一个虚拟的场景,包括相机,object,灯光,纹理等等,生成一副2D的图像. 最基础的渲染管线如下图所示: 主要的阶段包括三个:App ...
- 企业架构研究总结(37)——TOGAF企业连续体和工具之架构资源库及架构工具的选择
3. 架构资源库 在一个企业,尤其是在一个大型企业中,建设一个成熟的架构往往会产生大量的工作产品.为了很好地管理和利用这些工作产品,企业需要制定一个正式的针对不同类型架构资产的分类方法,并且还需要专门 ...
- Navicat Premium 11.0.10破解补丁
Navicat Premium 11.0.10破解补丁 Navicat Premium 是一个可多重连接的数据库管理工具,让你以单一程序同時连接到 MySQL.SQL Server.SQLite. ...
- 用Jekyll在github上写博客
用Jekyll在github上写博客——<搭建一个免费的,无限流量的Blog>的注脚 本来打算买域名,买空间,用wordpress写博客的.后来问了一个师兄,他说他是用github的空间, ...