互斥信号量
1. 优先级翻转
优先级翻转是使用二值信号量时常遇见的问题,在可剥夺内核中非常常见,但是在实时系统中不允许出现这种现象,因为会破坏任务的预期顺序,可能会导致严重后果。
如下图所示的优先级翻转的例子:
- 低优先级任务L要访问共享资源,在获取到信号量使用CPU的过程中,如果此时高优先级任务H到达,会剥夺L的CPU使用权从而运行任务H
- 当H想要访问共享资源时,由于此时该资源的信号量仍被L占用着,H只能挂起等待L释放该信号量
- L继续运行,此时中优先级任务M到达,再次剥夺L的CPU使用权从而运行任务M
- M执行完后,将CPU使用权归还给任务L,L继续运行
- L运行完毕并释放出了信号量,至此高优先级的任务H才能获取到该信号量访问共享资源并运行
由上图可见,任务H的优先级实际上降到了任务L的优先级水平,因为要一直等待L释放其占用的共享资源。过程中不需要使用共享资源的中优先级任务M抢占CPU使用权后顺利运行完成,这样就相当于M的优先级高于J,导致优先级翻转
2. 互斥信号量
互斥信号量其实就是一个拥有优先级继承的二值信号量。二值信号适合于同步应用中,互斥信号适用于需要互斥访问的应用中。互斥访问中互斥信号量相当于一个钥匙,当任务想要使用资源的时候就必须先获得这个钥匙,使用完资源后必须归还这个钥匙
当一个互斥信号量正被一个低优先级任务使用时,若有高优先级的任务也尝试获取该互斥信号量的话就会被阻塞。不过此时高优先级任务会将低优先级任务的优先级提升到与与自已相同的等级(即优先级继承)。优先级继承只是尽可能降低优先级翻转带来的影响,并不能完全消除优先级翻转
3. 互斥信号量的API函数
3.1 创建互斥信号量
SemaphoreHandle_t xSemaphoreCreateMutex(void)
SemaphoreHandle_t xSemaphoreCreateMutexStatic(StaticSemaphore_t * pxSemaphoreBuffer)
返回值:创建成功返回互斥信号量句柄;失败返回NULL
|
动态互斥信号量创建函数是一个宏,最终是通过xQueueCreateMutex()函数来完成,其源码如下:
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
#define xSemaphoreCreateMutex() xQueueCreateMutex(queueQUEUE_TYPE_MUTEX)
#endif
|
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;
}
|
static void prvInitialiseMutex( Queue_t *pxNewQueue ){
if( pxNewQueue != NULL ){
pxNewQueue->pxMutexHolder = NULL;
pxNewQueue->uxQueueType = queueQUEUE_IS_MUTEX;
pxNewQueue->u.uxRecursiveCallCount = 0;
traceCREATE_MUTEX( pxNewQueue );
(void) xQueueGenericSend(pxNewQueue,NULL,(TickType_t) 0U,queueSEND_TO_BACK);
}
else{
traceCREATE_MUTEX_FAILED();
}
}
|
3.2 释放互斥信号量
释放互斥信号量函数与二值信号量、计数信号量释放函数是一样的。但是由于互斥信号涉及到优先级继承的问题,所以处理过程会有一些区别。
信号量释放函数xSemaphoreGive()实际上调用xQueueGenericSend()函数,分析该函数源码(消息队列中有介绍)可见函数会调用prvCopyDataToQueue()函数。互斥信号量的优先级继承就是在prvCopyDataToQueue()函数中完成的,其源码如下:
BaseType_t xTaskPriorityDisinherit(TaskHandle_t const pxMutexHolder){
TCB_t * const pxTCB = ( TCB_t * ) 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 );
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();
}
return xReturn;
}
|
优先级处理函数xTaskPriorityDisinherit()的源码分析如下:
BaseType_t xQueueGenericReceive(QueueHandle_t xQueue,
id * const pvBuffer,
kType_t xTicksToWait,
nst 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){
pcOriginalReadPosition = pxQueue->u.pcReadFrom;
prvCopyDataFromQueue( pxQueue, pvBuffer );
if( xJustPeeking == pdFALSE ){
traceQUEUE_RECEIVE( pxQueue );
pxQueue->uxMessagesWaiting = uxMessagesWaiting - 1;
#if ( configUSE_MUTEXES == 1 )
{
if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX ){
pxQueue->pxMutexHolder = (int8_t *) pvTaskIncrementMutexHeldCount();
}
else{
mtCOVERAGE_TEST_MARKER();
}
}
#endif
if(listLIST_IS_EMPTY(&( pxQueue->xTasksWaitingToSend)) == pdFALSE){
if(xTaskRemoveFromEventList(&(pxQueue->xTasksWaitingToSend)) != pdFALSE){
queueYIELD_IF_USING_PREEMPTION();
}
else{
mtCOVERAGE_TEST_MARKER();
}
}
else{
mtCOVERAGE_TEST_MARKER();
}
}
else{
traceQUEUE_PEEK( pxQueue );
pxQueue->u.pcReadFrom = pcOriginalReadPosition;
if(listLIST_IS_EMPTY(&(pxQueue->xTasksWaitingToReceive)) == pdFALSE){
if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE ){
queueYIELD_IF_USING_PREEMPTION();
}
else{
mtCOVERAGE_TEST_MARKER();
}
}
else{
mtCOVERAGE_TEST_MARKER();
}
}
taskEXIT_CRITICAL();
return pdPASS;
}
else{
if( xTicksToWait == ( TickType_t ) 0 ){
taskEXIT_CRITICAL();
traceQUEUE_RECEIVE_FAILED( pxQueue );
return errQUEUE_EMPTY;
}
else if( xEntryTimeSet == pdFALSE ){
vTaskSetTimeOutState( &xTimeOut );
xEntryTimeSet = pdTRUE;
}
else{
mtCOVERAGE_TEST_MARKER();
}
}
}
taskEXIT_CRITICAL();
vTaskSuspendAll();
prvLockQueue( pxQueue );
if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE ){
if( prvIsQueueEmpty( pxQueue ) != pdFALSE){
traceBLOCKING_ON_QUEUE_RECEIVE( pxQueue );
#if ( configUSE_MUTEXES == 1 )
{
if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX ){
taskENTER_CRITICAL();
{
vTaskPriorityInherit( ( void * ) pxQueue->pxMutexHolder );
}
taskEXIT_CRITICAL();
}
else{
mtCOVERAGE_TEST_MARKER();
}
}
#endif
vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToReceive ), xTicksToWait );
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();
}
}
}
}
|
3.3 获取互斥信号量
互斥信号量获取函数与二值信号量、计数信号量获取函数是一样的,都是xSemaphoreTake()函数,最终调用xQueueGenericReceive();获取互斥信号量的过程也需要处理优先级继承的问题,源码分析如下示
BaseType_t xQueueGenericReceive(QueueHandle_t xQueue,
id * const pvBuffer,
ckType_t xTicksToWait,
onst 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){
pcOriginalReadPosition = pxQueue->u.pcReadFrom;
prvCopyDataFromQueue( pxQueue, pvBuffer );
if( xJustPeeking == pdFALSE ){
traceQUEUE_RECEIVE( pxQueue );
pxQueue->uxMessagesWaiting = uxMessagesWaiting - 1;
#if ( configUSE_MUTEXES == 1 )
{
if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX ){
pxQueue->pxMutexHolder = (int8_t *) pvTaskIncrementMutexHeldCount();
}
else{
mtCOVERAGE_TEST_MARKER();
}
}
#endif
if(listLIST_IS_EMPTY(&( pxQueue->xTasksWaitingToSend)) == pdFALSE){
if(xTaskRemoveFromEventList(&(pxQueue->xTasksWaitingToSend)) != pdFALSE){
queueYIELD_IF_USING_PREEMPTION();
}
else{
mtCOVERAGE_TEST_MARKER();
}
}
else{
mtCOVERAGE_TEST_MARKER();
}
}
else{
traceQUEUE_PEEK( pxQueue );
pxQueue->u.pcReadFrom = pcOriginalReadPosition;
if(listLIST_IS_EMPTY(&(pxQueue->xTasksWaitingToReceive)) == pdFALSE){
if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE ){
queueYIELD_IF_USING_PREEMPTION();
}
else{
mtCOVERAGE_TEST_MARKER();
}
}
else{
mtCOVERAGE_TEST_MARKER();
}
}
taskEXIT_CRITICAL();
return pdPASS;
}
else{
if( xTicksToWait == ( TickType_t ) 0 ){
taskEXIT_CRITICAL();
traceQUEUE_RECEIVE_FAILED( pxQueue );
return errQUEUE_EMPTY;
}
else if( xEntryTimeSet == pdFALSE ){
vTaskSetTimeOutState( &xTimeOut );
xEntryTimeSet = pdTRUE;
}
else{
mtCOVERAGE_TEST_MARKER();
}
}
}
taskEXIT_CRITICAL();
vTaskSuspendAll();
prvLockQueue( pxQueue );
if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE ){
if( prvIsQueueEmpty( pxQueue ) != pdFALSE){
traceBLOCKING_ON_QUEUE_RECEIVE( pxQueue );
#if ( configUSE_MUTEXES == 1 )
{
if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX ){
taskENTER_CRITICAL();
{
vTaskPriorityInherit( ( void * ) pxQueue->pxMutexHolder );
}
taskEXIT_CRITICAL();
}
else{
mtCOVERAGE_TEST_MARKER();
}
}
#endif
vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToReceive ), xTicksToWait );
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();
}
}
}
}
|
4. 互斥信号量的应用实例 本实例介绍并模拟如何使用互斥信号量来避免章节1中的优先级翻转现象
使用STM32CubeMX将 FreeRTOS 移植到工程中,创建优先级为高中低的三个任务、一个互斥信号量
- High_Task:高优先级任务,会获取互斥信号量,获取成功后进行相应的处理,处理完后释放互斥信号量
- Middle_Task:中优先级任务,简单的应用任务
- Low_Task:低优先级任务,会获取互斥信号量,获取成功后进行相应的处理,处理完后释放互斥信号量。但是任务占用互斥信号量的时间比高优先级任务占用的时间要长
4.1 STM32CubeMX设置
- USART1选择为异步通讯方式,波特率设置为115200Bits/s,传输数据长度为8Bit,无奇偶校验,1位停止位;
- 激活FreeRTOS,添加任务,设置任务名称、优先级、堆栈大小、函数名称等参数
- 使用FreeRTOS操作系统,一定要将HAL库的Timebase Source从SysTick改为其他定时器,选好定时器后,系统会自动配置TIM
- 输入工程名,选择路径(不要有中文),选择MDK-ARM V5;勾选Generated periphera initialization as a pair of ‘.c/.h’ files per IP ;点击GENERATE CODE,生成工程代码
4.2 MDK-ARM软件编程
- 添加High_Task、Middle_Task、Low_Task任务函数代码
void HighTask(void const * argument){
for(;;){
vTaskDelay(500);
printf("High task Pend Semaphore\r\n");
xSemaphoreTake(MutexSemHandle,portMAX_DELAY);
printf("High task running!\r\n");
HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_1);
xSemaphoreGive(MutexSemHandle);
vTaskDelay(500);
}
}
void MiddleTask(void const * argument){
for(;;){
printf("Middle task running!\r\n");
HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_0);
vTaskDelay(1000);
}
}
void LowTask(void const * argument){
for(;;){
xSemaphoreTake(MutexSemHandle,portMAX_DELAY);
printf("Low task running!\r\n");
for(int i=0;i<20000000;i++){
taskYIELD();
}
xSemaphoreGive(MutexSemHandle);
vTaskDelay(1000);
}
}
|
4.3 下载验证
编译无误下载到开发板后,打开串口调试助手,串口输出如下图示的调试信息
- 由于High_Task任务延时500ms,因此Middle_Task最先开始运行;
- High_Task开始运行,阻塞在请求互斥信号量这里,等待Low_Task释放互斥信号量
- 此时由于Low_Task正在使用互斥信号量,因此Low_Task的优先级暂时提升到了与High_Task相同的优先级,所以Middle_Task无法打断Low_Task的运行。Low_Task运行完后,释放互斥信号量,High_Task获取互斥信号量并运行
由此可见,互斥信号量有效的解决了优先级翻转的问题
|