Linux 锁机制(3)之自旋锁

  • 1. 自旋锁
    • 1.1 两种锁
    • 1.2 自旋锁
    • 1.3 自旋名字来源:自旋锁一直循环等待,直到获取锁为止。
    • 1.4 自旋锁优点:
  • 2 自旋锁特点/使用:
    • 2.1 临界区要小(否则出现死等,浪费cpu)
    • 2.2 线程获取自旋锁之前,要禁止当前处理器上的中断
    • 2.3 自旋锁是不可递归的
    • 2.4 由于不睡眠(忙等),自旋锁可以在中断上下文使用。
  • 3. spinlock_t 源码学习
    • 3.1 spinlock 源码文件解析
    • 3.2 自旋锁API
    • 3.3 自旋锁变体 API
    • 3.4 自旋锁伪代码实现
  • 4. 扩展:自旋锁在处理中断处理下半部时注意事项:
    • 4.1 进程上下文在对共享数据加锁前要禁止下半部的执行,解锁时再允许下半部的执行
    • 4.2 下半部在对共享数据加锁前要禁止中断处理(上半部),解锁时再允许中断的执行
    • 4.3 同类tasklet中的共享数据不需要保护
    • 4.4 同一个处理器上不会有tasklet相互抢占的情况
    • 4.5 同一个处理器上不会有软中断互相抢占的情况

1. 自旋锁

1.1 两种锁

内核当发生访问资源冲突的时候,可以有两种锁的解决方案选择:

  1. 一个是原地等待,如自旋锁
    特点:不会引起调用者睡眠,而是一直循环查看该锁是否释放
  2. 一个是挂起当前进程,调度其他进程执行,如互斥锁,信号量等
    特点:如果资源已经被占用,调用线程则进入睡眠等待

1.2 自旋锁

1.自旋锁简单说就是:
当一个线程获取了锁之后,其他试图获取这个锁的线程一直在循环等待获取这个锁,直至锁可用为止。

1.3 自旋名字来源:自旋锁一直循环等待,直到获取锁为止。

自旋锁一直循环等待,线程通过 busy-wait-loop 的方式来获取锁
即:当前的执行thread会不断的重新尝试直到获取锁进入临界区。

1.4 自旋锁优点:

  1. 自旋锁不会使线程状态发生切换,线程一直都是active的;
  2. 不会使线程进入阻塞状态,减少了不必要的上下文切换,执行速度快

2 自旋锁特点/使用:

2.1 临界区要小(否则出现死等,浪费cpu)

由于spin lock死等这种特性,因此它使用在那些代码不是非常复杂的临界区
如果临界区执行时间太长,那么不断在临界区门口“死等”的那些thread是多么的浪费CPU啊

(临界区代码短小,最好100行以内)
线程等待锁的时间很短,短到比线程两次上下文切换时间还少,说白了就是锁里操作的事情很简单。

2.2 线程获取自旋锁之前,要禁止当前处理器上的中断

防止获取锁的线程和中断形成竞争条件

例子说明:
比如:当前线程获取自旋锁后,在临界区中被中断处理程序打断,中断处理程序正好也要获取这个锁,
于是中断处理程序会等待当前线程释放锁,而当前线程也在等待中断执行完后再执行临界区和释放锁的代码。

2.3 自旋锁是不可递归的

递归的请求同一个自旋锁会自己锁死自己。

例子:
自旋锁可以在中断上下文中使用,但是试想一个场景:一个线程获取了一个锁,但是被中断处理程序打断,中断处理程序也获取了这个锁(但是之前已经被锁住了,无法获取到,只能自旋),中断无法退出,导致线程中后面释放锁的代码无法被执行,导致死锁。

2.4 由于不睡眠(忙等),自旋锁可以在中断上下文使用。

在linux kernel的实现中,经常会遇到这样的场景:共享数据被中断上下文和进程上下文访问,该如何保护呢?
如果只有进程上下文的访问,那么可以考虑使用semaphore或者mutex的锁机制,但是现在中断上下文也参和进来,那些可以导致睡眠的lock就不能使用了,这时候,可以考虑使用spin lock。

在中断上下文,是不允许睡眠的。所以,这里需要的是一个不会导致睡眠的锁——spinlock。

