Linux内核RCU(Read Copy Update)锁简析

在非常早曾经,大概是2009年的时候。写过一篇关于Linux RCU锁的文章《RCU锁在linux内核的演变》,如今我承认。那个时候我尽管懂了RCU锁,可是我没有能力用一种非常easy的描写叙述把Linux的实现给展示出来,有道是你能给别人用你自己的方式非常简洁地描写叙述清楚,你才是真正的精通它。否则,无异于背诵。换个说法,假设你在被面试。在短时间内靠嘴说给面试官,且他还要能听明白,就说明自己真的懂了,这样的时候,是不会给你机会分析源码的,也不可能让你背诵源码。
       时隔五年多,最近又碰到了这个话题,我不能自诩自己对RCU锁是多么精通,可是起码,和2009年相比,我确实有所进步,因此在这个台风肆虐的次日,我尝试着用我自己的方式描写叙述一下Linux对RCU锁的一种实现方式,作为《RCU锁在linux内核的演变》这篇文章的补充。本文不配图,没代码,仅仅是文字。
声明:假设你还不知道RCU锁是什么。请自行baidu,本文不再赘述概念。可是这也就等于说,假设我自己有一天忘记了RCU。我也不能指望从本文中得到不论什么帮助。我总是这样,不是吗?能力在忘不在记。得其义而忘其形。

RCU要素

RCU锁的要素包含

读标志

假设一个Reader企图占领一把RCU锁,它是不须要付出不论什么代价的,仅仅须要设置一个标志,让外界知道有Reader在占领这把RCU锁。多个Reader能够共同持有一把RCU锁。

写时拷贝

假设有一个Write企图更新RCU锁所保护的数据,那么它会首先查看该RCU锁的读标志,假设有该标志,说明有最少一个Reader持有了该RCU锁。它须要对原始数据make a copy,写这个副本并将更新过的副本保存在某处,等待时机用该副本更新原始数据。

更新时机

这个时机就是用副本更新原始数据的时间点。这个时间点怎样确定是RCU锁实现的算法核心,它直接能够确定全部的数据结构。确切来讲,Writer必须waitting for all readers leaving,方可Update原始数据,问题是,它是怎么知道全部的Reader都离开了呢?

Linux内核对RCU锁的几种实现

1.原始实现-利用抢占禁止

Linux内核于2.6内核引入了RCU锁的概念。在第一个版本号中,它利用了抢占禁止的方式来标志有Reader持有RCU锁,这意味着期间不能发生task切换(指的是task_struct所代表的sched entity切换)。

那么全部Reader均已经释放RCU锁的标志就是。task切换了,因此非常easy,用副本Update原始数据的时机就是task切换时。
       全部的Write会将自己写的副本挂在一个list上,在task切换的时候会touch这个list,假设该list非空,则遍历每个元素。Update原始数据。

评价[该部分与实现无关,纯形而上的,能够忽略]

这就是第一版的原始实现。它是否合理姑且不论。确实。它能够工作。可是:
a.它这样的实现是否会影响调度子系统的时延
b.因为禁用抢占,抢占粒度变粗,对交互性是否会有影响
c.对CPU间的task负载均衡的影响呢
我们发现,因为RCU的这个实现不是靠自身机制实现的,它不可避免地会影响到系统的核心机制。比方调度,负载均衡等,这意味着它不能长久。也无法经历复杂的演变。因为随着它在这条路上的逐步演进,对系统核心机制的影响将越来越大,故而,它必须从系统层面剥离出来。确实,它也是这么做的。这就是第二代RCU实现-可抢占RCU锁。

2.新实现-利用阶段计数器

须要一种更加有效的方式来标志Reader已经持有锁-第一要素读标志,而且这个标志要尽可能精确,且不能使用系统核心的机制,要做成全然封闭的闭环,不依靠外部当然也就不会影响外部。
       纯天然的想法就是使用计数器,每一把RCU持有一个Reader计数器,一旦有Reader前来持锁。仅仅须要一个原子操作,将该计数器加1就可以,Writer写数据时。发现计数器不是0就意味着须要make a copy了-第二要素写时拷贝(COW-Copy On Write)。

