Task之互斥制信号量

  • 2019 年 10 月 31 日
  • 筆記

多任务系统里经常面临一个问题:资源竞争。哪些资源呢?很多,例如外设、文件、数据等。当多个任务同时访问这些资源,就会发生冲突。例如下面这段代码

每调用一次函数myBufPut(),就会在数组myBuf[]中存入一个字符,这相当于一个入栈操作。在实际系统中,任务多了,任何情况都可能发生,假如有两个任务(T1、T2)都调用这个函数,就可能出现以下执行序列

T1刚把myBufIndex加了1,就被高优先级的T2抢占了。然后必须等T2退出就绪队列,T1才能继续执行。最后的结果是myBuf[0]里啥也没存,而myBuf[1]里先被存入'b',又被存入'a'。可见仅仅两行代码,就会出乱子。那如果只有一行代码呢?

这个函数够简单了吧,就一条执行++的语句。反编译看一下

可以看到,机器在执行时,是把它拆为三条指令的。也就是有两个位置可以抢占的,资源冲突的风险还是存在的。 更为严重的是,这种冲突发生的机率很小!例如T2早一点或晚一点进入就绪队列,都不会干扰到T1的执行。所以这种资源冲突很难复现出来。这种不确定性,给软件的调试带来了很大麻烦。 那怎么办呢,也就是如何避免冲突呢?VxWorks给出的主要解决方案是互斥信号量-Mutex Semaphore 多个任务竞争资源时,给共享的这个资源加上一个互斥信号量,就相当于加上一把锁。想要访问资源的任务,需要先申请互斥锁。如果申请成功,就可以访问资源;否则就得阻塞。资源访问完毕,需要释放互斥锁;然后其它任务才能够访问。 这个互斥信号量就像《Task之二进制信号量》一样,使用之前,也需要创建

返回值就是信号量的ID,参数options的取值通常有四个

SEM_Q_FIFO与SEM_Q_PRIORITY是互斥关系,创建信号量时,必须二选一,决定了多个任务阻塞到同一个信号量上的时候,要采用哪种排队方式。 创建信号量之后,就可以使用它了。用的是与二进制信号量一样的两个函数:semTake()用来申请信号量,semGive()用来释放信号量。

myBufPut()的例子就可以改成这样了

这样使用互斥信号量保护共享资源后,就不可能有多个任务同时访问它了

不过互斥信号量的使用规则有些不同

  • 互斥信号量是谁申请、谁释放
  • 申请成功后,可以再次申请,不过要释放同样的次数
  • 中断里本来就不能申请任何信号量,所以中断里也不能释放互斥信号量

流程图也就有所不同了

写个例子,尽可能多的覆盖这两个流程的分支

跑一下,看看效果

可以看到

  • task1可以多次申请同一信号量
  • task1释放同样次数后,其它阻塞任务才可能解除阻塞
  • option用的0,即SEM_Q_FIFO,因此先创建的task2先解除阻塞
  • task1释放次数过多时,报错

把option换成SEM_Q_PRIORITY试试

这次是task3先解除阻塞了,因为它的优先级高一些

互斥信号量在使用时,还有些需要注意的地方

上图中,三个任务的优先级都不一样,tHigh在竞争tLow已占用的同一资源时,被迫阻塞了。而这个时候,tMedium因为已经就绪就会抢在tLow之前执行。这种现象就叫做Priority Inversion,可以翻译为优先级倒置、反转、逆转、翻转等。意思就是说优先级高一些的tHigh需要等待优先级略低的tMedium。 这显然违背了实时系统的基本思想!

怎么解决呢?非常简单,在创建互斥信号量时,指定选项SEM_INVERSION_SAFE。这样当tHigh申请同一个信号量时,系统就会将持有该信号量的tLow的优先级临时提高到与tHigh相同,tLow就会先于tMedium执行,tMedium就不会干扰到tHigh了

有一点要注意的是:SEM_INVERSION_SAFE需要和SEM_Q_PRIORITY一起使用。 可能有人会疑惑,tHigh不是还要等tLow释放信号量才能执行吗?tLow的优先级本来更低呀。 tHigh不得不等,因为它俩要访问同一资源,否则会出现冲突。不过这也提醒我们,一个任务持有互斥信号量的时间要尽可能的短,不然可能会拖累高优先级的任务。 怎样才算短呢?在持有信号量的时候,尽量不要调用可能阻塞或延时的操作!

持有互斥信号量还有一个风险:任务被意外删除。这样信号量将永远不会被释放了,其它任务也不可能申请成功了。为了避免这样情况,要谨慎使用taskDelete(),另外,互斥信号量也提供了一种选项:SEM_DELETE_SAFE。有了这个选项,任务在持有该信号量时,尝试删除它的taskDelete()就会返回ERROR。 因此互斥信号量创建时的选项经常写作

最后,动态申请的信号量如果不用了,需要使用semDelete()删除。另外,在Shell里可以用show()命令来查看信号量的属性

最后的最后,留一个问题:听说过死锁吗?

这正是: 资源共享有风险,多人同用易混乱。 互斥Semaphore来上锁,谨慎使用保平安。