参见:操作系统二:上下文,进程上下文和中断上下文
https://blog.csdn.net/lqy971966/article/details/119103989

3. spinlock_t 源码学习

3.1 spinlock 源码文件解析

根据CPU体系结构,spinlock分为SMP版本和UP版本

[root@localhost home]# uname -a
Linux localhost.localdomain 3.10.0-862.el7.x86_64 #1 SMP Fri Apr 20 16:44:24 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux[root@localhost home]#
root@localhost home]# find / -name *spinlock*/* 针对 ARM 平台的 arch_spin_lock */
/usr/src/kernels/3.10.0-862.el7.x86_64/arch/x86/include/asm/qspinlock.h
/usr/src/kernels/3.10.0-862.el7.x86_64/arch/x86/include/asm/qspinlock_paravirt.h
/usr/src/kernels/3.10.0-862.el7.x86_64/arch/x86/include/asm/spinlock.h
/usr/src/kernels/3.10.0-862.el7.x86_64/arch/x86/include/asm/spinlock_types.h
/usr/src/kernels/3.10.0-862.el7.x86_64/drivers/hwspinlock
/usr/src/kernels/3.10.0-862.el7.x86_64/include/asm-generic/qspinlock.h
/usr/src/kernels/3.10.0-862.el7.x86_64/include/asm-generic/qspinlock_types.h
/usr/src/kernels/3.10.0-862.el7.x86_64/include/asm-generic/spinlock.h
/usr/src/kernels/3.10.0-862.el7.x86_64/include/config/arch/use/queued/spinlocks.h
/usr/src/kernels/3.10.0-862.el7.x86_64/include/config/paravirt/spinlocks.h
/usr/src/kernels/3.10.0-862.el7.x86_64/include/config/queued/spinlocks.h/* 以下是和体系结构无关的代码*/
/usr/src/kernels/3.10.0-862.el7.x86_64/include/linux/bit_spinlock.h
/usr/src/kernels/3.10.0-862.el7.x86_64/include/linux/hwspinlock.h
/usr/src/kernels/3.10.0-862.el7.x86_64/include/linux/rwsem-spinlock.h
/usr/src/kernels/3.10.0-862.el7.x86_64/include/linux/spinlock.h//spinlock.h 通用spin lock的接口函数声明,例如spin_lock、spin_unlock等
/usr/src/kernels/3.10.0-862.el7.x86_64/include/linux/spinlock_api_smp.h//SMP上的spin lock模块的接口声明
/usr/src/kernels/3.10.0-862.el7.x86_64/include/linux/spinlock_api_up.h//spinlock_api_up.h 这个头文件是non-debug版本的spin lock需要的
/usr/src/kernels/3.10.0-862.el7.x86_64/include/linux/spinlock_types.h   //spinlock_types.h 定义了通用spin lock的基本的数据结构(例如spinlock_t)
/usr/src/kernels/3.10.0-862.el7.x86_64/include/linux/spinlock_types_up.h//spinlock_types.h文件会根据系统的配置,如果是SMPUP则会include该头文件
/usr/src/kernels/3.10.0-862.el7.x86_64/include/linux/spinlock_up.h//spinlock_up.h 这个头文件是debug版本的SMP spin lock需要的
[root@localhost home]#

3.2 自旋锁API

方法               描述
spin_lock()         获取指定的自旋锁
spin_lock_irq()     禁止本地中断并获取指定的锁
spin_lock_irqsave() 保存本地中断的当前状态,禁止本地中断,并获取指定的锁
spin_unlock()       释放指定的锁
spin_unlock_irq()   释放指定的锁,并激活本地中断
spin_unlock_irqstore()  释放指定的锁,并让本地中断恢复到以前状态
spin_lock_init()    动态初始化指定的spinlock_t
spin_trylock()      试图获取指定的锁,如果未获取,则返回0
spin_is_locked()    如果指定的锁当前正在被获取,则返回非0,否则返回0

3.3 自旋锁变体 API

