信号量简介
信号量是一种解决同步问题的机制,可以实现对共享资源的有序访问,也就是通过信号量可以判断是否有资源。
可以用以下示意图展示,
- 当计数值大于0时,也就代表信号量有资源
- 当释放信号量时,信号量计数值,也就是资源数加一
- 当获取信号量时,信号量计数值,也就是资源数减一
- 信号量计数值有限制,当最大值限定为1时就是二值信号量,最大值不是1,就是计数型信号量
总结:信号量就是用来传递状态的,也就是是否有资源的。
信号量其实是基于队列实现的,信号量和队列的对比如下,
二值信号量
二值信号量实际上就是一个队列长度为 1 的队列,在这种情况下,队列就只有空和满两种情况。
二值信号量通常用于互斥访问或任务同步, 与互斥信号量比较类似,但是二值信号量有可能会导
致优先级翻转(后续解释)的问题,所以更适合任务同步。
二值信号量使用示意图,
如上图所示,Give操作就相当于把该标志置满,而Take操作则相当于把该标志取空,所以二值信号量其实和标志位很像。
二值信号量函数
使用过程:创建二值信号量 --> 释放二值信号量 --> 获取二值信号量
创建二值信号量
xSemaphoreCreateBinary():
#define xSemaphoreCreateBinary()
xQueueGenericCreate( ( UBaseType_t ) 1,
semSEMAPHORE_QUEUE_ITEM_LENGTH,
queueQUEUE_TYPE_BINARY_SEMAPHORE )
#define semSEMAPHORE_QUEUE_ITEM_LENGTH ((uint8_t) 0U)
此函数和我们使用队列一样,只是设置了队列长度为1,队列项目大小为0,最后这个宏调用的不同而已,不同的宏定义如下,
#define queueQUEUE_TYPE_BASE ( ( uint8_t ) 0U ) /* 队列 */
#define queueQUEUE_TYPE_SET ( ( uint8_t ) 0U ) /* 队列集 */
#define queueQUEUE_TYPE_MUTEX ( ( uint8_t ) 1U ) /* 互斥信号量 */
#define queueQUEUE_TYPE_COUNTING_SEMAPHORE ( ( uint8_t ) 2U ) /* 计数型信号量 */
#define queueQUEUE_TYPE_BINARY_SEMAPHORE ( ( uint8_t ) 3U ) /* 二值信号量 */
#define queueQUEUE_TYPE_RECURSIVE_MUTEX ( ( uint8_t ) 4U ) /* 递归互斥信号量 */
参数:
- uxQueueLength:队列长度
- uxItemSize:队列项目大小
返回值:如果队列创建成功,则返回二值信号量句柄;创建失败,返回NULL。
释放二值信号量
xSemaphoreGive():
#define xSemaphoreGive( xSemaphore)
xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ),
NULL,
semGIVE_BLOCK_TIME,
queueSEND_TO_BACK )
#define semGIVE_BLOCK_TIME ((TickType_t) 0U)
这个函数依旧是调用的队列发送函数,只是设置发送数据为NULL,阻塞时间为0。
返回值为pdPASS时,则释放信号量成功;否则表示释放失败。
获取二值信号量
xSemaphoreTake():
#define xSemaphoreTake( xSemaphore, xBlockTime)
xQueueSemaphoreTake( ( xSemaphore ), ( xBlockTime ))
参数:
- xSemaphore:要获取的信号量句柄
- xBlockTime:阻塞时间
返回值为pdTRUE时,则获取信号量成功;否则表示获取失败。
计数型信号量
计数型信号量相当于队列长度大于0 的队列,因此计数型信号量能够容纳多个资源。
计数型信号量使用场合:
- 事件计数:每次事件发生后,在事件处理函数中释放计数型信号量(计数值加 1),其他等待事件发生的任务获取计数型信号量(计数值减 1),这种场合计数值 一般在创建时设置为 0。
- 资源管理:信号量代表有效资源数目。任务想要访问共享资源,必须先获取这个共享资源的计数型信号量(计数值减1)。当然,在使用完共享资源后,也要释放资源的计数型信号量(计数值加1)。在这种场合下,计数值一般在创建时设置为受其管理的共享资源的最大可用数量。
计数型信号量函数
使用过程:创建计数型信号量 --> 释放计数型信号量 --> 获取计数型信号量
xSemaphoreCreateCounting():
#define xSemaphoreCreateCounting( uxMaxCount, uxInitialCount )
xQueueCreateCountingSemaphore( ( uxMaxCount ), ( uxInitialCount ))
参数:
- uxMaxCount:计数值最大值限定
- uxInitialCount:计数值初始值
返回值:如果队列创建成功,则返回计数型信号量句柄;创建失败,返回NULL。
由于计数型信号量的释放和获取函数和二值信号量的相关函数完全一样,所以要使用的话参考上面的二值信号量释放和获取函数。
优先级翻转
优先级翻转:高优先级任务反而慢执行,低优先级任务反而优先执行。
根据上图的例子,我们来解释优先级反转。
任务H:表示最高优先级任务,进行获取和释放二值信号量,之后进入1000ms的系统延时(进入阻塞状态)
任务M:表示中等优先级任务,只进行简单操作,比如打印
任务L:表示最低优先级任务,进行获取和释放二值信号量,但是在获取二值信号量后进行死延迟3000ms(不进入阻塞,)之后之后进入1000ms的系统延时(进入阻塞状态)
分析如下:
在程序最开始,当第一次释放二值信号量后,任务H会马上获取二值信号量,并在一定时间后将其释放进入1000ms阻塞状态,此时任务M进行简单操作程序,紧接着任务L会获取二值信号量(因为任务H已经释放),获取后由于需要死延迟3000ms,但是在1000ms以后任务H已经退出阻塞并进行抢占,此时执行任务H的获取操作,但是此时任务L并未将二值信号量释放所以任务H无法获得,并且此时任务M的阻塞时间到了就会进行抢占,也就形成了任务M一直在执行但是任务H却无法执行的现象,也就是优先级翻转。
互斥信号量
由于上述的优先级翻转问题,所以出现互斥信号量通过优先级继承机制尽可能的降低优先级翻转带来的影响。
互斥信号量不能用于中断函数,因为,
- 互斥信号量有任务优先级继承的机制, 但是中断不是任务,没有任务优先级, 所以互斥信号量只能用与任务中,不能用于中断服务函数。
- 中断服务函数中不能因为要等待互斥信号量而设置阻塞时间进入阻塞态。
从上图可以看出,互斥信号量就是会将低优先级的任务抬高到高优先级,这样中优先级任务就无法抢占原先低优先级任务,这一步就是优先级继承。
互斥信号量函数
使用过程:创建互斥信号量 --> 获取二值信号量 --> 释放二值信号量
可以发现,互斥信号量在创建之后可以直接获取,而不需要先释放。
xSemaphoreCreateMutex():
#define xSemaphoreCreateMutex() xQueueCreateMutex(queueQUEUE_TYPE_MUTEX)
该函数没有入口参数
返回值:如果互斥信号量创建成功,则返回互斥信号量句柄;创建失败,返回NULL。
备注:由于其他函数和二值信号量函数一样,所以不做过多介绍