信号量包括二值信号量、互斥信号量、计数型信号量、递归互斥信号量,用于管理共享资源以及任务间的同步,利用信号量值代表信号量的情况,当申请信号量时,信号量值会减一,当释放信号量时,信号量值会加一,所以任务间要共享某个变量或者要进行同步时,只需要查询信号量中的信号量值是否满足。
二值信号量
舍友B在洗澡,占了浴室,而浴室一般是一个人在里面洗澡,并且锁上门,这时候舍友A跑再快去浴室也没用。
舍友A由于抢不到浴室,就会每隔一段时间去敲一下浴室门。询问一下浴室里面的舍友A究竟出来没。
舍友A使用了浴室,浴室又给占用了,接下来舍友C舍友D想要使用浴室就只能等待舍友A出来。
创建二值信号量API:
释放信号量(二值信号量)计数型信号量、互斥信号量公用该表的API):
使用队列的发送函数,但是实际上并没有发送内容,只是在入队的时候,队列项会加一,通过判断队列中的队列项是否为一,来判断信号量是否有效。
注意:xSemaphoreGiveFromISR()函数只能用来释放二值信号量和计数型信号量,互斥信号量舍家都优先级继承的问题,中断API不属于任务,并没有处理中断优先级继承。
二值信号量更适合用于任务同步的原因:互斥信号量拥有优先级继承机制,二值信号量没有。
总结: 二值信号量的使命就是进行任务间的同步,完成任务与任务或者人物与中断之间的同步,主要还是应用任务与中断。
计数型信号量
二值信号量是创建队列长度为1的队列,计数型型信号量是创建队列长度大于1的队列,同样计数型信号量不能用来存储大量消息内容,所以用户只需要关心队列是否未满即可。通常用于:事件计数、资源管理,两个场合大同小异。
创建计数型信号量API
追踪进去,最后还是使用队列创建函数实现
通过队列创建函数创建完成后会再将队列结构成员初始化为计数信号量初值(源码简化)
QueueHandle_t xQueueCreateCountingSemaphore( const UBaseType_t uxMaxCount, const UBaseType_t uxInitialCount )
{
QueueHandle_t xHandle;
/*创建一个队列长度为uxMaxCount,队列项长度为0,队列类型为queueQUEUE_TYPE_COUNTING_SEMAPHORE 的队列*/
xHandle=xQueueGenericCreate(uxMaxCount, queueSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_COUNTING_SEMAPHORE );
if( xHandle != NULL )
{
//将成员初始化,代表计数型信号量值初始值
( ( Queue_t * ) xHandle )->uxMessagesWaiting = uxInitialCount;
}
else
{
}
return xHandle;
}
优先级翻转
任务H本来拥有最高的优先级执行权,但是在执行过程中,却因为某个信号量被任务L占领,不得不挂起,但此时任务L又被任务M抢占了CPU的使用权,因为任务M的优先级比它高,此时就出现了优先级翻转的问题,任务M、L比任务H先完成。
互斥信号量
优先级翻转在实时操作系统中是不允许出现的,因为一旦出现优先级翻转问题,某些任务需要第一时间处理,但是出现优先级翻转问题,导致任务不能马上运行解决。互斥信号量的出现就是为了解决优先级翻转问题。
互斥信号量与二值信号量唯一不同之处就是具有优先级继承的特性,高优先级任务在申请信号量时,如果此时信号量被低优先级任务所占用,高优先级任务同样会处于阻塞状态,但是互斥信号量还会再做一份工作,就是把低优先级任务提升到跟自己同等优先级,这样按照调度器算法,当低优先级任务执行完成后,调度器会再次遍历同等优先级中是否还有其它的任务,将CPU执行权限给与任务。这样就就能经可能的避免优先级翻转带来的影响。之所以不能完全消除主要是由于互斥信号量并不能在中断函数中使用,第一,因为中断特性(快进快出),不可能因为互斥信号量而阻塞。第二,中断API不支持处理优先级继承问题。
创建互斥信号量API
互斥信号量解决了优先级翻转其实就是在申请信号量的时候判断占用信号量的基任务是否比自己的任务优先级低,如果是就提升。在释放信号量时检查当前优先级是否等于基优先级,如果不是就恢复优先级,然后重新加入到就绪队列中。
递归互斥信号量
特殊的互斥信号量,互斥信号量只能获取一次,但是递归互斥信号量可以无限的获取递归互斥信号量,所以在释放的时候必须根据获得次数进行释放。
总结:递归互斥信号量应用在针对响应某一个任务对某一共享资源的多次请求,而共享资源已被锁起来,但是我们又不希望该任务被挂起,所以允许任务多次获取。