接口API的类型         spinlock中的定义    raw_spinlock的定义
定义spin lock并初始化 DEFINE_SPINLOCK     DEFINE_RAW_SPINLOCK
动态初始化spin   lock    spin_lock_init      raw_spin_lock_init
获取指定的spin lock      spin_lock           raw_spin_lock
获取指定的spin lock      spin_lock_irq       raw_spin_lock_irq同时disable本CPU中断保存本CPU当前的irq状态  spin_lock_irqsave   raw_spin_lock_irqsavedisable本CPU中断并获取指定的spin lock获取指定的spin lock     spin_lock_bh        raw_spin_lock_bh同时disable本CPU的bottom half释放指定的spin lock     spin_unlock         raw_spin_unlock
释放指定的spin lock      spin_unlock_irq     raw_spin_unock_irq同时enable本CPU中断释放指定的spin lock      spin_unlock_irqstore raw_spin_unlock_irqstore同时恢复本CPU的中断状态获取指定的spin lock        spin_unlock_bh      raw_spin_unlock_bh同时enable本CPU的bottom half尝试去获取spin lock,    spin_trylock        raw_spin_trylock如果失败,不会spin,而是返回非零值   判断spin lock是否是locked,    spin_is_locked  raw_spin_is_locked如果其他的thread已经获取了该lock,那么返回非零值,否则返回0

3.4 自旋锁伪代码实现

spinlock_t lock;
spin_lock_init(lock);   //初始化
……
spin_lock(&lock);   //加锁
/*  临界区 */
spin_unlock(&lock); //释放锁

4. 扩展:自旋锁在处理中断处理下半部时注意事项:

4.1 进程上下文在对共享数据加锁前要禁止下半部的执行,解锁时再允许下半部的执行

下半部处理和进程上下文共享数据时,由于下半部的处理可以抢占进程上下文的代码,
所以进程上下文在对共享数据加锁前要禁止下半部的执行,解锁时再允许下半部的执行。

4.2 下半部在对共享数据加锁前要禁止中断处理(上半部),解锁时再允许中断的执行

中断处理程序(上半部)和下半部处理共享数据时,由于中断处理(上半部)可以抢占下半部的执行,
所以下半部在对共享数据加锁前要禁止中断处理(上半部),解锁时再允许中断的执行。

4.3 同类tasklet中的共享数据不需要保护

同一种tasklet不能同时运行,所以同类tasklet中的共享数据不需要保护。

4.4 同一个处理器上不会有tasklet相互抢占的情况

不同类tasklet中共享数据时,其中一个tasklet获得锁后,不用禁止其他tasklet的执行,因为同一个处理器上不会有tasklet相互抢占的情况

4.5 同一个处理器上不会有软中断互相抢占的情况

同类型或者非同类型的软中断在共享数据时,也不用禁止下半部,因为同一个处理器上不会有软中断互相抢占的情况

参考:
https://blog.csdn.net/zhoutaopower/article/details/86598839
https://blog.csdn.net/zhoutaopower/article/details/86598839
https://www.cnblogs.com/sky-heaven/p/12802063.html

