FreeRTOS系列 您所在的位置:网站首页 互斥信号量mutex为1 FreeRTOS系列

FreeRTOS系列

2024-07-10 13:14| 来源: 网络整理| 查看: 265

互斥信号量 1. 优先级翻转

优先级翻转是使用二值信号量时常遇见的问题,在可剥夺内核中非常常见,但是在实时系统中不允许出现这种现象,因为会破坏任务的预期顺序,可能会导致严重后果。

如下图所示的优先级翻转的例子:

低优先级任务L要访问共享资源,在获取到信号量使用CPU的过程中,如果此时高优先级任务H到达,会剥夺L的CPU使用权从而运行任务H当H想要访问共享资源时,由于此时该资源的信号量仍被L占用着,H只能挂起等待L释放该信号量L继续运行,此时中优先级任务M到达,再次剥夺L的CPU使用权从而运行任务MM执行完后,将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) //参数:pxSemaphoreBuffer 指向一个StaticSemaphore_t类型的变量,用来保存信号量结构体 /***********************************************************************************/ 返回值:创建成功返回互斥信号量句柄;失败返回NULL

动态互斥信号量创建函数是一个宏,最终是通过xQueueCreateMutex()函数来完成,其源码如下:

#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) #define xSemaphoreCreateMutex() xQueueCreateMutex(queueQUEUE_TYPE_MUTEX) #endif /**************xQueueCreateMutex源码分析*********************/ QueueHandle_t xQueueCreateMutex( const uint8_t ucQueueType ){ Queue_t *pxNewQueue; const UBaseType_t uxMutexLength = (UBaseType_t) 1,uxMutexSize = (UBaseType_t) 0; /* 创建一个队列长度为1,队列项长度为0,队列类型为queueQUEUE_TYPE_MUTEX的队列 */ pxNewQueue = (Queue_t *)xQueueGenericCreate(uxMutexLength,uxMutexSize,ucQueueType); /* 初始化互斥信号量,其实就是初始化消息队列的控制块 */ prvInitialiseMutex( pxNewQueue ); return pxNewQueue; } /**************prvInitialiseMutex源码分析*********************/ static void prvInitialiseMutex( Queue_t *pxNewQueue ){ if( pxNewQueue != NULL ){ /* 对创建好的队列结构体的某些互斥信号量特有的成员变量重新赋值 */ pxNewQueue->pxMutexHolder = NULL;//互斥信号量专有的宏 pxNewQueue->uxQueueType = queueQUEUE_IS_MUTEX;//互斥信号量专有的宏 /* 如果是递归互斥信号量,将以下成员变量赋值为0 */ 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()函数中完成的,其源码如下:

/**************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 ){ /* 调用以下函数处理优先级继承问题 */ xReturn = xTaskPriorityDisinherit((void *) pxQueue->pxMutexHolder); pxQueue->pxMutexHolder = NULL;//互斥信号量释放后,就不属于任何任务了 } else{ mtCOVERAGE_TEST_MARKER(); } } #endif /* configUSE_MUTEXES */ } /*********************************************************/ /********************省略其他处理代码*********************/ /*********************************************************/ pxQueue->uxMessagesWaiting = uxMessagesWaiting + ( UBaseType_t ) 1; return xReturn }

优先级处理函数xTaskPriorityDisinherit()的源码分析如下:

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;//返回pdTRUE,表示需要进行任务调度 } else{ mtCOVERAGE_TEST_MARKER(); } } else{ mtCOVERAGE_TEST_MARKER(); } } else{ mtCOVERAGE_TEST_MARKER(); } return xReturn; } 3.3 获取互斥信号量

互斥信号量获取函数与二值信号量、计数信号量获取函数是一样的,都是xSemaphoreTake()函数,最终调用xQueueGenericReceive();获取互斥信号量的过程也需要处理优先级继承的问题,源码分析如下示

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){ /* 如有数据,调用以下函数使用数据拷贝的方式从队列中提取数据 */ 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 /* configUSE_MUTEXES */ /* 查看是否有任务因为入队而阻塞,若有就解除阻塞态 */ 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 ){ /* 如阻塞时间为0,则直接返回errQUEUE_EMPTY */ taskEXIT_CRITICAL(); traceQUEUE_RECEIVE_FAILED( pxQueue ); return errQUEUE_EMPTY; } else if( xEntryTimeSet == pdFALSE ){ /* 若阻塞时间不为0,则初始化时间状态结构体 */ vTaskSetTimeOutState( &xTimeOut ); xEntryTimeSet = pdTRUE; } else{ /* Entry time was already set. */ 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 /* 由于队列为空,将任务添加到xTasksWaitingToReceive列表中 */ 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设置 RCC设置外接HSE,时钟设置为72MUSART1选择为异步通讯方式,波特率设置为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任务函数代码 /******************HighTask**************************/ 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); } } /******************MiddleTask***********************/ void MiddleTask(void const * argument){ for(;;){ printf("Middle task running!\r\n"); HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_0); vTaskDelay(1000); } } /******************LowTask**************************/ void LowTask(void const * argument){ for(;;){ xSemaphoreTake(MutexSemHandle,portMAX_DELAY); printf("Low task running!\r\n"); for(int i=0;i


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有