信号量主要用来进行资源管理和任务同步,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 prvCopyDataToQueue( Queue_t * const pxQueue, const void *pvItemToQueue, const BaseType_t xPosition )
{
BaseType_t xReturn = pdFALSE;
UBaseType_t uxMessagesWaiting;
/* 获取当前队列数量 */
uxMessagesWaiting = pxQueue->uxMessagesWaiting;
if( pxQueue->uxItemSize == ( UBaseType_t ) 0 )
{
/* 什么都不做 */
}
/* 当前列表项数量加1 */
pxQueue->uxMessagesWaiting = uxMessagesWaiting + ( UBaseType_t ) 1;
return xReturn;
}
二值信号量的获取(xSemaphoreTake)跟前面队列的接收大同小异,不同的地方在于队列会将队列的数据复制出来给用户,而二值信号量只是递减信号量的计数值(uxMessagesWaiting),和前面的信号量释放相对应。其它基本一致,这里就不放代码分析了。
计数型信号量的接口函数如下:
/* 创建计数型信号量(不同于二值信号量,这个创建的队列长度由用户自定义,二值信号量队列长度只能为1) */
xSemaphoreCreateCounting()
xSemaphoreCreateCountingStatic(pxStaticSemaphore)
/* 释放计数型信号量 */
xSemaphoreGive(xSemaphore)
xSemaphoreGiveFromISR(xSemaphore, pxHigherPriorityTaskWoken)
/* 获取计数型信号量 */
xSemaphoreTake(xSemaphore, xBlockTime)
xSemaphoreTakeFromISR(xSemaphore, pxHigherPriorityTaskWoken)
创建计数型信号量源码分析:
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
#define xSemaphoreCreateCounting( uxMaxCount, uxInitialCount ) xQueueCreateCountingSemaphore( ( uxMaxCount ), ( uxInitialCount ) )
#endif
QueueHandle_t xQueueCreateCountingSemaphore( const UBaseType_t uxMaxCount, const UBaseType_t uxInitialCount )
{
QueueHandle_t xHandle;
configASSERT( uxMaxCount != 0 );
configASSERT( uxInitialCount <= uxMaxCount );
/* 创建一个队列长度为用户自定义的最大计数值,队列项大小为0的队列用作计数值信号量,实际上只使用队列的 uxMessagesWaiting 来计数 */
xHandle = xQueueGenericCreate( uxMaxCount, queueSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_COUNTING_SEMAPHORE );
if( xHandle != NULL )
{
/* 初始化计数值队列计数值(由用户自行定义) */
( ( Queue_t * ) xHandle )->uxMessagesWaiting = uxInitialCount;
traceCREATE_COUNTING_SEMAPHORE();
}
else
{
traceCREATE_COUNTING_SEMAPHORE_FAILED();
}
return xHandle;
}
释放计数型信号量源码分析:
#define xSemaphoreGive( xSemaphore ) xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ), NULL, semGIVE_BLOCK_TIME, queueSEND_TO_BACK )
这里可以看出还是调用的队列发送函数,发送数据为空,等待时间为0,实际上跟二值信号量的处理过程一样,都是去递增队列项的数量(uxMessagesWaiting)。
获取计数型信号量源码分析:
#define xSemaphoreTake( xSemaphore, xBlockTime ) xQueueSemaphoreTake( ( xSemaphore ), ( xBlockTime ) )
计数型信号量的获取函数和二值信号量的获取函数一样,实际上它们都不需要去获取数据,只需要去判断队列项的数量(uxMessagesWaiting)这个变量就可以了。
互斥信号量的接口函数如下:
/* 创建互斥信号量 */
xSemaphoreCreateMutex()
xSemaphoreCreateMutexStatic(pxMutexBuffer)
/* 释放互斥信号量 */
xSemaphoreGive(xSemaphore)
/* 获取互斥信号量 */
xSemaphoreTake(xSemaphore, xBlockTime)
创建互斥信号量源码分析:
QueueHandle_t xQueueCreateMutex( const uint8_t ucQueueType )
{
QueueHandle_t xNewQueue;
const UBaseType_t uxMutexLength = ( UBaseType_t ) 1, uxMutexSize = ( UBaseType_t ) 0;
/* 创建一个队列长度为1,队列项大小为0的队列用作互斥信号量(和二值信号量一样) */
xNewQueue = xQueueGenericCreate( uxMutexLength, uxMutexSize, ucQueueType );
/* 初始化互斥信号量参数,并初始为释放状态 */
prvInitialiseMutex( ( Queue_t * ) xNewQueue );
return xNewQueue;
}
通用队列创建函数前面章节已经分析了(xQueueGenericCreate),这里分析下初始化互斥信号量的源码:
static void prvInitialiseMutex( Queue_t *pxNewQueue )
{
if( pxNewQueue != NULL )
{
/* 初始化互斥信号量的拥有者为空(因为刚创建,暂时没任务来获取) */
pxNewQueue->u.xSemaphore.xMutexHolder = NULL;
/* 队列类型为互斥信号量 */
pxNewQueue->uxQueueType = queueQUEUE_IS_MUTEX;
/* 初始化递归互斥信号量的递归次数为0 */
pxNewQueue->u.xSemaphore.uxRecursiveCallCount = 0;
traceCREATE_MUTEX( pxNewQueue );
/* Start with the semaphore in the expected state. */
/* 初始化信号量为释放状态(队列通用发送函数前面章节已经分析了) */
( void ) xQueueGenericSend( pxNewQueue, NULL, ( TickType_t ) 0U, queueSEND_TO_BACK );
}
else
{
traceCREATE_MUTEX_FAILED();
}
}
释放互斥信号量的源码分析:
#define xSemaphoreGive( xSemaphore ) xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ), NULL, semGIVE_BLOCK_TIME, queueSEND_TO_BACK )
实际上也是调用了通用发送函数,但是通用发送函数里面有一段专门关于互斥信号量的分支代码,前面章节没分析,这里先回顾一下:
BaseType_t xQueueGenericSend( QueueHandle_t xQueue, const void * const pvItemToQueue, TickType_t xTicksToWait, const BaseType_t xCopyPosition )
{
....
/* 保存数据到队列中 */
xYieldRequired = prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );
....
}
static BaseType_t prvCopyDataToQueue( Queue_t * const pxQueue, const void *pvItemToQueue, const BaseType_t xPosition )
{
....
if( pxQueue->uxItemSize == ( UBaseType_t ) 0 )
{
/* 使用了互斥信号量执行这个分支 */
#if ( configUSE_MUTEXES == 1 )
{
/* 队列类型为互斥信号量 */
if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
{
/* 处理优先级继承的问题 */
xReturn = xTaskPriorityDisinherit( pxQueue->u.xSemaphore.xMutexHolder );
/* 处理完后清空互斥信号量的拥有者 */
pxQueue->u.xSemaphore.xMutexHolder = NULL;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_MUTEXES */
}
....
}
上面在释放互斥信号量的时候增加了一个互斥信号量的处理,用于解决任务在优先级继承之后信号量被释放的处理,互斥信号量被释放后如果因为获取发生过优先级继承,需要把任务的优先级变为没被继承前的基优先级,然后放入就绪列表等待执行,源码分析如下:
BaseType_t xTaskPriorityDisinherit( TaskHandle_t const pxMutexHolder )
{
TCB_t * const pxTCB = pxMutexHolder;
BaseType_t xReturn = pdFALSE;
/* 此信号量是否已经被其它任务获取执行这个分支 */
if( pxMutexHolder != NULL )
{
configASSERT( pxTCB == pxCurrentTCB );
configASSERT( pxTCB->uxMutexesHeld );
/* 当前互斥信号量个数,每释放一次减一 */
( pxTCB->uxMutexesHeld )--;
/* 是否存在优先级继承,如果存在的话任务当前优先级肯定和任务基优先级不同 */
if( pxTCB->uxPriority != pxTCB->uxBasePriority )
{
/* 判断当前释放的是不是任务所获取到的最后一个互斥信号量,只有在没有互斥体的情况下才取消继承权 */
if( pxTCB->uxMutexesHeld == ( UBaseType_t ) 0 )
{
/* 把任务从就绪列表移除 */
if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
{
/* 任务继承来的这个优先级对应的就绪表中没有其他任务的话就将取消这个优先级的就绪态 */
taskRESET_READY_PRIORITY( pxTCB->uxPriority );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
traceTASK_PRIORITY_DISINHERIT( pxTCB, pxTCB->uxBasePriority );
/* 重新设置任务的优先级为任务的基优先级uxBasePriority */
pxTCB->uxPriority = pxTCB->uxBasePriority;
/* 复位任务的事件列表项 */
listSET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) pxTCB->uxPriority );
/* 重新添加到任务就绪表中 */
prvAddTaskToReadyList( pxTCB );
/* 返回需要进行一次任务切换 */
xReturn = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* 互斥信号量未被其它任务获取,队列还是满的则返回pdFALSE,否则返回pdTRUE */
return xReturn;
}
获取互斥信号量使用 xSemaphoreTake 这个函数,虽然和前面二值信号量和互斥型信号量使用同一个函数,但是针对互斥型信号量又增加了一些处理,源码分析如下:
BaseType_t xQueueSemaphoreTake( QueueHandle_t xQueue, TickType_t xTicksToWait )
{
....
/* 针对互斥信号量,在信号量被释放后执行这个分支 */
#if ( configUSE_MUTEXES == 1 )
{
if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
{
/* 标记互斥信号量被此任务占用并且返回占用者的任务控制块,这个函数内部只做了 ( pxCurrentTCB->uxMutexesHeld )++; 处理来标示互斥信号量被占用 */
pxQueue->u.xSemaphore.xMutexHolder = pvTaskIncrementMutexHeldCount();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif
/* 针对互斥信号量,在信号量一直被占用后执行这个分支,对占用的这段时间做超时处理 */
#if ( configUSE_MUTEXES == 1 )
{
if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
{
taskENTER_CRITICAL();
{
/* 当前互斥信号量正在被占用,传入占用信号量的任务作为参数,判断是否进行任务优先级反转 */
/* xInheritanceOccurred = pdTURE 表示发生了优先级继承 */
xInheritanceOccurred = xTaskPriorityInherit( pxQueue->u.xSemaphore.xMutexHolder );
}
taskEXIT_CRITICAL();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif
}
在互斥信号量被占用时,再度获取信号量的任务需要放入延时列表和事件等待列表中,此时有可能就会导致优先级翻转,看下代码上是怎么解决这个问题的:
BaseType_t xTaskPriorityInherit( TaskHandle_t const pxMutexHolder )
{
TCB_t * const pxMutexHolderTCB = pxMutexHolder;
BaseType_t xReturn = pdFALSE;
/* 互斥信号量当前已被其它任务前获取了,用户再次去获取就会执行这个分支,可能会进行优先级继承 */
if( pxMutexHolder != NULL )
{
/* 占用信号量的那个任务优先级小于当前任务的优先级执行此分支 */
if( pxMutexHolderTCB->uxPriority < pxCurrentTCB->uxPriority )
{
if( ( listGET_LIST_ITEM_VALUE( &( pxMutexHolderTCB->xEventListItem ) ) & taskEVENT_LIST_ITEM_VALUE_IN_USE ) == 0UL )
{
listSET_LIST_ITEM_VALUE( &( pxMutexHolderTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) pxCurrentTCB->uxPriority );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* 如果互斥信号量的拥有者当前为就绪状态则执行此分支 */
if( listIS_CONTAINED_WITHIN( &( pxReadyTasksLists[ pxMutexHolderTCB->uxPriority ] ), &( pxMutexHolderTCB->xStateListItem ) ) != pdFALSE )
{
/* 从就绪列表中移除 */
if( uxListRemove( &( pxMutexHolderTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
{
/* 清除对应的就绪优先级标识 */
taskRESET_READY_PRIORITY( pxMutexHolderTCB->uxPriority );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* 修改互斥信号量占有者的优先级跟当前任务优先级一致,即拉高了互斥信号量占有者的优先 */
pxMutexHolderTCB->uxPriority = pxCurrentTCB->uxPriority;
/* 修改完毕,重新放回就绪列表中 */
prvAddTaskToReadyList( pxMutexHolderTCB );
}
/* 互斥信号量占有者当前为非就绪状态 */
else
{
/* 直接修改互斥信号量占任务的优先级 */
pxMutexHolderTCB->uxPriority = pxCurrentTCB->uxPriority;
}
traceTASK_PRIORITY_INHERIT( pxMutexHolderTCB, pxCurrentTCB->uxPriority );
/* 返回值告知发生了优先级继承 */
xReturn = pdTRUE;
}
/* 占用信号量的那个任务优先级大于或等于当前任务的优先级执行此分支 */
else
{
/* 互斥信号量占有者的基优先级小于当前任务的优先级执行此分支 */
if( pxMutexHolderTCB->uxBasePriority < pxCurrentTCB->uxPriority )
{
/* 返回告知发生了优先级继承 */
xReturn = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* 返回是否发生了优先级继承 */
return xReturn;
}
实际上就是根据信号量占有当前的任务优先级来决定是不是需要拉高信号量占有者的优先级,相当于继承后面那个高优先级任务的优先级,当信号量被释放时避免给其它低优先级任务优先执行了。
最后一个递归互斥信号量的函数接口如下 :
/* 创建递归互斥信号量 */
xSemaphoreCreateRecursiveMutex();
xSemaphoreCreateRecursiveMutexStatic(pxStaticSemaphore);
/* 释放递归互斥信号量 */
xSemaphoreGiveRecursive(xMutex);
/* 获取递归互斥信号量 */
xSemaphoreTakeRecursive(xMutex, xBlockTime);
创建函数和上面互斥信号量的创建函数一样的,这里先分析一下释放递归互斥信号量的函数:
#if( configUSE_RECURSIVE_MUTEXES == 1 )
#define xSemaphoreGiveRecursive( xMutex ) xQueueGiveMutexRecursive( ( xMutex ) )
#endif
BaseType_t xQueueGiveMutexRecursive( QueueHandle_t xMutex )
{
BaseType_t xReturn;
Queue_t * const pxMutex = ( Queue_t * ) xMutex;
configASSERT( pxMutex );
/* 检查递归互斥信号量是不是被当前任务获取的,要释放递归互斥信号量的任务
肯定是当前正在运行的任务。因为和互斥信号量一样,递归互斥信号量的获取
和释放要放在同一个任务中完成,如果当前正在运行的任务不是递归互斥信号
量的拥有者就不能释放 */
/* 检查这个任务是不是递归互斥信号量的拥有者 ,要释放递归互斥信号量的任务
肯定是当前正在运行的任务 */
if( pxMutex->u.xSemaphore.xMutexHolder == xTaskGetCurrentTaskHandle() )
{
traceGIVE_MUTEX_RECURSIVE( pxMutex );
/* uxRecursiveCallCount 用来记录递归信号量被获取的次数,每释放一次递减一次 */
( pxMutex->u.xSemaphore.uxRecursiveCallCount )--;
/* 当为0时说明是最后一次释放了 */
if( pxMutex->u.xSemaphore.uxRecursiveCallCount == ( UBaseType_t ) 0 )
{
/* 释放递归互斥信号量 */
( void ) xQueueGenericSend( pxMutex, NULL, queueMUTEX_GIVE_BLOCK_TIME, queueSEND_TO_BACK );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
xReturn = pdPASS;
}
else
{
xReturn = pdFAIL;
traceGIVE_MUTEX_RECURSIVE_FAILED( pxMutex );
}
return xReturn;
}
#endif
获取递归互斥信号量的源码分析:
#if( configUSE_RECURSIVE_MUTEXES == 1 )
#define xSemaphoreTakeRecursive( xMutex, xBlockTime ) xQueueTakeMutexRecursive( ( xMutex ), ( xBlockTime ) )
#endif
BaseType_t xQueueTakeMutexRecursive( QueueHandle_t xMutex, TickType_t xTicksToWait )
{
BaseType_t xReturn;
Queue_t * const pxMutex = ( Queue_t * ) xMutex;
configASSERT( pxMutex );
traceTAKE_MUTEX_RECURSIVE( pxMutex );
/* 判断当前要获取递归互斥信号量的任务是不是已经是递归互斥信号量的拥有者 */
if( pxMutex->u.xSemaphore.xMutexHolder == xTaskGetCurrentTaskHandle() )
{
/* 如果当前任务已经是递归互斥信号量的拥有者,说明任务已经获取了
递归互斥信号量,本次是重复获取递归互斥信号量 */
( pxMutex->u.xSemaphore.uxRecursiveCallCount )++;
xReturn = pdPASS;
}
else
{
/* 如果任务是第一次获取递归互斥信号量的话就需要调用信号
量获取函数完成真正的获取过程 */
xReturn = xQueueSemaphoreTake( pxMutex, xTicksToWait );
if( xReturn != pdFAIL )
{
/* 第一次获取递归互斥信号量成功后将uxRecursiveCallCount加一 */
( pxMutex->u.xSemaphore.uxRecursiveCallCount )++;
}
else
{
traceTAKE_MUTEX_RECURSIVE_FAILED( pxMutex );
}
}
return xReturn;
}
这两个函数只是在前面操作的基础上增加了一些赋值和逻辑判断,它允许一个任务多次释放和获取一个互斥信号量。