Linux 锁机制(3)之自旋锁相关推荐

  1. 【Linux驱动开发】016 自旋锁

    一.自旋锁简介 原子操作只能对整形变量或者位进行保护,但是,在实际的使用环境中怎么可能只有整形变量或位这么简单的临界区(所谓临界区就是共享数据段).比如,设备结构体变量就不是整型变量,我们对于结构体中 ...

  2. 关抢占 自旋锁_也说自旋锁

    前言 随着SMP处理器核越来越多 自旋锁是一种简单的轻量级的锁机制,理论上来说自旋锁可以小到一位,该位表示锁的状态,获取锁的线程尝试使用原子指令比较并交换该位,当锁不可用时反复自旋.自旋锁忙等待的机制 ...

  3. java 自旋锁与互斥锁_如何理解自旋锁和互斥锁?

    1.Python多线程run方法的中使用while循环时,如果在循环体没有使用停止程序机制,会一直运行下去.因此楼主如果想让编码方式得当,可以使用信号量或者其他变量机制通知循环体停止,或者判断队列是否 ...

  4. 并发编程中常见的锁机制:乐观锁、悲观锁、CAS、自旋锁、互斥锁、读写锁

    文章目录 乐观锁 VS 悲观锁 悲观锁 乐观锁 CAS CAS机制 ABA问题 CAS的优缺点 互斥锁 VS 自旋锁 互斥锁 自旋锁 对比及应用场景 读写锁 实现方式 读写锁 VS 互斥锁 乐观锁 V ...

  5. linux为什么要禁止内核抢占,为什么linux在内核代码持有自旋锁后禁用内核抢占?...

    我是 Linux的新手,正在阅读Rubini& amp;的Linux设备驱动程序.科比特.我对一个与自旋锁相关的陈述感到困惑;这本书说明 If a nonpreemptive uniproce ...

  6. 1.6的锁优化(适应性自旋/锁粗化/锁削除/轻量级锁/偏向锁)

    高效并发是JDK 1.6的一个重要主题,HotSpot虚拟机开发团队在这个版本上花费了大量的精力去实现各种锁优化技术,如适应性自旋(Adaptive Spinning).锁削除(Lock Elimin ...

  7. JUC多线程:synchronized锁机制原理 与 Lock锁机制

    前言: 线程安全是并发编程中的重要关注点,造成线程安全问题的主要原因有两点,一是存在共享数据(也称临界资源),二是存在多条线程共同操作共享数据.因此为了解决这个问题,我们可能需要这样一个方案,当存在多 ...

  8. c语言用户态锁使用,用户态自旋锁、读写自旋锁及互斥锁

    1.自旋锁 自旋锁最多可能被一个可执行线程所持有.一个被征用的自旋锁使得请求它的线程在等待锁重新可用时自旋(特别浪费处理器时间).所以自旋锁不应该被长时间持有. 自旋锁是不可递归的! (1)自旋锁相关 ...

  9. 悲观锁、乐观锁、间隙锁、死锁、自旋锁等

    悲观锁 在每一个数据被修改前认为会影响其他事务,持保守意见,对数据进行加锁,这就是悲观锁(Pessimistic Concurrency Control). 悲观锁主要分为共享锁(shared loc ...

  10. java线程锁机制_多线程之锁机制

    前言 在Java并发编程实战,会经常遇到多个线程访问同一个资源的情况,这个时候就需要维护数据的一致性,否则会出现各种数据错误,其中一种同步方式就是利用Synchronized关键字执行锁机制,锁机制是 ...

最新文章

  1. CoordinatorLayout+AppBarLayout顶部栏吸顶效果
  2. QoS是否提供更多带宽?-Vecloud
  3. 读取TFrecord
  4. matlab mex路径,使用matlab进行mex编译时的路径问题mexopts
  5. 重装Windows后修复Linux引导
  6. 代码管理_阿里巴巴自研代码管理平台技术解密
  7. php判断手机是安卓系统还是ios系统
  8. 厉害了!为了干掉 HTTP ,Spring团队又开源 nohttp 项目!
  9. anroid Remote Service 使用注意事项
  10. BZOJ3639 Query on a tree VII
  11. 嵌入式软件工程师经典面试题
  12. iOS 获取设备UDID
  13. php 模拟蜘蛛,PHP实现使用CURL模拟百度蜘蛛进行采集
  14. HX711 压力调试 51程序
  15. 查询计算机物理地址指令,怎么查询mac地址命令_如何查mac地址
  16. 融360 D轮融资超10亿 平台型互联网金融价值凸显
  17. Linux环境使用smart-go-dl install go1.18报错error obtaining VCS status: exit status 129 -buildvcs=false
  18. 06-1-SVM原理
  19. 服务器利用scp传输文件
  20. MFC程序每次启动前修改文件名

热门文章

  1. RN8215芯片 32768Hz晶体停振案例分析
  2. 再见,Python正则表达式!
  3. 单臂路由之2,多网口软路由实现单臂路由功能,且其剩余网口及光猫剩余网口均实现上网功能
  4. shell 脚本 基础
  5. 微信产品经理-张小龙:如何把产品做简单
  6. 在算力“沃土”上,种植互联网下一个奇迹十年
  7. iOS系统越狱研究现状梳理
  8. 【翻译】揭开HTML5的神秘面纱
  9. 2020科目一考试口诀_2020年驾驶员科目一满分口诀
  10. 深圳两青年在伊拉克炮火中淘金400万