Linux 内核设计了多种锁机制,比如 读写锁、自旋锁 和 信号量 等。为什么要设计这么多锁机制呢?这是因为不同的锁机制适用于不同的场景,比如 读写锁 适用于读多写少的场景;而 信号量 适用于进程长时间占用锁,并且允许上下文切换的场景。

本文主要介绍一种 Linux 内核中性能非常高的锁机制:RCU锁机制。

RCU 是 Read Copy Update 的缩写,中文意思是 读取、复制、更新。RCU锁机制 就是通过读取、复制和更新这三个操作来实现锁功能。在介绍 RCU锁 之前,我们先来看看下面的实例。

struct foo {int  a;char b;long c;};struct foo *gbl_foo;void foo_read(void)
{foo *fp = gbl_foo;if (fp != NULL)do_something(fp->a, fp->b, fp->c);
}void foo_update(foo *new_fp)
{foo *old_fp = gbl_foo;gbl_foo = new_fp;free(old_fp);
}

假如有线程 A 和线程 B 同时执行 foo_read(),而另线程 C 执行 foo_update(),那么会出现以下几种情况:

  1. 线程 A 和线程 B 同时读取到旧的 gbl_foo 的指针。
  2. 线程 A 和线程 B 同时读取到新的 gbl_foo 的指针。
  3. 线程 A 和线程 B 有一个读取到新的 gbl_foo 的指针,另外一个读取到旧的 gbl_foo 的指针。

如果线程 A 或线程 B 在读取旧的 gbl_foo 数据还没完成时,线程 C 释放了旧的 gbl_foo 指针,那么将会导致程序奔溃。

也就是说,在不加锁的情况下,对公共数据的访问是危险的。当然,我们可以使用 读写锁、信号量 或者 自旋锁 来对公共数据进行保护。但这些锁都有各自的弊端,比如:

  • 读写锁:对于写操作较多的场景,性能会非常差。
  • 信号量:上锁失败的进程将会切换上下文,从而导致系统的性能下降。
  • 自旋锁:获得锁的 CPU 将会阻塞其他 CPU 的允许,从而导致系统的并行能力下降。

那么有没有一种锁机制,对系统的性能影响不大的呢?所以,Linux 内核黑客们就创造出 RCU锁。

RCU锁原理

如果能够保证所有使用某个公共数据的线程不再使用它,那么就可以安全删除此公共数据。

1. 宽限期

在上面的例子中,如果能够保证线程 A 和线程 B 不再使用旧数据,那么线程 C 就能安全删除旧数据。

如下图所示(旧数据对应对象A,新数据对应对象B):

rcu-timeline

从上图的时间线可以看出,线程 A 和线程 B 从 glb_foo 指针获取的都是对象 A 的引用。

提示:因为 glb_foo 指针在时间点 B 才被替换成对象 B,而线程 A 和线程 B 都是在时间点 B 前获取 glb_foo 指针指向的对象,所以它们获取到的都是对象 A 的引用。

而在 安全点 后,线程 A 和线程 B 便不再使用旧数据(对象A)。所以此时,线程 C 便可以安全释放旧数据(对象A)。

线程 A 和线程 B 使用旧数据的这段期间,被称为 宽限期。如下图所示:

grace-period

所以,RCU锁 的核心思想就是怎么确定 宽限期。因为确定宽限期后,就可以随心所欲地释放旧数据。

资料直通车:Linux内核源码技术学习路线+视频教程内核源码

学习直通车:Linux内核源码内存调优文件系统进程管理设备驱动/网络协议栈

2. 宽限期确认

RCU锁 的原理虽然比较简单,但是实现却有点小复杂,主要是因为 宽限期 的确定比较麻烦。

为了能够确认 宽限期,使用 RCU 锁时有以下限制:

  • 使用 RCU 锁前,必须禁止内核抢占。
  • 在 RCU 锁保护的临界区中,不能使用可能触发调度的函数(如不能调用 alloc_pages 函数)。

由于在 RCU 临界区是禁止调度的,所以如果 CPU 发生了调度,就可以确定当前线程已经退出了临界区(也就是说当前线程不再引用旧对象)。如果所有的 CPU 都至少发生过一次调度,那么也就说明没有任何线程引用旧对象,此时就可以安全释放旧对象了。

所以,RCU 锁的核心原理是:在释放旧对象前,必须等待所有 CPU 核心至少调度一次。如下代码所示:

void foo_update(struct foo *new_fp)
{// 1. 将 gbl_foo 指向新对象spin_lock(&foo_mutex);foo *old_fp = gbl_foo;gbl_foo = new_fp;spin_unlock(&foo_mutex);// 2. 等待所有 CPU 核心至少调度一次synchronize_kernel();// 3. 释放旧对象free(old_fp);
}

foo_update() 函数释放旧对象的步骤如下:

  1. 使用新对象替换旧对象,在替换前必须使用自旋锁进行保护,避免多个 CPU 同时修改 gbl_foo 指针的值。
  2. 等待所有 CPU 核心至少调度一次。
  3. 由于所有 CPU 核心都至少调度过一次,那么可以确认现在没有线程引用旧对象,所以可以安全释放旧对象。

3. RCU临界区

通过前面的分析可知,在 RCU 临界区中是不能发生调度的。要保证临界区不发生调度,首先要确保在临界区中不能调用可能触发调度的函数,如:alloc_pages()。这点需要 RCU 使用者自己保证。

另外一点要保证的是,内核不能发生抢占,这点可以通过调用 preempt_disable() 函数实现。内核定义了一个名为 rcu_read_lock() 的宏,如下所示:

