FreeRTOS信号量简易分析
- 架构:Cortex-M3
- 版本:FreeRTOS V9.0.0
- 前言:在多线程的系统中,通常要处理一些同步或资源竞争的问题,这时候可以使用信号量。
1.二值信号量
1.1二值信号量的创建
#define xSemaphoreCreateBinary() xQueueGenericCreate( ( UBaseType_t ) 1, semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE )
创建后会返回一个句柄,用于以后获取和释放信号量。可以看到,创建二值信号量和创建队列调用同一个函数xQueueGenericCreate
,只不过二值信号量固定是创建一个队列项为1、队列大小为0。xQueueGenericCreate
之前讨论过了,这里就不浪费时间了。
初始化后的结构体:
![](https://i-blog.csdnimg.cn/blog_migrate/4cfdfc2cb2413aef0f1f751790126b2c.png)
1.2 释放二值信号量
1.2.1 普通释放二值信号量
#define xSemaphoreGive( xSemaphore ) xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ), NULL, semGIVE_BLOCK_TIME, queueSEND_TO_BACK )
要传入二值信号量句柄进去。可以看到这个和入队的函数xQueueGenericSend
又是一样的,但是在调用prvCopyDataToQueue
时并不是真正的入队,而只是uxMessagesWaiting
加1就完事了,那么基本就能判断出来,二值信号量是以uxMessagesWaiting
来判断是否有效。之后的流程和入队没有什么区别。
1.2.2 中断中释放二值信号量
#define xSemaphoreGiveFromISR( xSemaphore, pxHigherPriorityTaskWoken ) xQueueGiveFromISR( ( QueueHandle_t ) ( xSemaphore ), ( pxHigherPriorityTaskWoken ) )
1.3 获取二值信号量
1.3.1 普通获取二值信号量
#define xSemaphoreTake( xSemaphore, xBlockTime ) xQueueGenericReceive( ( QueueHandle_t ) ( xSemaphore ), NULL, ( xBlockTime ), pdFALSE )
要传入二值信号量句柄和阻塞时间进去。这个宏和出队函数xQueueGenericReceive
也是一样的。如果当前uxMessagesWaiting
>0,就是单纯的uxMessagesWaiting
减1,然后退出。如果当前没收到,就挂载到delaylist上。
1.3.2 中断中获取二值信号量
#define xSemaphoreTakeFromISR( xSemaphore, pxHigherPriorityTaskWoken ) xQueueReceiveFromISR( ( QueueHandle_t ) ( xSemaphore ), NULL, ( pxHigherPriorityTaskWoken ) )
2.计数信号量
2.1 创建计数信号量
#define xSemaphoreCreateCounting( uxMaxCount, uxInitialCount ) xQueueCreateCountingSemaphore( ( uxMaxCount ), ( uxInitialCount ) )
创建计数信号量要传入两个值,最大数量和初始数量。
具体分析xQueueCreateCountingSemaphore
QueueHandle_t xQueueCreateCountingSemaphore( const UBaseType_t uxMaxCount, const UBaseType_t uxInitialCount )
{
QueueHandle_t xHandle;
configASSERT( uxMaxCount != 0 );
configASSERT( uxInitialCount <= uxMaxCount );
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;
}
很明显,表面上的创建计数信号量,背地里又是创建队列xQueueGenericCreate
,创建队列的参数把uxMaxCount
传入,queueSEMAPHORE_QUEUE_ITEM_LENGTH
为0。
根据程序画出初始化后的结构体:
![](https://i-blog.csdnimg.cn/blog_migrate/e8e025a70c06da041f45ec2f296e9fd3.png)
2.2获取计数信号量
2.2.1普通获取计数信号量
#define xSemaphoreTake( xSemaphore, xBlockTime ) xQueueGenericReceive( ( QueueHandle_t ) ( xSemaphore ), NULL, ( xBlockTime ), pdFALSE )
和二值是一样的,如果把初始值设置为0,最大值设置为1,那就是二值信号量了。
2.2.2 中断中获取计数信号量
#define xSemaphoreTakeFromISR( xSemaphore, pxHigherPriorityTaskWoken ) xQueueReceiveFromISR( ( QueueHandle_t ) ( xSemaphore ), NULL, ( pxHigherPriorityTaskWoken ) )
2.3 释放计数信号量
2.3.1 普通释放计数信号量
#define xSemaphoreGive( xSemaphore ) xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ), NULL, semGIVE_BLOCK_TIME, queueSEND_TO_BACK )
同样和二值信号量一样,没什么好说的。
2.3.2 中断中释放计数信号量
#define xSemaphoreGiveFromISR( xSemaphore, pxHigherPriorityTaskWoken ) xQueueGiveFromISR( ( QueueHandle_t ) ( xSemaphore ), ( pxHigherPriorityTaskWoken ) )
3.互斥量
3.1互斥量的创建
用途:通常使用互斥量是为了同一个资源只能由一个任务访问。
#define xSemaphoreCreateMutex() xQueueCreateMutex( queueQUEUE_TYPE_MUTEX )
具体看xQueueCreateMutex
#if( ( configUSE_MUTEXES == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
QueueHandle_t xQueueCreateMutex( const uint8_t ucQueueType )
{
Queue_t *pxNewQueue;
const UBaseType_t uxMutexLength = ( UBaseType_t ) 1, uxMutexSize = ( UBaseType_t ) 0;
pxNewQueue = ( Queue_t * ) xQueueGenericCreate( uxMutexLength, uxMutexSize, ucQueueType );
prvInitialiseMutex( pxNewQueue );
return pxNewQueue;
}
#endif /* configUSE_MUTEXES */
可以看到又是熟悉的xQueueGenericCreate
,所以当你能看懂队列部分,那信号量这一篇基本上是信手拈来。
uxMutexLength
是1,uxMutexSize
是0。其中和其它类型的信号量这个函数不同的是prvInitialiseMutex
具体看函数prvInitialiseMutex
static void prvInitialiseMutex( Queue_t *pxNewQueue )
{
if( pxNewQueue != NULL )
{
/* The queue create function will set all the queue structure members
correctly for a generic queue, but this function is creating a
mutex. Overwrite those members that need to be set differently -
in particular the information required for priority inheritance. */
pxNewQueue->pxMutexHolder = NULL;
pxNewQueue->uxQueueType = queueQUEUE_IS_MUTEX;
/* In case this is a recursive mutex. */
pxNewQueue->u.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();
}
}
pxMutexHolder
是pcTail的宏定义,uxQueueType
是pcHead的宏定义。互斥量创建的时候最大的不同是会调用xQueueGenericSend
,调用了后会使得uxMessagesWaiting
变为1。
3.2释放互斥量
互斥量被某个任务释放时,代表着这个资源被任务占用,其它相同优先级的任务是无法访问的。
#define xSemaphoreTake( xSemaphore, xBlockTime ) xQueueGenericReceive( ( QueueHandle_t ) ( xSemaphore ), NULL, ( xBlockTime ), pdFALSE )
互斥量的释放也是调用xQueueGenericReceive
,按正常流程走,很简单的把uxMessagesWaiting
减1,并且把使用该互斥量的任务记录在pxMutexHolder
中。当前任务pxCurrentTCB
的持有互斥量个数uxMutexesHeld
加1。
3.3获取互斥量
互斥量被某个任务获取时,代表着这个资源被任务解放出来,其它任务可以正常访问
#define xSemaphoreGive( xSemaphore ) xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ), NULL, semGIVE_BLOCK_TIME, queueSEND_TO_BACK )
互斥量的获取调用的是xQueueGenericSend
,按照正常流程走,把uxMessagesWaiting
加1,然后把pxMutexHolder
清空,表示当前已经没有任务使用这个资源了。同样的再把uxMutexesHeld
减1。
3.4优先级反转
使用互斥量,能够降低优先级反转带来的影响,注意只是降低。
3.4.1 什么是优先级反转
比如我们创建了三个任务A、B、C,A的优先级为1,B的优先级为2,C的优先级为3。B、C处于一个阻塞的状态,此时A任务处于运行状态,使用互斥量来访问资源X,在访问的过程中,B、C由于外部因素从阻塞状态变为等待状态,那么调度器就会优先从A任务切换到C任务,原因是C的优先级比A和B的优先级大。那么这时,C想要去访问资源X,发现访问不了,原因是资源X已经被A任务占有了,迫于无奈,C只能进入阻塞状态并切换到B任务,等B任务执行完了,B任务就进入阻塞状态,那么CPU的使用权又到了A任务上,A任务就慢悠悠的执行直到释放资源X,释放了资源X后,C才能从阻塞状态变为执行状态,并且去访问资源X。(以上只是举个没有优先级反转处理的例子,实际上FreeRTOS的互斥量是有处理优先级反转这个问题的)。通过这个例子可以看到,明明C的优先级是最高的,却让B先执行了。
没有优先级反转处理的任务执行流程:
![](https://i-blog.csdnimg.cn/blog_migrate/615cb6eca232af9e3fd8f0c4fa78163f.png)
A(被高优先级抢占)->C(无法访问资源X而阻塞)->B(正常执行并切换任务)->A(释放了资源X,而被高优先级抢占)->C(正常执行)
3.4.2 FreeRTOS对优先级反转的处理
依旧举上面这个例子创建三个任务A、B、C,**A的优先级为1**,**B的优先级为2**,**C的优先级为3**。B、C处于一个阻塞的状态,此时A任务处于运行状态,使用互斥量来访问资源X,结合FreeRTOS互斥量的代码来分析A在使用互斥量时还做了什么:
void *pvTaskIncrementMutexHeldCount( void )
{
/* If xSemaphoreCreateMutex() is called before any tasks have been created
then pxCurrentTCB will be NULL. */
if( pxCurrentTCB != NULL )
{
( pxCurrentTCB->uxMutexesHeld )++;
}
return pxCurrentTCB;
}
它把A这个任务记录了下来。那么此时外部因素使得C任务先执行,C也要访问这个资源X,结合代码看看发生了什么:
#if ( configUSE_MUTEXES == 1 )
{
if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
{
taskENTER_CRITICAL();
{
vTaskPriorityInherit( ( void * ) pxQueue->pxMutexHolder );
}
taskEXIT_CRITICAL();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif
进入临界区并调用vTaskPriorityInherit
void vTaskPriorityInherit( TaskHandle_t const pxMutexHolder )
{
TCB_t * const pxTCB = ( TCB_t * ) pxMutexHolder;
/* If the mutex was given back by an interrupt while the queue was
locked then the mutex holder might now be NULL. */
if( pxMutexHolder != NULL )
{
/* If the holder of the mutex has a priority below the priority of
the task attempting to obtain the mutex then it will temporarily
inherit the priority of the task attempting to obtain the mutex. */
if( pxTCB->uxPriority < pxCurrentTCB->uxPriority )
{
/* Adjust the mutex holder state to account for its new
priority. Only reset the event list item value if the value is
not being used for anything else. */
if( ( listGET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ) ) & taskEVENT_LIST_ITEM_VALUE_IN_USE ) == 0UL )
{
listSET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) pxCurrentTCB->uxPriority ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* If the task being modified is in the ready state it will need
to be moved into a new list. */
if( listIS_CONTAINED_WITHIN( &( pxReadyTasksLists[ pxTCB->uxPriority ] ), &( pxTCB->xStateListItem ) ) != pdFALSE )
{
if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
{
taskRESET_READY_PRIORITY( pxTCB->uxPriority );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* Inherit the priority before being moved into the new list. */
pxTCB->uxPriority = pxCurrentTCB->uxPriority;
prvAddTaskToReadyList( pxTCB );
}
else
{
/* Just inherit the priority. */
pxTCB->uxPriority = pxCurrentTCB->uxPriority;
}
traceTASK_PRIORITY_INHERIT( pxTCB, pxCurrentTCB->uxPriority );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
首先会判断C任务pxCurrentTCB->uxPriority
的优先级是不是大于A优先级pxTCB->uxPriority
,很明显是大于的,把A任务从旧的优先级List(优先级:1)上移除,并挂载到新的优先级List(优先级:3)上,然后把A优先级变得和C优先级一样为3,A从阻塞状态变为等待状态。那么此时就会去执行A任务,A就会执行到xSemaphoreGive
把互斥量归还,具体看xSemaphoreGive
做了什么:
#if ( configUSE_MUTEXES == 1 )
{
if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
{
/* The mutex is no longer being held. */
xReturn = xTaskPriorityDisinherit( ( void * ) pxQueue->pxMutexHolder );
pxQueue->pxMutexHolder = NULL;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_MUTEXES */
可以看到真正处理优先级问题的是xTaskPriorityDisinherit
接着看xTaskPriorityDisinherit
#if ( configUSE_MUTEXES == 1 )
BaseType_t xTaskPriorityDisinherit( TaskHandle_t const pxMutexHolder )
{
TCB_t * const pxTCB = ( TCB_t * ) pxMutexHolder;
BaseType_t xReturn = pdFALSE;
if( pxMutexHolder != NULL )
{
/* A task can only have an inherited priority if it holds the mutex.
If the mutex is held by a task then it cannot be given from an
interrupt, and if a mutex is given by the holding task then it must
be the running state task. */
configASSERT( pxTCB == pxCurrentTCB );
configASSERT( pxTCB->uxMutexesHeld );
( pxTCB->uxMutexesHeld )--;
/* Has the holder of the mutex inherited the priority of another
task? */
if( pxTCB->uxPriority != pxTCB->uxBasePriority )
{
/* Only disinherit if no other mutexes are held. */
if( pxTCB->uxMutexesHeld == ( UBaseType_t ) 0 )
{
/* A task can only have an inherited priority if it holds
the mutex. If the mutex is held by a task then it cannot be
given from an interrupt, and if a mutex is given by the
holding task then it must be the running state task. Remove
the holding task from the ready list. */
if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
{
taskRESET_READY_PRIORITY( pxTCB->uxPriority );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* Disinherit the priority before adding the task into the
new ready list. */
traceTASK_PRIORITY_DISINHERIT( pxTCB, pxTCB->uxBasePriority );
pxTCB->uxPriority = pxTCB->uxBasePriority;
/* Reset the event list item value. It cannot be in use for
any other purpose if this task is running, and it must be
running to give back the mutex. */
listSET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) pxTCB->uxPriority ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */
prvAddTaskToReadyList( pxTCB );
/* Return true to indicate that a context switch is required.
This is only actually required in the corner case whereby
multiple mutexes were held and the mutexes were given back
in an order different to that in which they were taken.
If a context switch did not occur when the first mutex was
returned, even if a task was waiting on it, then a context
switch should occur when the last mutex is returned whether
a task is waiting on it or not. */
xReturn = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
return xReturn;
}
#endif /* configUSE_MUTEXES */
任务A的uxMutexesHeld
减1,在创建A任务时,uxBasePriority
里面是A最开始的优先级,对比现在的优先级,发现变了,就会把A从优先级List(优先级:3)上移除,并初始化uxTopReadyPriority,又把A的优先级从3变回了1,把A又挂载到了优先级List(优先级:1)上,返回退出。此时就会去执行C任务了,C任务就会发现资源X没人占用,便继续执行下去,执行完了C程序就自然地进入阻塞状态,B任务就会得到执行。
有优先级反转处理的任务执行流程:
![](https://i-blog.csdnimg.cn/blog_migrate/699b596937bb86fab8db813afb11e92f.png)
A(被高优先级抢占)->C(无法访问资源X而修改A的优先级)->A(优先级被修改而执行,释放资源X,并改回原来的优先级,被高优先级抢占)->C(正常访问资源X,正常阻塞)->B(正常执行)
这两者对比,有优先级反转处理的高优先级任务等待的时间是减少了,并且也符合抢占优先级这个机制。
4.递归互斥量
4.1 创建递归互斥量
#define xSemaphoreCreateRecursiveMutex() xQueueCreateMutex( queueQUEUE_TYPE_RECURSIVE_MUTEX )
故名思意,就是在递归的时候使用的互斥量。
具体看创建函数:
QueueHandle_t xQueueCreateMutex( const uint8_t ucQueueType )
{
Queue_t *pxNewQueue;
const UBaseType_t uxMutexLength = ( UBaseType_t ) 1, uxMutexSize = ( UBaseType_t ) 0;
pxNewQueue = ( Queue_t * ) xQueueGenericCreate( uxMutexLength, uxMutexSize, ucQueueType );
prvInitialiseMutex( pxNewQueue );
return pxNewQueue;
}
它和互斥量调用的是同一个函数,但传进的参数是queueQUEUE_TYPE_RECURSIVE_MUTEX
。它和创建互斥量的初始化过程是一样的。
4.2 释放递归互斥量
#define xSemaphoreGiveRecursive( xMutex ) xQueueGiveMutexRecursive( ( xMutex ) )
具体看xQueueGiveMutexRecursive
BaseType_t xQueueGiveMutexRecursive( QueueHandle_t xMutex )
{
BaseType_t xReturn;
Queue_t * const pxMutex = ( Queue_t * ) xMutex;
configASSERT( pxMutex );
/* If this is the task that holds the mutex then pxMutexHolder will not
change outside of this task. If this task does not hold the mutex then
pxMutexHolder can never coincidentally equal the tasks handle, and as
this is the only condition we are interested in it does not matter if
pxMutexHolder is accessed simultaneously by another task. Therefore no
mutual exclusion is required to test the pxMutexHolder variable. */
if( pxMutex->pxMutexHolder == ( void * ) xTaskGetCurrentTaskHandle() ) /*lint !e961 Not a redundant cast as TaskHandle_t is a typedef. */
{
traceGIVE_MUTEX_RECURSIVE( pxMutex );
/* uxRecursiveCallCount cannot be zero if pxMutexHolder is equal to
the task handle, therefore no underflow check is required. Also,
uxRecursiveCallCount is only modified by the mutex holder, and as
there can only be one, no mutual exclusion is required to modify the
uxRecursiveCallCount member. */
( pxMutex->u.uxRecursiveCallCount )--;
/* Has the recursive call count unwound to 0? */
if( pxMutex->u.uxRecursiveCallCount == ( UBaseType_t ) 0 )
{
/* Return the mutex. This will automatically unblock any other
task that might be waiting to access the mutex. */
( void ) xQueueGenericSend( pxMutex, NULL, queueMUTEX_GIVE_BLOCK_TIME, queueSEND_TO_BACK );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
xReturn = pdPASS;
}
else
{
/* The mutex cannot be given because the calling task is not the
holder. */
xReturn = pdFAIL;
traceGIVE_MUTEX_RECURSIVE_FAILED( pxMutex );
}
return xReturn;
}
如果当前任务不是递归互斥量的所有者,那么就会退出。然后对uxRecursiveCallCount
减1。如果减到为0的时候,uxMessagesWaiting
加1。
4.3 获取递归互斥量
#define xSemaphoreTakeRecursive( xMutex, xBlockTime ) xQueueTakeMutexRecursive( ( xMutex ), ( xBlockTime ) )
具体调用xQueueTakeMutexRecursive
BaseType_t xQueueTakeMutexRecursive( QueueHandle_t xMutex, TickType_t xTicksToWait )
{
BaseType_t xReturn;
Queue_t * const pxMutex = ( Queue_t * ) xMutex;
configASSERT( pxMutex );
/* Comments regarding mutual exclusion as per those within
xQueueGiveMutexRecursive(). */
traceTAKE_MUTEX_RECURSIVE( pxMutex );
if( pxMutex->pxMutexHolder == ( void * ) xTaskGetCurrentTaskHandle() ) /*lint !e961 Cast is not redundant as TaskHandle_t is a typedef. */
{
( pxMutex->u.uxRecursiveCallCount )++;
xReturn = pdPASS;
}
else
{
xReturn = xQueueGenericReceive( pxMutex, NULL, xTicksToWait, pdFALSE );
/* pdPASS will only be returned if the mutex was successfully
obtained. The calling task may have entered the Blocked state
before reaching here. */
if( xReturn != pdFAIL )
{
( pxMutex->u.uxRecursiveCallCount )++;
}
else
{
traceTAKE_MUTEX_RECURSIVE_FAILED( pxMutex );
}
}
return xReturn;
}
先判断当前是不是任务获取了这个递归互斥量,如果是,uxRecursiveCallCount
加1,如果不是,那么可能是当前递归互斥量还不属于任何一个任务的,这种情况就会调用xQueueGenericReceive
,然后uxMessagesWaiting
会减1,uxRecursiveCallCount
加1。
总结:
也就是说当第一个获取递归互斥量时,uxMessagesWaiting
会减一,当最后一次释放递归互斥量时,uxMessagesWaiting
加1。真正记录递归次数的是uxRecursiveCallCount
,获取一次就会加1,释放一次就减1