如今的问题是第三要素,Writer怎么知道全部的Reade都已经将锁释放了呢??
       纯天然的想法就是在某个Reader释放锁的时候,计数器减1。当计数器又一次变为0的时候。这就是副本更新原始数据的时机。

确实是这样,可是依照持锁和解锁的分布看,它们应该是均等的,这意味着计数器的值会在一个期望值上下波动,变成0的希望及其渺茫,因此须要引入还有一个參量,即阶段。
       将唯一的那个RCU计数器分裂为两个计数器:old readers和new readers。

太初,任选某一个时刻,将RCU锁当前的计数器(称为原始计数器)值复制一份存入old readers,计数器清0,原始计数器改称为new readers。复制结束的当下,new readers计数器为0,old readers计数器为现阶段持有锁的reader的数量。而且持锁者task(即task_struct)与RCU锁之间保持关联(难道不是一个task_struct字段能够搞定的吗?)。task永远知道自己是new reader还是old reader。

此时,就能够明白定义lock和unlock的行为了:
lock--设置自己的task为new reader,将RCU的new reader计数器加1。

unlock--获取自己的task是new reader还是old reader,将自己所在的reader计数器减1。
此时非常明白的事实是。old reader计数器总是会递减而不会递增,而new reader不但会递增也会递减。这样,选择Update的时机也非常明白了,那就是,old reader计数器变为0,这个时刻,就该将全部的副本覆盖原始数据了。
       如今总结全部的三个要素:

读标志

为该RCU锁的new reader计数器加1

写时拷贝

假设该RCU锁的old reader计数器不为0,则运行写时复制。

更新时机

每次unlock操作,都会将本task的reader计数器(或者是new reader。或者是old reader)减1。一旦该RCU锁的old reader计数器变成0。则运行全部的Update操作。

评价[该部分与实现无关,纯形而上的。能够忽略]

持有RCU锁的reader。能够睡眠,能够被抢占。能够调度到别的CPU上,全然是封闭的,和系统其他的机制无关。

然而。我一直在思考一个更好的实现,仅仅因疯子不给力。!

3.RCU Tree实现(实在是没有2好)

今天实在没有时间了,要出去。兴许补充。

时间: 2024-10-06 17:50:15

Linux内核RCU(Read Copy Update)锁简析的相关文章

Linux内核RCU(Read Copy Update)锁简析-前传

如果你用Linux perf tool的top命令做热点纠察时,你会发现,前10名嫌疑犯里面肯定有好几个都是锁!在进行并行多处理时,不可避免地会遇到锁的问题,这是不可避免的,因为这一直以来也许是保护共享数据的唯一方式,被保护的区域就是临界区.而我们知道,锁的开销是巨大的,因为它不可避免地要么等待,要么让别人等待,然而这并不是开销的本质,开销的本质在于很多锁都采用了"原子操作"这么一个技术,如此一个原子操作会对总线或者cache一致性造成很大的影响,比如要对一个变量进行原子加1,不要认为

linux 内核 RCU机制详解

RCU(Read-Copy Update)是数据同步的一种方式,在当前的Linux内核中发挥着重要的作用.RCU主要针对的数据对象是链表,目的是提高遍历读取数据的效率,为了达到目的使用RCU机制读取数据的时候不对链表进行耗时的加锁操作.这样在同一时间可以有多个线程同时读取该链表,并且允许一个线程对链表进行修改(修改的时候,需要加锁).RCU适用于需要频繁的读取数据,而相应修改数据并不多的情景,例如在文件系统中,经常需要查找定位目录,而对目录的修改相对来说并不多,这就是RCU发挥作用的最佳场景.

linux 内核的另一个自旋锁 - 读写锁

