在讲自旋锁、信号量之前,先聊一聊为什么要使用自旋锁和信号量?    --    为了保护共享资源不被同时访问,需要用某种互斥机制加以保护


首先讲应用场景:当某一个执行单元在使用一块共享资源时,是不能被打断的,否则会造成共享资源内的数据被另一个执行单元改变,造成无法预知的错误。为了预防以上可能产生的情景,就需要对这一块共享资源做保护措施。

保护共享资源的方式有很多,例如:中断屏蔽、自旋锁、信号量...

保护共享资源的互斥机制--中断屏蔽:通过local_irq_disable(禁止中断操作)/local_irq_save(禁止中断操作,并保存目前CPU中断位信息,对ARM而言,就是保存CPSR)/local_bh_disable(禁止中断底半部),local_irq_enable(使能中断操作)/local_irq_restore(使能中断操作,并恢复CPU中断位信息,对ARM而言,就是恢复CPSR)/local_bh_enable(使能中断底半部)...
    保护共享资源的互斥机制--原子操作:实现是atomic_inc(),其底层实现会用到atomic_add(),汇编中用到了ldrex、strex,作用是监控总线上是否有其他的实体存取该地址,如果有并发访问,执行strex时,第一个寄存器的值被设置为1并且存储行为不成功。
    保护共享资源的互斥机制--自旋锁:查阅底层自旋锁代码和原子操作代码可以发现,在最开始的一段代码两者都是想同的,说明自旋锁的实现就是对一块资源先执行一次原子操作(对这块资源上的原子变量值自增1),然后再判断该锁是否正在被使用,如果这个锁当前处于空闲状态,那么,程序获得这个自旋锁,并继续执行;否则,会一直在这个while循环里,直到获得这个自旋锁,再继续执行。
    1.单处理器中低优先级的进程被高优先级的进程抢占,同时他们访问同一块共享资源
    2.多处理器中,CPU1的进程、CPU2的进程同时访问同一块共享资源
    3.上述两种情况下,中断打断进程的运行,同时中断也访问同一块共享
    针对上面竞态产生的几种情况,阐述自旋锁如何应对:
    1.采用spin_lock(),保护共享资源不被抢占;
    2.采用spin_lock(),保护共享资源不被抢占;
    3.除了采用spin_lock(),还需要加上local_irq_disable()/local_irq_save()/local_bh_disable(),在保证共享资源不被抢占的同时避免了中断驶入对共享资源造成破坏。

--------自旋锁对信号量------------------------------------------------------

需求                                 建议的加锁方法

低开销加锁                         优先使用自旋锁

短期锁定                           优先使用自旋锁

长期加锁                           优先使用信号量

中断上下文中加锁                 使用自旋锁

持有锁是需要睡眠、调度         使用信号量

这篇文章讲的非常仔细:https://blog.csdn.net/xu_guo/article/details/6072823


CPU运行的场景一定是以下三种情况
    1.执行在用户态,进程上下文
    2.执行在内核态,进程上下文
    3.执行在内核态,中断上下文
    其中,中断上下文不适合用信号量,因为信号量会引起休眠,所以在中断上下文中更推荐使用自旋锁;
    看了一部分的文章,我个人认为:自旋锁和信号量的功能其实是差不多的,但是!自旋锁更适合用于处理时间特别短的情景下,信号量特别适合用处处理时间特别长的情景下。
    如果被保护的共享资源只在进程上下文访问,使用信号量保护该共享资源非常合适,如果对共享资源的访问时间非常短,自旋锁也可以。但是如果被保护的共享资源需要在中断上下文访问(包括底半部即中断处理句柄和顶半部即软中断),就必须使用自旋锁。
    自旋锁保持期间是抢占失效的(内核不允许被抢占) ,而信号量和读写信号量保持期间是可以被抢占的。


