《Linux 内核设计与实现》10. 内核同步方法

这篇具有很好参考价值的文章主要介绍了《Linux 内核设计与实现》10. 内核同步方法。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

原子操作

原子操作:可以保证指令以原子的方式执行,即执行过程不被打断。

原子整数操作

整数的原子操作只针对 atomic_t 类型。因为:

  • 让原子函数只接收 atomic_t 类型的操作数,可以确保原子操作只与这种特殊类型数据一起使用。同时这也保证了该类型的数据不会被传递给任何非原子函数。
  • 使用 atomic_t 类型确保编译器不对相应的值进行访问初始化 —— 这点使得原子操作最终接收到正确的内存地址,而不只是一个别名。

atomic_t 类型定义在 include/linux/types.h:

typedef struct {
	volatile int counter;
} atomic_t;

在 Linux 上 atomic_t 整数类型都是 32 位,其中数据位为高 24 位,低 8 位嵌入了一个锁,因为 SPARC 体系结构对原子操作缺乏指令级支持,所以只能利用该锁来避免对原子类型数据的并发访问。因此在 SPARC 机器上只能用 24 位,在其它机器上可以用 32 位。

(貌似已经在 SPARC 机器上只能使用 24 位的问题已经被解决了)

《Linux 内核设计与实现》10. 内核同步方法

原子操作在 arch/alpha/include/asm/atomic.h 中:

《Linux 内核设计与实现》10. 内核同步方法

原子操作通常是内联函数,往往是通过内嵌汇编指令来实现的。若某个函数本就具备原子性,那么通常会被定义为一个宏。

在编写代码时,尽量使用原子操作,而不是使用复杂的加锁机制。相对于锁机制,原子操作可以给系统带来较小的开销,对高速缓存行的影响也小。

64 位原子操作

因为移植性的问题,atomic_t 变量无法在体系结构之间改变。因此 atmoic_t 类型即便在 64 位下也是 32 位的,要使用 64 位的,则要使用 atomic64_t 类型,其功能与 32 位无异。

#ifdef CONFIG_64BIT // 判断系统是不是 64 位的
typedef struct {
	volatile long counter;
} atomic64_t;
#endif

《Linux 内核设计与实现》10. 内核同步方法

开发者应该使用 32 位的 atomic_t 类型,因为为了可以在各体系结构之间可以移植代码。

原子位操作

路径:arch/alpha/include/asm/bitops.h

《Linux 内核设计与实现》10. 内核同步方法

非原子位函数在其原子位函数名字前多了两条下划线,例如 test_bit() 的非原子位函数为 __test_bit()。

自旋锁

  • Linux 内中最常见的锁。

  • 最多只能被一个线程持有。

  • 若一个线程试图得到一个已被持有的锁,那么该线程就会一直循环等待该锁被释放为止。

  • 在任意时间,自旋锁都可以防止多于一个线程同时进入临界区。

  • 自旋锁因为会循环等待锁,因此特别浪费处理器时间,所有自旋锁不该被长时间占有。

    也可以让请求线程休眠,直到锁重新可用为止。(这会带来一定的开销,如两次上下文切换)

  • 自旋锁的设计初衷:在短期内进行轻量级加锁。

路径:arch/alpha/include/asm/spinlock.h

也可能是这个:include/linux/spinlock.h

自旋锁不可递归:你试图得到一个你持有的锁,必须自旋等待,可是你处于自旋等待中,因此无法释放锁,导致给自己锁死了。
根据此情况,可知在中断使用自旋锁时,一定要先关闭中断。

CONFIG_DEBUG_SPINLOCK 配置选择为使用自旋锁的代码加入了许多调试检测的手段。

《Linux 内核设计与实现》10. 内核同步方法

读写自旋锁

  • 一个或多个读任务可以并发地持有读者锁(读者锁有多个)。
  • 用于写的锁最多只能被一个写任务持有(写者锁只有一个),且此时不能有并发的读操作。
  • 在中断在使用该锁时:
    • 读任务:无需关闭中断
    • 写任务:必须关闭有写操作的中断

《Linux 内核设计与实现》10. 内核同步方法

信号量

  • Linux 中的信号量是一种睡眠锁。
  • 一个任务试图得到一个已被占用的信号量时,信号量会将其推进一个等待队列,然后让其睡眠。直到那个信号量被释放,处于等待队列中的那个任务将被唤醒,并获得该信号量。
  • 相比于自旋锁,信号量拥有更好的处理器利用率,当却比自旋锁有更大的开销。

从信号量的睡眠特性得出的一些结论:

《Linux 内核设计与实现》10. 内核同步方法

计数信号量和二值信号量

  • 使用者数量(usage count)或数量(count):信号量同时允许持有的数量可以在声明信号量中指定。
  • 同一时刻,当只允许一个锁持有者时,信号量(count)为 1,这种情况下,被称为二值信号量或互斥信号量。
  • 数量设置为大于 1 时,即同一时刻最多允许 count 个锁持有者,这被称为计数信号量。