除spinlock外,linux 内核还有一个自旋锁,名为arch_rwlock_t.它的头文件是qrwlock.h,包含在spinlock.h,头文件中对它全称为"Queue read/write lock".这个锁只使用了两个成员变量就实现了读写锁.一个spinlock,以及一个整形锁变量.而spinlock就是这个Queue. 锁的原理是,当没有写意愿或写锁使用时,任意读锁可以并发.当有写意愿或写锁使用时,一切的读锁和写锁都必须进行排队. arch_rwlock_t的锁变量虽然只

Linux中 /proc/[pid] 目录各文件简析

Linux 内核提供了一种通过 proc 文件系统,在运行时访问内核内部数据结构.改变内核设置的机制.proc 文件系统是一个伪文件系统,它只存在内存当中,而不占用外存空间.它以文件系统的方式为访问系统内核数据的操作提供接口. 用户和应用程序可以通过 proc 得到系统的信息,并可以改变内核的某些参数.由于系统的信息,如进程,是动态改变的,所以用户或应用程序读取 proc 文件时,proc 文件系统是动态从系统内核读出所需信息并提交的. 下面列出的这些文件或子文件夹,并不是都是在你的系统中存在,

JUC锁简析(基于源码的详解后续会陆续发出)

张图说明下要分享的内容: 01. Lock接口 JUC包中的 Lock 接口支持那些语义不同(重入.公平等)的锁规则.所谓语义不同,是指锁可是有"公平机制的锁"."非公平机制的锁"."可重入的锁"等等. "公平机制"是指"不同线程获取锁的机制是公平的", 而"非公平机制"则是指"不同线程获取锁的机制是非公平的","可重入的锁"是指同一个锁能够被一个

Linux内核的同步机制---自旋锁

自旋锁的思考:http://bbs.chinaunix.net/thread-2333160-1-1.html 近期在看宋宝华的<设备驱动开发具体解释>第二版.看到自旋锁的部分,有些疑惑.所以来请教下大家. 以下是我參考一些网络上的资料得出的一些想法,不知正确与否.记录下来大家讨论下: (1) linux上的自旋锁有三种实现: 1. 在单cpu.不可抢占内核中,自旋锁为空操作. 2. 在单cpu,可抢占内核中,自旋锁实现为"禁止内核抢占".并不实现"自旋"

数据库锁简析(转载)

一.前言 数据库大并发操作要考虑死锁和锁的性能问题.这里做个简明解释,为下面描述方便,这里用T1代表一个数据库执行请求,T2代表另一个请求,也可以理解为T1为一个线程,T2 为另一个线程.T3,T4以此类推.下面以SQL Server为例. 二.锁的种类 共享锁(Shared lock). 例1: ---------------------------------------- T1: select * from table (请想象它需要执行1个小时之久,后面的sql语句请都这么想象) T2:

Linux驱动之平台设备驱动模型简析(驱动分离分层概念的建立)

Linux设备模型的目的:为内核建立一个统一的设备模型,从而有一个对系统结构的一般性抽象描述.换句话说,Linux设备模型提取了设备操作的共同属性,进行抽象,并将这部分共同的属性在内核中实现,而为需要新添加设备或驱动提供一般性的统一接口,这使得驱动程序的开发变得更简单了,而程序员只需要去学习接口就行了. 对于整个设备总线驱动模型的样子,如下图.简单来说,bus 负责维护注册进来的devcie 与 driver,每注册进来一个device 或者 driver 都会调用 Bus->match 函数

Linux运维学习之文件属性block简析

Linux运维基础学习中,关于block了解多少呢?咱们今天就来简单了解下. 1.block是怎么来的? 是格式化创建文件系统的时候诞生的. 2.block是什么意思? 实际用来存放数据的地方 3.block有什么特点? 1)存放数据的 2)block的大小默认4KB (centos 6.x) 3)文件比较大,需要使用多个block;文件比较小的时候(1KB)剩余的空间会被浪费. 4.如何查看? #查看 系统中block的使用情况=====磁盘空间的使用情况 [[email protected]