自旋锁:一种当获取不到锁时,采用循环 “ 测试并设置 ” 的方式等待获取锁的一种互斥锁。
获取方法:采用 test-and-set 原子操作测试并设置某个内存变量。 —— 实现互斥的方法。
不被打断:通过禁止抢占实现。
使用场合:可在进程上下文和中断上下文使用。
特点:
1 、这种锁在获取不到时系统开销大,所以获取锁和释放锁的区间代码执行时间应该尽可能短。
2 、在单 CPU 内核可抢占系统中,自旋锁持有期间内核抢占被禁止。但在单 CPU 内核不支持抢占的系统,自旋锁退化为空操作。 —— 也就是说,如果持有锁的区间代码执行时间过长,会出现其它操作的不响应(假死机)现象。
3 、因为抢占被禁止,自旋锁可保证临界区不受 “ 别的 CPU 和本 CPU 内 ” 的抢占进程进程打扰。(但是可响应中断,如果连中断都不响应,需要特定的加锁函数)
4 、可能受到中断和底半部( BH) 的影响,为此,与开、关中断配合,为此有:
spin_lock_irq(),spin_unlock_irq()
spin_lock_irqsave(),spin_unlock_irqstore()
spin_lock_bh(),spin_unlock_bh()
具体含义请参考教材或网络搜索。
自旋锁的扩展: 1 、读写自旋锁; 2 、顺序锁; 3 、读 - 拷贝 - 更新( RCU)


信号量:一种当获取不到锁时,采用睡眠以等待唤醒的方式的一种同步机制。
不被打断:通过何种方式实现没找到参考,但可推断为同自旋锁。
获取方法:同自旋锁。
适用场合:由于会导致睡眠,只能在进程上下文中使用。但是 int down_trylock(struct semphore *sem) 可以。
特点:
获取不到锁时,进入睡眠状态,系统开销为上下文切换的时间 Tsw 。
自旋锁和信号量的对比:
1 、当锁不能获取时,信号量开销为 Tsw (上下文切换时间),自旋锁开销为等待获取时间 Tcs ,这两个时间对比权衡使用哪种机制。
2 、信号量可用于保护包含可能引起阻塞的代码 ( 即保护的代码中有可引起睡眠的函数如 copy_to_user(),copy_from_user()) ,自旋锁不能。自旋锁如果也使用这样的代码,当睡眠时另一个程进也要获取这把锁时,会 进入循环,而睡眠时间一般相对较长,系统资源开销大的时间过长时,资源耗尽的情况会发生,直到这个睡眠的进程被唤醒,代码执行完毕,资源才得以释放。至于 自旋锁区间睡眠引起死锁的情况我实在想不出来。但教材中都这么说。
3 、中断上下文中只能使用自旋锁,不可使用信号量。因为中断上下文中是不能被调度的,但睡眠后会发生上下文切换,需要调度,在中断上下文中睡眠只能永久睡眠 —— 死机!


