信号量主要用来进行资源管理和任务同步,FreeRTOS中信号量分为二值信号量、计数型信号量、互斥信号量、递归互斥信号量。
二值信号量: 一个只有一个队列项,队列项大小为0的队列,队列只有满和空两种状态。一般用于中断和任务之间的同步,例如串口通信,在串口通信中断服务函数中接收到串口发送过来的数据后释放二值信号量来通知串口的处理任务接收到了数据,串口处理任务由原来的阻塞状态恢复为就绪状态,实现了在没数据时阻塞将CPU让给其它任务执行,在收到数据后,又能立马得到执行。
计数型信号量:一个有N个队列项,队列项大小为0的队列,每当事件发生的时候事件处理函数就会释放信号量(等同于发送数据到队列),每释放一次信号量的计数值就递增一次,也就是队列项个数加一,当其他任务来获取计数型信号量时(等同于从队列中接收数据),信号量计数值就减一。根据这个计数值,一般可用来查看一项资源的使用情况。
互斥信号量:一个拥有优先级继承的二值信号量。在访问二值信号量时由于队列满或者空导致任务被阻塞时,其它任务想访问队列也会被阻塞,这不已经是互斥了吗?但是为什么还要专门弄出个互斥信号量来呢?主要还是因为互斥信号量具有优先级继承的功能,优先级继承是为了解决优先级翻转的问题,优先级翻转就是由于某种特殊情况下导致低优先级任务先于高优先级任务执行,这里参考正点原子FreeRTOS教材的解析:
当一个互斥信号量正在被一个低优先级的任务使用,而此时有个高优先级的任务也尝试获取这个互斥信号量的话就会被阻塞,被阻塞期间的如果有个中优先级任务占用了CPU资源,导致信号来时低优先级任务也不能得到及时处理从而导致高优先级也没法得到处理,不过使用互斥信号量后这个高优先级的任务会将低优先级任务的优先级提到与自己相同的优先级,这个过程就是优先级继承,由于存在优先级继承所以互斥信号量不能用于中断中。优先级继承并不能完全消除优先级翻转,它只是尽可能的降低优先级翻转带来的影响。在写代码时应尽可能避免优先级翻转的发生。
递归互斥信号量:一个特殊的互斥信号量,已经获取了互斥信号量的任务不能再获取这个信号量,但递归互斥信号量可以,而且次数不限。
下面对各种信号量的源码进行分析,实际上信号量的源码和前面队列的源码大同小异,信号量是在队列的基础上抽离出来的一个概念,前面分析过的函数这里只是简单过一下,不具体分析了,需要的话可以先看下队列的源码分析。
二值信号量的接口函数如下:
/* 创建二值信号量 */
xSemaphoreCreateBinary()
xSemaphoreCreateBinaryStatic(pxStaticSemaphore)
/* 释放二值信号量 */
xSemaphoreGive(xSemaphore)
xSemaphoreGiveFromISR(xSemaphore, pxHigherPriorityTaskWoken)
/* 获取二值信号量 */
xSemaphoreTake(xSemaphore, xBlockTime)
xSemaphoreTakeFromISR(xSemaphore, pxHigherPriorityTaskWoken)
创建二值信号量可以使用动态创建和静态创建两种,静态创建的话用户自行分配一个 StaticSemaphore_t 类型变量作为形参。二值信号量实际上是创建一个队列项个数为1,队列项大小为0,等待时间为0的队列,实际上只使用队列的 uxMessagesWaiting 变量来代表有无数据,定义如下:
#define semSEMAPHORE_QUEUE_ITEM_LENGTH ( ( uint8_t ) 0U )
#define queueQUEUE_TYPE_BINARY_SEMAPHORE ( ( uint8_t ) 3U )
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
#define xSemaphoreCreateBinary() xQueueGenericCreate( ( UBaseType_t ) 1, semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE )
#endif
二值信号量创建调用的是队列创建函数,前面已经分析过了。再分析下释放二值信号量(往队列发数据):
#define semGIVE_BLOCK_TIME ( ( TickType_t ) 0U )
#define xSemaphoreGive( xSemaphore ) xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ), NULL, semGIVE_BLOCK_TIME, queueSEND_TO_BACK )
#define xSemaphoreGiveFromISR( xSemaphore, pxHigherPriorityTaskWoken ) xQueueGiveFromISR( ( QueueHandle_t ) ( xSemaphore ), ( pxHigherPriorityTaskWoken ) )
从源码中可以看出来,二值信号量只是简单的将队列项数量加一来表示:
BaseType_t xQueueGenericSendFromISR( QueueHandle_t xQueue, const void * const pvItemToQueue, BaseType_t * const pxHigherPriorityTaskWoken, const BaseType_t xCopyPosition )
{
.....
/* 如果队列是空的,二值信号量在复制一次数据到队列后就不再进入了,因为 uxLength 定义为1 */
if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == queueOVERWRITE ) )
{
( void ) prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );
}
.....
}
static BaseType_t prvCopyDataToQueu