linux中的同步和互斥机制 为什么需要锁 从一个很经典的问题开始:假设有两个线程,两个线程无锁的情况下循环给一个全局变量加1,循环100次,全局变量的初始化值为0,那么最终这个全局变量的值是多少? 答案是2 ~ 200。 为什么会这样呢?我们先想一下给一个变量+1分为哪几步:从内存中将这个变量读取到寄存器中CPU通过寄存器进行+1运算将计算后的结果通过寄存器写到内存里 这三个步骤不能一条指令完成,也就是说过程中是可以被打断的,我们模拟如下过程:线程A将变量从内存读到寄存器中(此时变量值为0)线程B打断线程A,将变量增加了99次,然后将变量写回了内存(此时变量值为99)线程A继续运行,因为之前已经从内存中读过变量的值了,所以直接通过寄存器进行+1计算然后将计算的结果写回内存(此时变量值为1)线程B运行,将变量从内存读到寄存器中(此时变量值为1)线程A运行,一直运行到循环结束(此时变量值为100)线程B运行,此时线程B只剩一次循环,利用寄存器进行+1计算以后将结果写到内存里(此时变量值为2) 以上的过程是极端情况,这样我们知道了为什么最后的结果是个范围值。 数据混乱的根本原因是什么 到这我们需要停下来想一想,造成上述情况的根本原因是什么?换句话说,我们需要保证什么,变量最后的结果才能和我们的期望一样? 聪明的你一定想到了,问题出在当“读改写”流程被打断后,下次切换回来的时候当前进程并不知道这个变量有没有被别人修改过,仍然按当时自己当时读取的数据进行运算,这样就出现了问题。那么我们是不是只要保证“读改写”过程不会被打断或者在这个过程中不会有人改这个变量就可以了。 并发源 首先我们要清楚对一个变量可能的竞争来源有哪些,这里锁的使用我们以spin_lock为例,因为spin_lock是可以在中断上下文中使用的也最具代表性,而mutex_lock只能在进程上下文使用
image.png 单核非抢占内核 单核非抢占内核在中断返回的时候不会重新调度,也就是说进程A被中断打断后等到中断返回的时候运行的还是进程A,那么只可能出现如下几种情况:
image.png 当变量只会在进程上下文被修改,那么什么操作也不需要做就可以保证读改写不会被打断当变量会在进程上下文和软中断中被修改的时候,那么我们只需要在进程上下文中关闭中断下半部即可当变量会在进程上下文和中断上下文中被修改的时候,我们只需要在进程上下文关闭中断即可 单核抢占内核 抢占开启后,在中断返回的时候会进行一次调度,那么相比非抢占的时候,就会多如下这种情况:
image.png 这种情况我们关闭抢占即可,这样中断返回的时候就不会发生调度,也就不会运行进程2 多核情况 这里首先要先介绍下per cpu变量,顾名思义,就是一个变量对于各个CPU都有一份副本,每个CPU维护自己那份副本,这样可以减少各个CPU之间的竞争,而且对于runqueue这种本来就应该各个CPU自治的变量就更加合适了(因为每个CPU都有自己的就绪队列),需要注意的是使用per cpu变量的时候要关闭抢占,因为如果抢占打开的话,当一个进程读取了CPU0上的副本后被抢占了,下次运行的时候可能就被迁移到CPU1上了,这样继续运行的话就相当于在CPU1上操作CPU0的副本了 per cpu变量当变量会在进程上下文和中断上下文中被修改时,关闭本CPU中断即可,使用local_lock_irq()或者local_lock_irqsave()当变量只会在进程上下文被修改时,关闭抢占即可,使用local_lock() 普通变量理论上来说我们使用spin_lock()后就可以保证处于原子上下文,避免和其他并发源产生竞争了,但是存在如下情况:1. CPU0的进程上下文中拿到了spin_lock;2.此时当前CPU触发了一个中断,在当前CPU的中断上下文中需要同一个spin_lock会发生什么?结果是会发生死锁,在中断上下文的那次永远也不到锁,因为需要退出中断返回进程上下文以后才能释放锁,但现在不到锁又会原地自旋退出不了中断,所以当变量可能会在进程上下文和中断上下文使用的时候需要使用spin_lock_irq()或者spin_lock_irqsave(),这里关中断的原因和local_lock_irq()关闭中断的原因是不一样的 如何选择锁 确定自己要保护的对象确定自己要保护的对象可能会在什么情况下读写,是只会在进程上下文读写还是在进程上下文和中断上下文都有可能读写确定数据的读写频率 如何使用锁 如下是平常代码中加锁的情况: 当我第一次阅读有锁相关的代码的时候有这样的疑惑:使用锁的时候其实就是用lock和unlock在代码段中构建一段临界区,那这段临界区到底在保护谁呢?毕竟这段代码中有很多变量,如果这个问题搞不清楚那后面自己使用锁的时候必然会出现问题。面向对象的思想充斥着linux代码里,我们可以看到大多数情况下spin_lock()传入的锁是嵌在一个结构体里的,说明这个锁就是这个结构体的锁,那这段临界区自然就是保护这个结构体的。 spin_lock 特点 互斥机制在进程上下文和中断上下文都可以使用不到锁时会原地自旋,所以临界区要求尽可能快地退出,不然会浪费CPU资源持锁后处于原子上下文,不能调度只有持锁的进程才能释放锁 mutex_lock 特点 互斥机制只能在进程上下文使用不到锁后当前进程会睡眠,具体处于TASK_UNINTERRUPTIBLE,TASK_INTERRUPTIBLE还是TASK_KILLABLE根据使用的对应接口函数决定持锁后可以发生进程切换只有持锁的进程可以释放锁 semaphore 信号量是一种同步机制而不是互斥机制,当信号量的值大于0时即可进入临界区,主要有两种用法:初始化成一个非零值,代表某种资源的总量,资源后down(),释放资源后up(),例如一台电脑只有3个USB口,那你三次以后,别人就不能再了。初始化为0,使用时对应生产者消费者模型,生产者up(),消费者down(),例如有两个进程,一个进程负责处理数据,一个进程负责发送数据,当然要先准备好一帧数据以后才可以发送。 特点 可以多个进程同时进入临界区释放锁的进程不一定是持锁的进程 atomic变量 特点 只保护一个变量,保证对该变量的读改写操作不会被打断 读写锁 特点 读者之间不互斥写者之间互斥,同一时间只能有一个写者在临界区里写者和读者之间相互互斥,只要有写者在临界区里读者就不能进入,同样读者在临界区里的时候写者也不能进入因为在读多写少的情况下存在写者饥饿问题已经不推荐使用 RCU 特点 读者之间不互斥写者之间互斥读者和写者之间不互斥,当一个写者在临界区里的时候可以有多个读者
2024最新激活全家桶教程,稳定运行到2099年,请移步至置顶文章:https://sigusoft.com/99576.html
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。 文章由激活谷谷主-小谷整理,转载请注明出处:https://sigusoft.com/29322.html