信号量保护

版权声明:本文为博主 Osprey 原创文章,转载请联系博主! https://blog.csdn.net/weixin_42876465/article/details/84592314

让我们举个例子。记DeviceALocked是一个位于内存中的R/W变量,用于指示设备A是否已经在使用中。任何一个任务,若欲使用设备A,都必须先检查这个变量的值。如果它的值为零,则表示设备可以使用。在任务获取到设备A后,它要把DeviceALocked的值改为1,表示设备A已经被占用。在设备A使用完毕后,该任务通过重新清零DeviceALocked来释放设备A,从而使其它任务可以使用此设备。
看起来这是个如意算盘。不过可否想过,如果两个任务都想访问设备A,是否有潜在的危险?比如,在任务1读取了DeviceALocked后,发现是零于是准备使用此设备,但还没来得及把它改为1,就不巧被调度器切出(比如,轮转调度),然后调度器让任务2执行,于是任务2也读到零,从而它使用设备A。但是在任务2在用完设备A之前,调度器又切回任务1。由于任务1早先读回来的是零,所以它认为设备A是空闲的,于是使用设备A,这时就违背了设备A必须互斥访问的限制,使系统出现紊乱危象!如果设备A是台打印机,则把两个文档的内容打在了一起;如果设备A是油门控制器,则可能使汽车失控或熄火,后果不堪设想。
----------------------------------------------------------------摘自《权威指南》

使用信号量之前必须创建信号量,否则可能导致不可预料的后果,主要表现在如下:
在这里插入图片描述
在不开启参数检查的情况下,刚好通过了后面的判断语句而继续往下执行,必然会导致程序出错。因为你根本不知道地址为0的位置的数据到底是什么,很可能刚好就等于OS_EVENT_TYPE_SEM而继续执行。所以在调试阶段,建议打开参数检查功能,当程序完成之后再关闭该功能。
信号量挂起(OSSemPend)有两个限制:
 不可以在中断使用;
 不可以在关闭调度的情况下使用。
在执行OSSemPost时,一旦发现有任务在等待该信号量,就会从最等待该信号量的所有任务中寻找最高优先级的任务进入就绪态,并将该任务从等待该信号量的事件表移除。但是要注意进入就绪态后不一定马上能运行。还有一点,如果没有任务在等待该信号量,那么这个信号量计数器是会自加的,直到最大值。
关中断
最简单的方法就是采用关中断的方式进行数据的保护。但是这样会增加中断响应时间。
在什么情况下需要关闭中断呢?
1、操作的对象是共享资源(全局变量),其他任务可能在整个操作流程中打断你的操作,并且会更改你的全局变量,最终会引起非常严重的后果的情况下需要关中断。
2、当任务在执行对时序要求非常高的操作,因为被其它任务中断浪费了很多时间,当回来的时候错过了dead time,最终导致操作失败。
---------------------------------------------------------------------------------------2018/08/23 Osprey