信号量支持的两个原子操作:

  • P() / down():对信号量减一来得到一个信号量。
  • V() / up():对信号量加一释放信号量。

信号量方法列表

路径:include/linux/semaphore.h

《Linux 内核设计与实现》10. 内核同步方法

读写信号量

位于:include/linux/rwsem.h

down_read_trylock() 和 down_write_trylock() 方法,若成功获得了信号量锁,返回非0,若信号量锁被争用,则返回0,这与普通信号量情况相反,要小心。

互斥体

  • 信号量是内核中唯一允许睡眠的锁。

    信号量适用于较复杂、未明情况下的互斥访问。简单的锁定,使用信号量并不方便,且信号量调试也不方便。

  • 互斥体是比信号量更简单允许休眠的锁。

  • 互斥体指的是任何可以睡眠的强制互斥锁,如计数是 1 的信号量。

  • 互斥体是一种互斥信号。

  • mutex 是简化版的信号量,因为不再需要管理任何使用计数。

  • 开启 mutex 的调试功能:CONFIG_DEBUG_MUTEXES

路径:include\linux\mutex.h

信号量和互斥体

互斥体和信号量很相似,应该优先使用 mutex,不到万不得已就别用信号量,一般只有很底层的代码才需要信号量。因此首选 mutex,若发现不能满足其约束条件,且没有别的选择时,再考虑选择信号量。

自旋锁和互斥体

《Linux 内核设计与实现》10. 内核同步方法

完成变量

在内核中一个任务需要发出信号通知另一个任务发生了某个特定事件,利用完成变量是使两个任务得以同步的简单方法。当一个任务要执行一些工作时,另一个任务就会在完成变量上等待。当这个任务完成工作后,会使用完成变量去唤醒在等待的任务。

完成变量仅仅只是代替信号量的一个简单方案。例如,当子进程执行或退出时,vfork() 系统调用使用完成变量唤醒父进程。

路径:include\linux\completion.h

《Linux 内核设计与实现》10. 内核同步方法

BLK:大内核锁

  • 是一个全局自旋锁。
  • 最初的目的是为了方便实现从 SMP 过度到细粒度加锁机制,这个过程当然是有效的,但是现在已经成了内核可扩展性的障碍。
  • 在内核中不鼓励使用 BKL。
  • 一个执行线程递归的请求锁,那么释放锁也必须以同样的次序进行。
  • BKL 更像是保护代码的,而不是保护数据。
  • 难以发现所有 BKL 的用户之间的关系。

BKL 的特性:

《Linux 内核设计与实现》10. 内核同步方法

顺序锁

  • 简单的锁机制,用于读写共享数据。
  • 该锁的依靠一个序列计数器实现。
  • 当数据被写入时,会得到一个锁,并且序列值增加。在读取数据前后,序列号都被读取。如果读取的序列号值相同,说明在读操作进行的过程中没有被写操作打断过(因为锁的初值是 0,所以写锁会使值成奇数,释放时成偶数)。
  • 如果读取的值是偶数,表面写操作没有发生。
  • seq 锁对写者更有利,只要没有其它写者,写锁总能被成功获得。
  • 读者不会影响写锁。
  • 挂起的写者会不断地使得读操作循环,直到不再有任何写者持有锁为止。

路径:include/linux/seqlock.h

《Linux 内核设计与实现》10. 内核同步方法

seq 锁使用场景:

《Linux 内核设计与实现》10. 内核同步方法

禁止抢占

  • 由于内核是抢占的,意味着一个任务与抢占任务可能会在同一个临界区内。
  • 为了避免这种情况,内核抢占代码使用自旋锁作为非抢占区域的标记。
  • 如果一个自旋锁被持有,内核便不能进行抢占。
  • 在多处理器中,若自旋锁未被持有,内核又是抢占式的,那么新调度任务就可能访问同一个变量。
  • 即便是单处理器,变量也可能被伪并发访问。

《Linux 内核设计与实现》10. 内核同步方法

顺序和屏障

  • 在处理多处理器之间或硬件之间的同步问题时,需要在程序中以指定的顺序发出读指令和写指令。
  • 但编译器和处理器为了提高效率,可能对读和写重新排序。(x86 处理器不会对写重新排序)
  • 所有可能重新排序的处理器都提供了机器指令来确保顺序执行。同样也确保编译器不会对给定点周围的指令序列重新排序。

重新排序的例子:

a = 1;
b = 2;

重新排序导致可能会在 a 中存放新的值之前就在 b 中存放新值。

编译器和处理器都看不出两者之间的关系。编译器会在编译时按这种顺序编译,这种顺序会是静态的,编译的目标代码就只把 a 放在 b 之前。但是处理器会重新动态排序,因为处理器在执行指令期间,会在取指令和分配时,把表面看似无关的指令按自认为最好的顺序排列。大多数情况下,这样的排序是最佳的。

处理器和编译器绝不会重新排序的情况:

a = 1;
b = a;

书中后面这小段我没懂,此处略…

