浅谈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机制在更新数据时,先将数据从链表中移除(类似商品下架),然后等待正在使用该数据的读者使用完毕,这段时间我们叫“宽限期”(类似以下架应用仍然继续提供客服,但会有一个期限),等宽限期过后,便修改跟新,然后重新插回链表中(类似应用重新上架)。这是一个非常巧妙的设计,需要花些时间去理解,但是一旦理解, 就很容易掌握这些概念了,甚至不需要任何记忆。

时间: 2024-08-27 23:55:41

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

浅谈Linux内存管理机制

经常遇到一些刚接触Linux的新手会问内存占用怎么那么多? 在Linux中经常发现空闲内存很少,似乎所有的内存都被系统占用了,表面感觉是内存不够用了,其实不然.这是Linux内存管理的一个优秀特性,在这方面,区别于Windows的内存管理.主要特点是,无论物理内存有多大,Linux 都将其充份利用,将一些程序调用过的硬盘数据读入内存,利用内存读写的高速特性来提高Linux系统的数据访问性能.而Windows是只在需要内存时,才为应用程序分配内存,并不能充分利用大容量的内存空间.换句话说,每增加一

浅谈Linux中的信号机制(二)

首先谢谢 @小尧弟 这位朋友对我昨天夜里写的一篇<浅谈Linux中的信号机制(一)>的指正,之前的题目我用的“浅析”一词,给人一种要剖析内核的感觉.本人自知功力不够,尚且不能对着Linux内核源码评头论足.以后的路还很长,我还是一步一个脚印的慢慢走着吧,Linux内核这座山,我才刚刚抵达山脚下. 好了,言归正传,我接着昨天写下去.如有错误还请各位看官指正,先此谢过. 上篇末尾,我们看到了这样的现象:send进程总共发送了500次SIGINT信号给rcv进程,但是实际过程中rcv只接受/处理了1

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

转自:http://blog.csdn.net/fzubbsc/article/details/37736683?utm_source=tuicool&utm_medium=referral 很早之前就接触过同步这个概念了,但是一直都很模糊,没有深入地学习了解过,近期有时间了,就花时间研习了一下<linux内核标准教程>和<深入linux设备驱动程序内核机制>这两本书的相关章节.趁刚看完,就把相关的内容总结一下.为了弄清楚什么事同步机制,必须要弄明白以下三个问题: 什么是互

Linux内核同步机制

http://blog.csdn.net/bullbat/article/details/7376424 Linux内核同步控制方法有很多,信号量.锁.原子量.RCU等等,不同的实现方法应用于不同的环境来提高操作系统效率.首先,看看我们最熟悉的两种机制——信号量.锁. 一.信号量 首先还是看看内核中是怎么实现的,内核中用struct semaphore数据结构表示信号量(<linux/semphone.h>中): [cpp] view plaincopyprint? struct semaph

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

Linux内核同步机制之(一):原子操作 http://www.wowotech.net/linux_kenrel/atomic.html 一.源由 我们的程序逻辑经常遇到这样的操作序列: 1.读一个位于memory中的变量的值到寄存器中 2.修改该变量的值(也就是修改寄存器中的值) 3.将寄存器中的数值写回memory中的变量值 如果这个操作序列是串行化的操作(在一个thread中串行执行),那么一切OK,然而,世界总是不能如你所愿.在多CPU体系结构中,运行在两个CPU上的两个内核控制路径同

浅析Linux内核同步机制

很早之前就接触过同步这个概念了,但是一直都很模糊,没有深入地学习了解过,近期有时间了,就花时间研习了一下<linux内核标准教程>和<深入linux设备驱动程序内核机制>这两本书的相关章节.趁刚看完,就把相关的内容总结一下.为了弄清楚什么事同步机制,必须要弄明白以下三个问题: 什么是互斥与同步? 为什么需要同步机制? Linux内核提供哪些方法用于实现互斥与同步的机制? 1.什么是互斥与同步?(通俗理解) 互斥与同步机制是计算机系统中,用于控制进程对某些特定资源的访问的机制. 同步

[]转帖] 浅谈Linux下的五种I/O模型

浅谈Linux下的五种I/O模型 https://www.cnblogs.com/chy2055/p/5220793.html  一.关于I/O模型的引出 我们都知道,为了OS的安全性等的考虑,进程是无法直接操作I/O设备的,其必须通过系统调用请求内核来协助完成I/O动作,而内核会为每个I/O设备维护一个buffer.如下图所示: 整个请求过程为: 用户进程发起请求,内核接受到请求后,从I/O设备中获取数据到buffer中,再将buffer中的数据copy到用户进程的地址空间,该用户进程获取到数

(转)浅谈 Linux 内核无线子系统

前言 Linux 内核是如何实现无线网络接口呢?数据包是通过怎样的方式被发送和接收呢? 刚开始工作接触 Linux 无线网络时,我曾迷失在浩瀚的基础代码中,寻找具有介绍性的材料来回答如上面提到的那些高层次的问题. 跟踪探索了一段时间的源代码后,我写下了这篇总结,希望在 Linux 无线网络的工作原理上,读者能从这篇文章获得一个具有帮助性的概览. 1.全局概览 在开始探索 Linux 无线具体细节之前,让我们先来把握一下 Linux 无线子系统整体结构.如图1,展示了 Linux 无线子系统各个模

浅谈Linux下Makefile编写

浅谈Linux下Makefile的编写 前言:本文简要介绍Makefile文件的编写规范,结合具体项目中的应用进行讲解. 具体代码地址: https://github.com/AnSwErYWJ/DogFood/blob/master/Makefile 简介 Make工具最主要也是最基本的功能就是通过makefile文件来描述源程序之间的相互关系并自动维护编译工作.而makefile 文件需要按照某种语法进行编写,文件中需要说明如何编译各个源文件并连接生成可执行文件,并要求定义源文件之间的依赖关