转自别人的笔记:
4.linux内核并发和竞态
  面试题:谈谈进程间通信的机制
  4.1.案例:要求LED设备,同一时刻只能被一个应用程        序打开访问操作
  实施方案:
  1.在用户空间实现
    A,B,C多进程可以采用进程间通信来决定由哪个进程     打开硬件设备
  2.在底层驱动实现
    不管有多少个进程访问设备,永远先open,它们最终     调用同一个底层驱动的open,只需在底层驱动的open     做相关的代码限定即可
  参考代码:

  static int open_cnt = 1;static int led_open(struct inode *inode,struct file *file){if (--open_cnt != 0) {printk("设备已被打开!\n");open_cnt++;return -EBUSY;//返回设备忙错误}printk("设备打开成功!\n");return 0;}static int led_close(struct inode *inode,struct file *file){open_cnt++;return 0;}

回顾:
  gcc编译过程:
  预处理->汇编->编译->连接
  正常情况:
  以--open_cnt语句为了,要经过:
  1.ldr ... //先加载
  2.--  ... //再自减
  3.str ... //最后存储
 
  先A进程来打开设备:
      1.先加载 open_cnt = 1;
      2.修改,写回:open_cnt = 0;
      3.结果:A打开设备成功
      
  再B进程打开设备:
        1.先加载 open_cnt = 0
        2.修改,写回:open_cnt = -1
        3.结果:B打开设备失败
        
  异常情况:
  先A进程来打开设备:
      1.先加载 open_cnt = 1
      由于linux内核支持单CPU进程与进程之前的抢    占(高优先级的进程抢占低优先级进程的CPU资    源),此时此刻,B进程抢占A进程的CPU资源
  B进程接着执行:
      1.先加载 open_cnt = 1
      2.修改,写回:open_cnt = 0;
      3.结果:B打开设备成功
        4.B执行完毕,将CPU资源让给A进程
  A继续执行:
      2.修改,写回:open_cnt = 0;
      3.结果:A打开设备成功
 
  总结:产生问题的原因是进程与进程之前的抢占引起    的竞争状态;
      产生这种竞争状态的根本原因是加载,修改,存    储这三个过程不具备原子性
      只要加载,修改,存储这三条语句在执行的时候,    不会发生CPU资源的切换,也就是让这三条语句    的执行路径具有原子性,即可避免这种竞争问    题


4.2.linux内核产生竞争状态的几种情形:
    1.单CPU进程与进程的抢占
    2.中断和进程
    3.中断和中断
    4.多核(共享内存,闪存,IO等)


4.3.概念
    1.并发:多个执行单元同时发生
    2.竞态:多个执行单元同时对共享资源的访问,形        参竞争状态
    3.共享资源:软件上的全局变量或者硬件寄存器
    4.互斥访问:当一个执行单元在访问共享资源的时        候,其他执行单元被禁止访问
    5.临界区:访问共享资源的代码区域
    6.执行路径具有原子性:代码的执行过程不允许发              生CPU资源的切换


4.4.linux内核解决竞态问题的方法
  中断屏蔽
  自旋锁
  信号量
  原子操作


5.linux内核解决竞态方法之中断屏蔽
  特点:
  1.能够解决进程与进程抢占引起的竞态问题
  2.能够解决中断和进程引起的竞态问题
  3.能够解决中断和中断引起的竞态问题
  4.多核的竞态问题无法解决
  5.中断屏蔽所保护的临界区执行速度要快,更不能做休     眠操作


编程步骤:
  1.明确驱动代码中哪些是共享资源
  2.明确驱动代码中哪些是临界区
  3.明确临界区中是否有休眠
  4.访问临界区之前屏蔽中断
    unsigned long flags;
    local_irq_save(flags);//屏蔽中断,保存中断状态                到flags
    “local”:指的是当前临界区所在的CPU,把对应的中        断信号进行屏蔽,其他CPU上的中断信号不        做屏蔽
  5.访问临界区
    切记不能休眠
    执行速度要快
6.访问临界区之后恢复中断
    local_irq_restore(flags);//恢复中断,恢复之前                   保存的中断状态
    参考例子代码:

    static int open_cnt = 1; //共享资源static int led_open(struct inode *inode,struct file *file){unsigned long flags;local_irq_save(flags);//关中断//临界区if (--open_cnt != 0) {printk("设备已被打开!\n");open_cnt++;local_irq_restore(flags);//开中断return -EBUSY;//返回设备忙错误}local_irq_restore(flags);//开中断printk("设备打开成功!\n");return 0;}

5.linux内核解决竞态方法之自旋锁
  特点:
  1.能够解决多核引起的竞态问题
  2.能够解决进程与进程的抢占引起的竞态问题
  3.如果要解决中断的引起的问题,必须使用衍生自旋锁
  4.自旋锁=自旋 + 锁
  5.持有自旋锁的任务在访问临界区时,临界区的执行      速度要快,更不能做休眠操作
  6.如果没有获取自旋锁的任务,将会原地忙等待自旋锁     的释放
 数据类型:spinlock_t


编程步骤:
  1.明确共享资源
  2.明确临界区
  3.明确是否有休眠
  4.定义初始化自旋锁
    spinlock_t lock;
    spin_lock_init(&lock);
  5.访问临界区之前获取自旋锁
    spin_lock(&lock);
    说明:
    1.如果获取自旋锁,立即返回
    2.如果没有获取自旋锁,原地忙等待,直到持有自旋                      锁
    3.中断引起的竞态问题无法解决
    
    或者:
    
    spin_trylock(&lock);
    说明:
    1.如果获取自旋锁,返回true
    2.如果没有获取自旋锁,也返回,不进行忙等待,返回                      false
    3.中断引起的竞态问题无法解决
    
    或者:
    
    unsigned long flags;
    spin_lock_irqsave(&lock, flags);
    说明:
    1.获取自旋锁,立即返回
    2.没有获取自旋锁,忙等待
    3.也能够解决中断引起的竞态问题
    
  6.访问临界区
    切记不能做休眠操作
    
  7.访问临界区之后,释放自旋锁
    spin_unlock(&lock);
    说明:
    此方法跟spin_lock/spin_trylock配对
    
    或者
    spin_unlock_irqrestore(&lock, flags);
    说明:
    此方法跟spin_lock_irqsave配对
 
    参考代码:
    static int open_cnt = 1; //共享资源
    static int led_open(struct inode *inode,
              struct file *file){
      unsigned long flags;
      spin_lock_irqsave(&lock, flags);
      //关中断,获取锁
      //临界区
      if (--open_cnt != 0) {
          printk("设备已被打开!\n");
          open_cnt++;
       spin_unlock_irqrestore(&lock,flags);
              //释放锁,开中断
          return -EBUSY;//返回设备忙错误
      }
      spin_unlock_irqrestore(&lock,flags);
      //释放锁,开中断
      printk("设备打开成功!\n");
      return 0;
   }
   
   
   案例:利用自旋锁实现一个设备只能被打开一次
   实验步骤:
   ARM执行:
   1.insmod led_drv.ko
   2.ls /dev/myled -lh
   3../led_test & //启动A进程,后台运行
   4.ps  //查看A进程的PID
   5.top //查看A进程的状态,按Q键退出
      PID      休眠状态          进程名称
      86   ...  S(休眠)  ....     ./led_test
   6../led_test  //启动B进程
   7.kill A进程的PID
 
6.linux内核解决竞态方法之信号量
  特点:
  1.信号量又称睡眠锁
  2.信号量的本质就是解决自旋锁保护的临界区中不能      进行休眠的问题,如果将来临界区有休眠动作,只能      考虑使用信号量,而不是自旋锁
  3.没有获取信号量的任务将会进入休眠等待状态
 
  数据类型:struct semaphore
 
  编程步骤:
  1.明确共享资源
  2.明确临界区
  3.明确是否有休眠
  4.定义初始化信号量
    struct semaphore sema;
    sema_init(&sema, 1); //初始化为互斥信号量
  5.访问临界区之前获取信号量
    down(&sema)
    说明:
    1.如果获取信号量,立即返回
    2.如果没有获取信号,进程将进入不可中断的休眠状    态,直到获取信号量立即返回
    3.不可中断的休眠状态指示休眠的进程在休眠期间    不会立即处理接收到的信号,而是休眠的进程被    唤醒以后才会处理之前接收到的信号;这种休眠    状态的进程只能由驱动主动唤醒(持有信号量的    任务来唤醒)
    4.不能用于中断
    
    或者:
    down_interruptible(&sema);
    1.如果获取信号量,立即返回false
    2.如果没有获取信号,进程将进入可中断的休眠状       态,直到获取信号量立即返回false,如果返回true表     示是由于信号引起的唤醒
    3.可中断的休眠状态指示休眠的进程在休眠期间           会立即处理接收到的信号
    4.不能用于中断
    5.可中断的休眠进程被唤醒的方法有两种:
      1.一种接收到了信号
      2.一种接收到了信号量
    
    或者:
    down_trylock(&sema);  
    1.获取信号量返回false
    2.没有获取信号量,任务不休眠,立即返回true
    3.可以用于中断上下文
    
  6.访问临界区
    可以进行休眠操作
  7.访问临界区之后释放信号
    up(&sema);
    1.先唤醒休眠的进程
    2.再释放信号量给之前休眠的进程
 
  案例:利用信号量,实现一个设备只被打开一次
  实验步骤:
  down方法:
  1.insmod led_drv.ko
  A->B->kill A->kill B
  2../led_test & //启动A进程
    A休眠是因为调用sleep
  3.ps //查看A进程的PID
  4../led_test & //启动B进程
    B休眠是因为没有获取信号量
  5.ps //查看B进程的PID
  6.top //查看A,B进程的状态,按Q键退出
     106    S    ./led_test
     109    D    ./led_test
     第一列:进程的PID
     第二列:进程的状态
            "S":可中断的休眠状态
            “D”:不可中断的休眠状态
            
  7.kill A进程的PID
  8.top //查看B进的状态
    B休眠是因为调用sleep
  9.kill B进程的PID
 
  A->B->kill B->kill A
  10../led_test & //启动A
  11../led_test & //启动B
  12.ps
  13.top
  14.kill B进程的PID
  15.kill A进程的PID
 
  down_interruptible方法:
  1.insmod led_drv.ko
  A->B->kill A->kill B
  2../led_test & //启动A进程
    A休眠是因为调用sleep
  3.ps //查看A进程的PID
  4../led_test & //启动B进程
    B休眠是因为没有获取信号量
  5.ps //查看B进程的PID
  6.top //查看A,B进程的状态,按Q键退出
     106    S    ./led_test
     109    S    ./led_test
     第一列:进程的PID
     第二列:进程的状态
            "S":可中断的休眠状态
            “D”:不可中断的休眠状态
            
  7.kill A进程的PID
  8.top //查看B进的状态
    B休眠是因为调用sleep
  9.kill B进程的PID
 
  A->B->kill B->kill A
  10../led_test & //启动A
  11../led_test & //启动B
  12.ps
  13.top
  14.kill B进程的PID
  15.kill A进程的PID

【同步与并发】自旋锁、信号量相关推荐

  1. Linux内核中的同步原语:自旋锁,信号量,互斥锁,读写信号量,顺序锁

    Linux内核中的同步原语 自旋锁,信号量,互斥锁,读写信号量,顺序锁 rtoax 2021年3月 在英文原文基础上,针对中文译文增加5.10.13内核源码相关内容. 1. Linux 内核中的同步原 ...

  2. 深入理解Linux自旋锁(1.0)

    学习方法论 写作原则 标题括号中的数字代表完成度与完善度 0.0-1.0 代表完成度,1.1-1.5 代表完善度 0.0 :还没开始写 0.1 :写了一个简介 0.3 :写了一小部分内容 0.5 :写 ...

  3. Linux并发与竞争介绍(原子操作、自旋锁、信号量、互斥体)

    目录 并发与竞争 并发与竞争简介 保护内容是什么 原子操作 原子操作简介 原子整形操作API函数(atomic_t 结构体) 原子位操作API 函数 自旋锁 自旋锁简介 自旋锁API函数 线程与线程 ...

  4. linux 内核同步--理解原子操作、自旋锁、信号量(可睡眠)、读写锁、RCU锁、PER_CPU变量、内存屏障

    内核同步 内核中可能造成并发的原因: 中断–中断几乎可以在任何时刻异步发生,也就可以随时打断当前正在执行的代码. 软中断和tasklet–内核能在任何时刻唤醒或调度软中断和tasklet,打断当前正在 ...

  5. 漫画Linux 并发、竞态、互斥锁、自旋锁、信号量

    1. 锁的由来? 学习linux的时候,肯定会遇到各种和锁相关的知识,有时候自己学好了一点,感觉半桶水的自己已经可以华山论剑了,又突然冒出一个新的知识点,我看到新知识点的时候,有时间也是一脸的懵逼,在 ...

  6. 漫画|Linux 并发、竞态、互斥锁、自旋锁、信号量都是什么鬼?

    文章目录 1. 锁的由来? 2. 什么是并发和竞态? 2.1 并发与竞态概念 3. 讨论下死锁 3.1 多进程调度导致死锁 3.2 单线程导致死锁 4. 互斥锁和自旋锁.信号量的区别? 5. 如何解决 ...

  7. 一文看懂临界区、互斥锁、同步锁、临界区、信号量、自旋锁等名词!

    点击上方"业余草",选择"置顶公众号" 第一时间获取技术干货和业界资讯! 关于线程安全的专有名词有一大堆.你们突然之间问我这个名词是什么意思,那个名词是什么意思 ...

  8. Linux内核的并发与竞态、信号量、互斥锁、自旋锁

    /************************************************************************************ *本文为个人学习记录,如有错 ...

  9. Linux驱动编程 step-by-step (七) 并发 竞态 (信号量与自旋锁)

    并发 竞态 (信号量与自旋锁) 代码传至并发竞态控制 并发进程 导致竞态的一个例子 前面所述的字符驱动都是没有考虑并发竟态的情况,想象一下 一个进程去读一个字符设备,另一个进程在同一时间向这个设备写入 ...

最新文章

  1. 分治算法的设计思想(二分检索、二分归并排序)
  2. 思维 ---- 两两匹配问题 2021杭电多校第6场 E - Median
  3. u盘扩容软件_扩容盘的认识与检测和量产还原
  4. 实用代码-C#之IP地址和整数的互转
  5. PyCaret:又一个神仙ML库
  6. 经过 Webpack 处理过的 SAP Spartacus main.js
  7. java session使用_java学习之web基础(8):使用session实现带验证码的登录功能
  8. 大数据之-Hadoop3.x_MapReduce_MapTask源码解析---大数据之hadoop3.x工作笔记0126
  9. 一步步教你轻松学主成分分析PCA降维算法
  10. word是多线程的程序_线程的基本概念,实现多线程的四种基本方式
  11. putty以及psftp的基本操作,使用方法等
  12. 适合笔记本电脑看Kindle MOBI 电子书的软件
  13. 重磅 | 2020年区块链领域全球授权专利报告
  14. Android 9.0 简单适配
  15. IDEA的LeetCode力扣插件设置与使用(超详细)
  16. 基于动力学模型的无人驾驶车辆MPC轨迹跟踪算法及carsim+matlab联合仿真学习笔记
  17. 计算你来到世界多少天
  18. 长隆原创巨制《龙秀》震撼上演 珠海横琴又添文旅新地标
  19. JavaScript语言精粹-读书笔记(1)
  20. 鲁棒的激光雷达与相机标定方法

热门文章

  1. 手把手教你完成一个数据科学小项目(9):情感分析与词云
  2. Python环境安装Spyder
  3. 【git】fatal: in unpopulated submodule
  4. php 变量 2的值取整,php取整数值的方法
  5. java查找第k大的数字_[经典算法题]寻找数组中第K大的数的方法总结
  6. 乐视生态的七大战略性风险
  7. 20190901PDD第二题
  8. twilio python_在Python中用Twilio接收和处理短消息
  9. 互联网企业风生水起 四大商业模式彰显生机
  10. 腾讯NLP算法岗实习面经