《Linux 内核设计与实现》10. 内核同步方法文章来源地址https://www.toymoban.com/news/detail-440844.html

到了这里,关于《Linux 内核设计与实现》10. 内核同步方法的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Linux内核源码分析 (B.4) 深度剖析 Linux 伙伴系统的设计与实现

    Linux内核源码分析 (B.4) 深度剖析 Linux 伙伴系统的设计与实现 在上篇文章 《深入理解 Linux 物理内存分配全链路实现》 中,笔者为大家详细介绍了 Linux 内存分配在内核中的整个链路实现: image.png 但是当内核执行到 get_page_from_freelist 函数,准备进入伙伴系统执行具体内存分配

    2024年02月07日
    浏览(34)
  • 深入理解Linux内核网络——内核与用户进程协作之同步阻塞方案(BIO)

    系列文章: 深入理解Linux网络——内核是如何接收到网络包的 深入理解Linux网络——内核与用户进程协作之同步阻塞方案(BIO) 深入理解Linux网络——内核与用户进程协作之多路复用方案(epoll) 深入理解Linux网络——内核是如何发送网络包的 深入理解Linux网络——本机网络

    2024年02月13日
    浏览(36)
  • 【Linux内核解析-linux-5.14.10-内核源码注释】内核启动kernel_init解释

    static int __ref kernel_init(void *unused) : 声明一个静态整型函数 kernel_init() ,该函数不会被其他文件访问,使用 __ref 标记表示该函数是可重定位的,并且该函数不需要任何参数。 wait_for_completion(kthreadd_done); : 等待 kthreadd 线程完成初始化, wait_for_completion() 函数会阻塞当前进程,直到

    2024年02月02日
    浏览(53)
  • 【嵌入式环境下linux内核及驱动学习笔记-(10-内核内存管理)】

    对于包含MMU(内存管理单元)的处理器而言,linux系统以虚拟内存的方式为每个进程分配最大4GB的内存。这真的4GB的内存空间被分为两个部分–用户空间 与 内核空间。用户空间地地址分布为0~3GB,剩下的3 ~ 4GB 为内核空间。如下图。 用户进程通常只能访问用户空间的虚拟地址

    2024年02月11日
    浏览(40)
  • 10 月发布,Ubuntu 23.10 已升级到 Linux Kernel 6.3 内核

    导读 Canonical 于近日宣布,代号为 Mantic Minotaur 的 Ubuntu 23.10 发行版本已升级基于 Linux Kernel 6.3 内核。 Canonical宣布,代号为 Mantic Minotaur 的 Ubuntu 23.10 发行版本已升级基于 Linux Kernel 6.3 内核。 Ubuntu 23.10 于今年 4 月下旬进入开发阶段,初期基于和 Ubuntu 23.04(Lunar Lobster)相同的

    2024年02月12日
    浏览(28)
  • 文件IO_文件读写(附Linux-5.15.10内核源码分析)

    目录 1.什么是文件偏移量? 1.1 文件偏移量介绍 1.2 文件偏移量重点 1.3 文件偏移量工作原理 2.文件偏移量设置 2.1 lseek函数 2.2 lseek内核源码分析 3.写文件 3.1 write函数 3.2 write内核源码分析 4.读文件 4.1 read函数 4.2 read内核源码分析 5.文件读写,文件偏移量设置示例代码 在介绍文

    2024年02月16日
    浏览(46)
  • 银河麒麟V10(内核Linux)设置有线连接IP地址以及查看

    设置有线连接的IP地址步骤如下:   查看IP地址: hostname -i  得到回环地址127.0.1.1   hostname -I  得到具体的网卡IP ifconfig -a 查看所有 如图:  

    2024年02月11日
    浏览(58)
  • win10日程怎么同步到安卓手机?电脑日程同步到手机方法

    在如今快节奏的生活中,高效地管理时间变得至关重要。而对于那些经常在电脑上安排日程的人来说,将这些重要的事务同步到手机上成为了一个迫切的需求。因为目前国内使用win10系统电脑、安卓手机的用户较多,所以越来越多的职场人士想要知道,win10日程怎么同步到安卓

    2024年02月15日
    浏览(25)
  • Win10电脑关闭OneDrive自动同步的方法

    在 Win10 电脑操作过程中,用户想要关闭OneDrive的自动同步功能,但不知道具体要怎么操作?首先用户需要打开OneDrive,然后点击关闭默认情况下将文档保存到OneDrive选项保存,最后关闭在这台电脑上同步设置保存就好了。接下来小编将给大家带来取消掉OneDrive自动同步功能的方

    2024年01月17日
    浏览(31)
  • Linux系统之升级内核版本方法

    确保yum仓库的正常,本次实践用的是阿里的镜像源。 直接更新内核版本,此方法适用于更新内核补丁。 重启服务器 检查内核版本 重启完成,发现内核版本已经升级为【3.10.0-1160.83.1.el7.x86_64】 ELRepo是一个为Linux提供驱动程序和内核映像的存储库,这里的内核大版本升级方案就

    2024年02月06日
    浏览(54)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包