互斥信号量其实就是一个拥有优先级继承的二值信号量,在同步的应用中(任务与任务或中断与任务之间的同步)二值信号量最适合。互斥信号量适合用于那些需要互斥访问的应用中。在互斥访问中互斥信号量相当于一个钥匙,当任务想要使用资源的时候就必须先获得这个钥匙,当使用完资源以后就必须归还这个钥匙,这样其他的任务就可以拿着这个钥匙去使用资源。互斥信号量使用和二值信号量相同的API操作函数,所以互斥信号量也可以设置阻塞时间,不同于二值信号量的是互斥信号量具有优先级继承的特性。当一个互斥信号量正在被一个低优先级的任务使用,而此时有个高优先级的任务也尝试获取这个互斥信号量的话就会被阻塞。不过这个高优先级的任务会将低优先级任务的优先级提升到与自己相同的优先级,这个过程就是优先级继承。优先级继承尽可能的降低了高优先级任务处于阻塞态的时间,并且将已经出现的"优先级翻转"的影响降到最低。优先级继承并不能完全的消除优先级翻转,它只是尽可能的降低优先级翻转带来的影响。硬实时应用应该在设计之初就要避免优先级翻转的发生。互斥信号量不能用于中断服务函数中,原因如下:
1、互斥信号量有优先级继承的机制,所以只能用在任务中,不能用于中断服务函数。
2、中断服务函数中不能因为要等待互斥信号量而设置阻塞时间进入阻塞态。
FreeRTOS提供了两个互斥信号量创建函数,如下表所示:
函数 | 描述 |
---|---|
xSemaphoreCreateMutex() | 使用动态方法创建互斥信号量 |
xSemaphoreCreateMutexStatic() | 使用静态方法创建互斥信号量 |
1、函数xSemaphoreCreateMutex()
此函数用于创建一个互斥信号量,所需要的内存通过动态内存管理方法分配。此函数本质是一个宏,真正完成信号量创建的是函数xQueueCreateMutex(),此函数原型如下:
SemaphoreHandle_t xSemaphoreCreateMutex(void)
参数 | 描述 |
---|---|
返回值 | NULL:互斥信号量创建失败。其他值:创建成功的互斥信号量的句柄。 |
2、函数xSemaphoreCreateMutexStatic()
此函数也是创建互斥信号量的,只不过使用此函数创建互斥信号量的话信号量所需要的,RAM需要由用户来分配,此函数是个宏,具体创建过程是通过函数xQueueCreateMutexStatic()来完成的,函数原型如下:
SemaphoreHandle_t xSemaphoreCreateMutexStatic(StaticSemaphore_t*pxMutexBuffer)
参数 | 描述 |
---|---|
pxMutexBuffer | 此参数指向一个StaticSemaphore_t类型的变量,用来保存信号量结构体。 |
返回值 | NULL:互斥信号量创建失败。其他值:创建成功的互斥信号量的句柄。 |
1、互斥信号量创建过程分析
动态创建互斥信号量函数xSemaphoreCreateMutex(),此函数是个宏,定义如下:
#define xSemaphoreCreateMutex() xQueueCreateMutex(queueQUEUE_TYPE_MUTEX)
可以看出,真正干事的是函数xQueueCreateMutex(),此函数在文件queue.c中有如下定义,
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 );(1)
prvInitialiseMutex( pxNewQueue );(2)
return pxNewQueue;
}
其中prvInitialiseMutex()代码如下:
static void prvInitialiseMutex( Queue_t *pxNewQueue )
{
if( pxNewQueue != NULL )
{
/*虽然创建队列的时候会初始化队列结构体的成员变量,但是现在是创建互斥信号量,有些成员需要重新赋值,特别是用于优先级继承的*/
pxNewQueue->pxMutexHolder = NULL;(1)
pxNewQueue->uxQueueType = queueQUEUE_IS_MUTEX;(2)
//如果是递归互斥信号量
pxNewQueue->u.uxRecursiveCallCount = 0;(3)
traceCREATE_MUTEX( pxNewQueue );
//释放互斥信号量
( void ) xQueueGenericSend( pxNewQueue, NULL, ( TickType_t ) 0U, queueSEND_TO_BACK );
}
else
{
traceCREATE_MUTEX_FAILED();
}
}
(1)和(2)、这里大家可能会疑惑,队列结构体Queue_t中没有pxMutexHolder和uxQueueType这两个成员变量?这两个东西是哪里来的妖孽?这两个其实是宏,专门为互斥信号量准备的,在文件queue.c中有如下定义:
#define pxMutexHolder pcTail
#define uxQueueType pcHead
#define queueQUEUE_IS_MUTEX NULL
当Queue_t用于表示队列的时候pcHead和pcTail指向队列的存储区域,当Queue_t用于表示互斥信号量的时候就不需要pcHead和pcTail了。当用于互斥信号量的时候将pcHead指向NULL来表示pcTail保存着互斥队列的所有者,pxMutexHolder指向拥有互斥信号量的那个任务的任务控制块。重命名pcTail和pcHead就是为了增强代码的可读性。
(3)、如果创建的互斥信号量是互斥信号量的话,还需要初始化队列结构体中的成员变量u.uxRecursiveCallCount。
互斥信号量创建成功以后会调用函xQueueGenericSend()释放一次信号量,说明互斥信号量默认就是有效的!互斥信号量初始化完成如下图所示:
2、释放互斥信号量过程分析
释放互斥信号量的时候和二值信号量、计数型信号量一样,都是用的函数xSemaphoreGive()(实际上完成信号量释放的是函数xQueueGenericSend()。)不过由于互斥信号量涉及到优先级继承的问题,所以具体处理过程会有点区别。使用函数xSemaphoreGive()释放信号量最重要的一步就是将uxMessagesWaiting加一,而这一步就是通过函数
prvCopyDataToQueue()来完成的,释放信号量的函数xQueueGenericSend()会调用prvCopyDataToQueue()。互斥信号量的优先级继承也是在函数prvCopyDataToQueue()中完成的,此函数中有如下一段代码:
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 )
{
//互斥信号量
#if ( configUSE_MUTEXES == 1 )
{
if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )(1)
{
xReturn = xTaskPriorityDisinherit( ( void * ) pxQueue->pxMutexHolder );(2)
pxQueue->pxMutexHolder = NULL;(3)
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_MUTEXES */
}
/**********省略其他代码************/
现在来看一下函数xTaskPriorityDisinherit()是怎么具体的处理优先级继承的,函数xTaskPriorityDisinherit0代码如下:
BaseType_t xTaskPriorityDisinherit( TaskHandle_t const pxMutexHolder )
{
TCB_t * const pxTCB = ( TCB_t * ) pxMutexHolder;
BaseType_t xReturn = pdFALSE;
if( pxMutexHolder != NULL )(1)
{
/* 当一个任务获取到互斥信号量就会涉及优先级继承的问题,正在释放互斥信号量的任务肯定是现在正在运行的任务pxCurrentTCB */
configASSERT( pxTCB == pxCurrentTCB );
configASSERT( pxTCB->uxMutexesHeld );
( pxTCB->uxMutexesHeld )--;(2)
/* 是否存在优先级继承?如果存在的话任务当前优先级肯定和任务基优先级不同*/
if( pxTCB->uxPriority != pxTCB->uxBasePriority )(3)
{
/* 当前任务只获取到一个互斥信号量 */
if( pxTCB->uxMutexesHeld == ( UBaseType_t ) 0 )(4)
{
if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )(5)
{
taskRESET_READY_PRIORITY( pxTCB->uxPriority );(6)
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* 使用新的优先级将任务重新添加到就绪列表中 */
traceTASK_PRIORITY_DISINHERIT( pxTCB, pxTCB->uxBasePriority );
pxTCB->uxPriority = pxTCB->uxBasePriority;(7)
listSET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) pxTCB->uxPriority );(8)
prvAddTaskToReadyList( pxTCB );(9)
xReturn = pdTRUE;(10)
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
return xReturn;
}
3、获取互斥信号量过程分析
获取互斥信号量的函数同获取二值信号量和计数型信号量的函数相同,都是xSemaphoreTake()(实际执行信号量获取的函数是xQueueGenericReceive()),获取互斥信号量的过程也需要处理优先级继承的问题,函数xQueueGenericReceive()在文件queue.c中有定义,在缩减后的函数代码如下:
BaseType_t xQueueGenericReceive( QueueHandle_t xQueue,
void * const pvBuffer, TickType_t xTicksToWait,
const BaseType_t xJustPeeking )
{
BaseType_t xEntryTimeSet = pdFALSE;
TimeOut_t xTimeOut;
int8_t *pcOriginalReadPosition;
Queue_t * const pxQueue = ( Queue_t * ) xQueue;
for( ;; )
{
taskENTER_CRITICAL();
{
const UBaseType_t uxMessagesWaiting = pxQueue->uxMessagesWaiting;
//判断队列是否有消息
if( uxMessagesWaiting > ( UBaseType_t ) 0 )(1)
{
pcOriginalReadPosition = pxQueue->u.pcReadFrom;
prvCopyDataFromQueue( pxQueue, pvBuffer );(2)
if( xJustPeeking == pdFALSE )(3)
{
traceQUEUE_RECEIVE( pxQueue );
//移除消息
pxQueue->uxMessagesWaiting = uxMessagesWaiting - 1;(4)
#if ( configUSE_MUTEXES == 1 )(5)
{
if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
{
pxQueue->pxMutexHolder = ( int8_t * ) pvTaskIncrementMutexHeldCount(); (6)
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_MUTEXES */
/*查看是否有任务因为入队而阻塞,如果有的话就需要解除阻塞态。*/
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE )(7)
{
if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE )
{
/*如果解除任务阻塞的任务优先级比当前任务优先级高的话就需要进行一次任务切换*/
queueYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else(8)
{
traceQUEUE_PEEK( pxQueue );
//读取队列中的消息以后需要删除消息
pxQueue->u.pcReadFrom = pcOriginalReadPosition;
//如果有任务因为出队而阻塞的话就解除任务的阻塞态
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )(9)
{
if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
{
//如果解除阻塞的任务优先级比当前任务优先级高的话就需要进行一次任务切换
queueYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
taskEXIT_CRITICAL();
return pdPASS;
}
else //队列为空 (10)
{
if( xTicksToWait == ( TickType_t ) 0 )
{
/*队列为空,如果阻塞时间为0的话就直接返回errQUEUE_EMPTY*/
taskEXIT_CRITICAL();
traceQUEUE_RECEIVE_FAILED( pxQueue );
return errQUEUE_EMPTY;
}
else if( xEntryTimeSet == pdFALSE )
{
//队列为空且设置了阻塞时间,需要初始化时间状态结构体。
vTaskSetTimeOutState( &xTimeOut );
xEntryTimeSet = pdTRUE;
}
else
{
/* Entry time was already set. */
mtCOVERAGE_TEST_MARKER();
}
}
}
taskEXIT_CRITICAL();
vTaskSuspendAll();
prvLockQueue( pxQueue );
//更新时间状态结构体,并且检查超时是否发送。
if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE )(11)
{
if( prvIsQueueEmpty( pxQueue ) != pdFALSE )(12)
{
traceBLOCKING_ON_QUEUE_RECEIVE( pxQueue );
#if ( configUSE_MUTEXES == 1 )
{
if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )(13)
{
taskENTER_CRITICAL();
{
vTaskPriorityInherit( ( void * ) pxQueue->pxMutexHolder );(14)
}
taskEXIT_CRITICAL();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif
vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToReceive ), xTicksToWait );(15)
prvUnlockQueue( pxQueue );
if( xTaskResumeAll() == pdFALSE )
{
portYIELD_WITHIN_API();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else//再试一次
{
prvUnlockQueue( pxQueue );
( void ) xTaskResumeAll();
}
}
else
{
prvUnlockQueue( pxQueue );
( void ) xTaskResumeAll();
if( prvIsQueueEmpty( pxQueue ) != pdFALSE )
{
traceQUEUE_RECEIVE_FAILED( pxQueue );
return errQUEUE_EMPTY;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
}