中断屏蔽:https://www.amobbs.com/thread-5547895-1-1.html
CM3位带操作
如果存储器系统支持“锁定传送”( locked transfers),或者总线上只有一个主机,还可以使用CM3的位带功能来实现互斥锁的操作。通过使用位带,则可以在C程序中实现互斥锁,但是操作过程与互斥访问是不同的。在使用位带来做资源分配的控制机制时,需要使用位带存储区的内存单元(比如,一个字),该内存单元的每个位表示资源正被特定的任务使用。在位带别名区的读写实质上是锁定的“读‐改‐写”(在传送期间总线不能被其它主机占有)。因此,只要每个任务都仅修改分配给它们自己的锁定位,其它任务锁定位的值就不会丢失,即使是两个任务同时写自己的锁定位也不怕,如图:
在这里插入图片描述
以两个任务为例,看如下情况:(红色箭头代表跳转执行)
在这里插入图片描述
任务2首先读取并判断,之后因某种原因跳转到任务1执行读取、判断、设置操作,又因为某种原因在设置完之后又跳转到任务2去进行设置、读取操作,后来又到任务1去执行读取并判断操作,发现有其它位置1,则放弃该资源,然后当运行任务2时,因为还是之前的数据,所以认为最终的判断认为有其它任务占用,也放弃该资源。最终的结果就是两个任务都放弃了该资源。但这种情况很少见,因为这需要在几条指令中来来回回跳转才会发生这种情况,一般情况是整个操作流程只会被其它任务中断一次。
再来分析以下执行流程,任务1在读取完之后任务2设置了占用标志,然后回到任务1判断,结果就是任务1占用资源,即使后续又被任务2中断了,任务2也是会读取到任务1的占用标志,从而放弃资源。
在这里插入图片描述
以下是中断判断操作(判断未执行)之后可能跳转的位置,自行分析(关键在于设置)。
在这里插入图片描述
那么位带别名操作避免的是什么呢?其实就是用于避免读-改-写的操作中因为中断,导致其它任务设置的数据因为最后写的步骤而丢失了。如下:
在这里插入图片描述
因为在设置的时候发生中断,导致任务2设置,但是因为中断的是设置操作,所以当回到设置的时候,读取的数据还是之前的数据,此时再进行设置操作必然导致任务2的设置操作丢失,并最终导致严重后果。
再深入思考后发现这些操作的关键在于你设置标志后判断你是否真的拥有该资源。那是否可以将前面的读取判断操作省去而直接去设置该标志呢?我认为是可以的,但是这里有一个缺点就是因为你不管有没有其它任务设置标志都自己去设置,一旦在有其他任务设置的情况下设置标志,你就必须进行清除自己标志的操作,加上你之前设置的操作,明显比先判断之后再进行设置操作效率更低。
那么是不是说如果没有位带操作就没办法进行互斥访问呢?当然不是,简单的方法是关中断的方式,但通过先前的分析发现,其实关键点在于别破坏其他设置的标志即可,这样我们可以把字节当位来看也是可以的,你设置你的字节标志,我设置我的字节标志,互不干扰,一样可以达到位带操作的效果,只是空间占用更大一些罢了。但是单片机最大的读取类型是double型数据,在stm32中即为8字节,也就是说可设置的最大任务数就是8个任务,而位带操作为8*8=64个任务,但是是否可以采用多次判断的方式增加任务数呢?比如两个double组合使用时利用两次判断两个变量的方法确定是否占用资源。这是一个解决方法,但是这个方法是否存在风险呢?通过后面的分析其实可以得到答案。
再进行深入思考之后,你就会发现,最为关键的就是设置之后的“读取”这个确认操作,这个操作是整个资源锁定操作的分水岭。在整个操作流程中,不管哪个操作被中断,然后被其它任务设置标志位,关键都在读取这一步,谁将变量读取到寄存器时没有其他任务设置标志位中,谁就占用了资源。
两个任务在宏观上可以认为在同时执行,但是在执行读取操作的时候,如果两个任务同时进行到了这一步,必然有一个先后(不管谁先谁后,,只要对方没有在你读取前放弃占用,都会放弃占用,而如果说真存在同时读取(两个cpu)的情况,那么必然是同时放弃资源的结果)。既然已经执行到了读取操作,那么就必然进行了设置操作,也就是说两个人同时设置了标志位,都准备占用资源,不管谁进行读取,最终的结果就是资源已经被占用,之后就是放弃了,即使任务在在读取后马上被中断判断的操作,但却不会影响该任务后续的判断、放弃操作了,因为你读取的数据已经决定了该任务的所有后续操作,所以关键操作就是读取操作,你读取的到底是什么数据。
那么最糟糕的情况就如前面所说,两个任务的在读取之后都发现被其他任务占用,然后都放弃资源,然后我们再分析其他可能性,两个任务的读取的数据理论上有四种可能,11,10,01,00。但是实际上00是可以排除的,因为读取之前必然已经进行了设置,那么除了
该任本身的标志位外只有其他任务可能设置的标志位了,即要么设置,要么没设置。如果设置了,又分为两种情况,如果另一个任务已经放弃了占用,那么它就可以占用资源,如果该任务在读取之前另一任务没有放弃资源,那么就放弃资源。
用图表示可能更清晰一些:
在这里插入图片描述
在这里插入图片描述
其实进行深入分析之后可以发现,就是之前所说的两个关键操作它们的先后顺序,而决定是否占用资源的关键操作就是读取操作。谁先读取到没有其他任务占用的情况谁就占用了该资源。
现在再来考虑有没有可能出现两个任务读取操作之后发现对方都没请求占用资源而同时占用资源的情况?我们知道互斥操作为的就是避免这种情况而特意设定的操作流程,如果这种情况不能避免分析再多都没用。事实上这种情况是不可能出现的,因为一个任务在读取的时候这个任务已经设置完本任务的标志位了,一旦读取之前另一个任务没有设置标志位,就算是占用了资源,然后另一任务在读取的时候必然是有任务占用的结果,就会放弃占用,就不会存在这个任务读取时没任务占用,另一个任务读取的时候也没占用,因为同一时间只能有一个任务先进行读取操作,不可能同时,即使是同时进行读取操作,最终的结果也只是同时放弃资源罢了。也就是说在在设置操作后给了任务两种可能性,占用或不占用,同时也避免了两个任务同时占用的情况。
所以之前的遗留的问题答案是不会有风险,因为最糟糕的情况也只是两个任务同时放弃资源。
总之一句话,谁读取时没其他任务占用,不管后面发生什么情况,这个资源我占定了。
---------------------------------------------------------------------------------------2018-08-18 Osprey
在上面的互斥量问题的思考中,我们可以得出一个结论,读取的数据是什么决定了你接下来的动作是什么,并且按照先前的探讨可以发现即使不关闭总中断也不会导致资源使用的混乱问题,正是基于此考虑,认为很多情况下是可以不关闭中断的,但是当我看到如下uCOS II源码的时候,认为不关闭中断也是可以的,但是进行深入思考发现必须关闭中断才行。
在这里插入图片描述
看如上消息邮箱OSMboxPend的源码,可以发现在读取消息指针的时候就关闭了中断,但是按照先前的思考,即使没有关闭中断,大不了在读取过程中被中断,然后将OSEventPtr设置为非零状态。但是不会影响后面的判断操作,但是真的如此吗?
在这里插入图片描述
从上面两种情况分析可以发现,在不关闭中断的情况下确实可以保证判断语句正确执行(这里假设pmsg = pevent->OSEventPtr;这条C语言语句需要3条汇编语句操作执行),但是最终根据读取的pmsg值进行判读后的结果却会导致两种截然不同的效果。一个是直接返回,当前任务正常执行,另一种情况是将当前任务设置为等待状态,除此之外可能还会有影响整个系统混乱的操作,这是绝对不允许的情况。
在之前分析共享资源的互斥量时发现也会出现这种情况,但是为什么却不会没事呢?这是因为最糟糕的情况就是都获取不到共享资源,而一般来说都获取不到资源的虽然少见,但在之前那种不管中断操作的情况下确实会出现,但是即使都获取不到资源也不会导致系统严重后果,因为一般来说,当自己没有获取到资源的时候,下次还会继续尝试获取,另一个任务同样如此,在不管中断的情况下,只是导致重新获取资源的话,这是可以接受的一种情况。但是现在分析的这种情况却不允许,因为本来能正常运行的,你却让它进入等待状态,很可能导致严重的后果。所以在判断问题上,如果打断判断的不同结果会导致非常严重的后果,那么最好将读取、判断、分类动作设置成原子操作(不可打断的操作),而将整个流程设置成原子操作的一般方法就是关中断。
----------------------------------------------------------------------------------更新2018/08/23 Osprey
CM3的互斥访问
在这里插入图片描述

欢迎关注公众号,获取最新内容
在这里插入图片描述

没有更多推荐了,返回首页