#define rcu_read_lock()  preempt_disable()

可以看出, rcu_read_lock() 宏其实就是 preempt_disable() 函数的别名。所以,使用 RCU 锁时,可以使用 rcu_read_lock() 宏对临界区进行保护。

当退出临界区时,需要调用 rcu_read_unlock() 把内核抢占打开。rcu_read_unlock() 的定义如下:

#define rcu_read_unlock() preempt_enable()

可以看出,rcu_read_unlock() 宏就是 preempt_enable() 的别名。

所以,当我们使用 RCU 锁对临界区进行保护时,必须将需要保护的代码放置在 rcu_read_lock() 和 rcu_read_unlock() 之间,如下所示:

void foo_read(void)
{// 1. 保护临界区rcu_read_lock();foo *fp = gbl_foo;if (fp != NULL)do_something(fp->a, fp->b, fp->c);// 2. 退出临界区rcu_read_unlock();
}

一文深入分析|RCU原理相关推荐

  1. 带你一文解析RCU锁机制原理

    原理 Read Copy Update 读(Read):读者不需要获得任何锁就可访问RCU保护的临界区: 拷贝(Copy):写者在访问临界区时,写者"自己"将先拷贝一个临界区副本, ...

  2. (转载)深入分析HDFS原理及读写流程

    一.架构体系 1.1.什么是HDFS? HDFS即Hadoop Distributed File System的简称,采用Master/Slave主从结构模型来管理数据.在设计上采用了分而治之的思想, ...

  3. RCU 原理与实现分析

    RCU锁谈起来许多人云里雾里,听着也觉得高大上,竟然可以不加锁就实现加锁的效果.此外,网上关于RCU实现原理的完整闭环逻辑分析,比较少,大多停留在阻塞写者,等待读者退出临界区的程度.而对如何实现延迟调 ...

  4. java虚拟机线程调优与底层原理分析_啃碎并发(七):深入分析Synchronized原理...

    原标题:啃碎并发(七):深入分析Synchronized原理 前言 记得开始学习Java的时候,一遇到多线程情况就使用synchronized,相对于当时的我们来说synchronized是这么的神奇 ...

  5. 分布式服务架构精讲pdf文档:原理+设计+实战,(收藏再看)

    前言 如果你期待对分布式系统有一个更全面的认识,想要了解各个技术在分布式系统中如何应用.分别解决哪些问题.有怎样优秀的实现,推荐阅读.收藏本篇. 分布式.微服务几乎是现在的技术人员必须要了解的架构方向 ...

  6. 深入分析Synchronized原理(阿里面试题)

    我们学习Java的时候,一遇到多线程情况就使用synchronized,相对于当时的我们来说synchronized是这么的神奇而又强大,那个时候我们赋予它一个名字"同步",也成为 ...

  7. Userspace RCU原理

    ##总览 urcu全称user-space read-copy update即用户态RCU,它提供了与内核RCU相似的功能,使得在多核多线程并发访问共享数据时,reader线程不用阻塞于writer线 ...

  8. VS平台账号注册机--源码--详细文档--分析-原理-实现

    VS平台账号注册机的原理和实现 Kimm King Kimmking@163.com 2008年12月27日 摘要:通过分析vs平台的网页注册方式,分析验证码,使用c#模拟表单提交,实现账号自动注册. ...

  9. 深入分析 Synchronized 原理

    Synchronized 简介 Synchronized 是 Java 中的关键字,是一种同步锁.在多线程编程中,有可能会出现多个线程同时争抢同一个共享资源的情况,这个资源一般被称为临界资源.这种共享 ...

最新文章

  1. 2021-05-25 传递函数阶跃响应指标的matlab计算
  2. TCP 通信过程中各步骤的状态
  3. centos7安装golang
  4. 汉诺塔游戏的python实现——递归函数
  5. 将not exists更改为外连接
  6. 如何精准鉴别菜鸟和老手程序员 网友:精辟!
  7. B站 React教程笔记day1(4)调色板案例
  8. 升级centos6.5系统的gcc为4.8.5的简易步骤
  9. 计算机软件技术职业工作规划,软件技术职业规划书.docx
  10. Ant安装及环境配置
  11. ping C语言实现
  12. docker-redis配置文件修改
  13. 简单的有监督学习实例——简单线性回归
  14. 西湖论剑2020-BrokenSystems
  15. 我的专业我的梦作文计算机,我的创新我的梦优秀作文
  16. vue2路由手动创建二级路由路由传参路由守卫打包上线
  17. mobilenet压缩
  18. xp无线网卡开启的服务器,笔记本xp系统开启无线网卡的方法
  19. 联想微型计算机设置从u盘启动,联想笔记本设置u盘为第一启动项教程
  20. 漫画:三分钟学习一道位运算的面试题,万一遇到了呢?

热门文章

  1. AJAX 和 JSON学习笔记
  2. 错误:The ‘pycocotools>=2.0‘ distribution was not found and is required by the application
  3. [蓝桥杯][基础练习]Sine之舞、Python
  4. 基于三菱PLC和MCGS组态农田智能灌溉系统
  5. 疫情时代下,普通人如何在不确定的世界活得好一点?
  6. 科技新品 | 卡西欧紧凑型G-SHOCK;三星全新智能手机内存组合;佳能单张纸彩色印刷系统新品...
  7. openwrt 端口映射
  8. jQuery源码分析之实例find和filter方法的区别七问
  9. 糗事百科-动态获取全部页码数并爬取图片
  10. Error:java: java.lang.OutOfMemoryError:insufficient memory,Java heap space, GC overh