FreeRTOS个人笔记-互斥量

article/2025/9/17 16:48:38

根据个人的学习方向,学习FreeRTOS。由于野火小哥把FreeRTOS讲得比较含蓄,打算在本专栏尽量细化一点。作为个人笔记,仅供参考或查阅。

配套资料:FreeRTOS内核实现与应用开发实战指南、野火FreeRTOS配套视频源码、b站野火FreeRTOS视频。搭配来看更佳哟!!!

互斥量

互斥量,是一种特殊的二值信号量,它和信号量不同的是,
它支持互斥量所有权、递归访问以及防止优先级翻转的特性,用于实现对临界资源的独占式处理。任意时刻互斥量的状态只有两种,开锁或闭锁。
当互斥量被任务持有时,该互斥量处于闭锁状态,这个任务获得互斥量的所有权。
当该任务释放这个互斥量时,该互斥量处于开锁状态, 任务失去该互斥量的所有权。
当一个任务持有互斥量时,其他任务将不能再对该互斥量进行开锁或持有。持有该互斥量的任务也能够再次获得这个锁而不被挂起,这就是递归访问,也就是递归互斥量的特性,
这个特性与一般的信号量有很大的不同,在信号量中,由于已经不存在可用的信号量, 任务递归获取信号量时会发生主动挂起任务最终形成死锁。

二值信号量和互斥信号量非常相似,都可以用于临界资源访问也可以用于同步功能。但是有一些细微差别:
互斥信号量有优先级继承机制, 二值信号量则没有这个机制。
这使得二值信号量更偏向应用于同步功能(任务与任务间的同步或任务和中断间同步), 而互斥信号量更偏向应用于临界资源的访问。

用于互锁的互斥量可以充当保护资源的令牌, 当一个任务希望访问某个资源时,它必须先获取令牌。当任务使用完资源后,必须还回令牌,以便其它任务可以访问该资源。
当任务获取到信号量的时候才能开始使用被保护的资源,使用完就释放信号量,下一个任务才能获取到信号量从而可用使用被保护的资源。
但是信号量会导致的另一个潜在问题,那就是任务优先级翻转 。 而 FreeRTOS 提供的互斥量可以通过优先级继承算法, 可以降低优先级翻转问题产生的影响,
所以,用于临界资源的保护一般建议使用互斥量。
优先级继承算法是指,假设某个低优先级的任务占有某种资源,可暂时提高该任务优先级,使该任务与在所有等待该资源的任务中优先级最高那个任务的优先级相等,
而当这个低优先级任务执行完毕释放该资源时,优先级重新回到初始设定值。因此,继承优先级的任务避免了系统资源被任何中间优先级的任务抢占。
这个优先级继承机制确保高优先级任务进入阻塞状态的时间尽可能短,以及将已经出现的“优先级翻转”危害降低到最小。

在很多场合中,某些资源只有一个,当低优先级任务正在占用该资源的时候,即便高优先级任务也只能乖乖的等待低优先级任务使用完该资源后释放资源。
这里高优先级任务无法运行而低优先级任务可以运行的现象称为“优先级翻转”。

对于第一点,L 任务正在使用某临界资源, H 任务被唤醒,执行 H 任务。但 L 任务并未执行完毕,此时临界资源还未释放。 

对于第二点,这个时刻 H 任务也要对该临界资源进行访问,但 L 任务还未释放资源,由于保护机制,H 任务进入阻塞态,L 任务得以继续运行,此时已经发生了优先级翻转现象。

对于第三点,某个时刻 M 任务被唤醒,由于 M 任务的优先级高于 L 任务, M 任务抢占了 CPU 的使用权,M任务开始运行,此时 L 任务尚未执行完,临界资源还没被释放。

对于第四点,M 任务运行结束,归还 CPU 使用权,L 任务继续运行。

对于第五点,L任务运行结束,释放临界资源,H 任务得以对资源进行访问,H 任务开始运行。

对于第一点,L 任务正在使用某临界资源,L 任务正在使用某临界资源, H 任务被唤醒,执行 H 任务。但 L 任务并未执行完毕,此时临界资源还未释放。

对于第二点,某一时刻 H 任务也要对该资源进行访问,由于保护机制,H 任务进入阻塞态。此时发生优先级继承,系统将 L 任务的优先级暂时提升到与 H 任务优先级相同,L任务继续执行。

对于第三点,在某一时刻 M 任务被唤醒,由于此时 M 任务的优先级暂时低于 L 任务,所以 M 任务仅在就绪态,而无法获得 CPU 使用权。

对于第四点,L任务运行完毕,H 任务获得对资源的访问权,H 任务从阻塞态变成运行态,此时 L 任务的优先级会变回原来的优先级。

对于第五点,当 H 任务运行完毕,M任务得到 CPU 使用权,开始执行。

对于第六点,系统正常运行,按照设定好的优先级运行。

互斥量的使用比较单一,因为它是信号量的一种,并且它是以锁的形式存在。在初始化的时候,互斥量处于开锁的状态,而被任务持有的时候则立刻转为闭锁的状态。
互斥量更适合于:可能会引起优先级翻转的情况。
递归互斥量更适用于:任务可能会多次获取互斥量的情况下。这样可以避免同一任务多次递归持有而造成死锁的问题。

互斥量不能在中断服务函数中使用,因为其特有的优先级继承机制只在任务起作用,在中断的上下文环境毫无意义。

互斥量结构体

如果使用信号量或者互斥量,需要包含 semphr.h 头文件。 
FreeRTOS 的互斥量控制块结构体与消息队列结构体是一模一样的, 只不过结构体中某些成员变量代表的含义不一样。

typedef struct QueueDefinition
{int8_t *pcHead;					//队列头指针int8_t *pcTail;					//队列尾指针int8_t *pcWriteTo;				//指向队列消息存储区下一个可用消息空间//pcReadFrom 与 uxRecursiveCallCount 是一对互斥变量, 使用联合体用来确保两个互斥的结构体成员不会同时出现。 //当结构体用于队列时,pcReadFrom 指向出队消息空间的最后一个,即读取消息时候是从 pcReadFrom 指向的空间读取消息内容//当结构体用于互斥量时,uxRecursiveCallCount 用于计数,记录递归互斥量被“调用” 的次数。union							{int8_t *pcReadFrom;			UBaseType_t uxRecursiveCallCount;}u;//发送消息阻塞列表,用于保存阻塞在此队列的任务,任务按照优先级进行排序,由于队列已满,想要发送消息的任务无法发送消息。//获取消息阻塞列表,用于保存阻塞在此队列的任务,任务按照优先级进行排序,由于队列是空的,想要获取消息的任务无法获取到消息。List_t xTasksWaitingToSend;		List_t xTasksWaitingToReceive;	//uxMessagesWaiting用于消息队列,记录当前消息队列的消息个数//uxMessagesWaiting用于互斥量,表示有效信号量的个数。1:有效,0:无效//uxLength用于消息队列,表示队列长度,即能存放多少消息//uxLength用于互斥量,表示最大的信号量可用个数,最大为1。因为信号量要么是有效的,要么是无效的。//uxItemSize用于消息队列,表示单个消息的大小//uxItemSize用于互斥量,则无需存储空间,为 0 即可。volatile UBaseType_t uxMessagesWaiting;UBaseType_t uxLength;			UBaseType_t uxItemSize;			//这两个成员变量为 queueUNLOCKED 时,表示队列未上锁;当这两个成员变量为 queueLOCKED_UNMODIFIED 时,表示队列上锁。//队列上锁后,储存从队列收到的列表项数目,也就是出队的数量,如果队列没有上锁,设置为 queueUNLOCKED//队列上锁后,储存发送到队列的列表项数目,也就是入队的数量,如果队列没有上锁,设置为 queueUNLOCKED。volatile int8_t cRxLock;		volatile int8_t cTxLock;		#if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )uint8_t ucStaticallyAllocated;	#endif#if ( configUSE_QUEUE_SETS == 1 )struct QueueDefinition *pxQueueSetContainer;#endif#if ( configUSE_TRACE_FACILITY == 1 )UBaseType_t uxQueueNumber;uint8_t ucQueueType;#endif} xQUEUE;
typedef xQUEUE Queue_t;

创建互斥量

xSemaphoreCreateMutex()用于创建一个互斥量,并返回一个互斥量句柄。该句柄的原型是一个 void 型的指针,在使用之前必须先由用户定义一个互斥量句柄。

#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )#define xSemaphoreCreateMutex() xQueueCreateMutex( queueQUEUE_TYPE_MUTEX )
#endif

不是递归的互斥量由函数 xSemaphoreCreateMutex() 或 xSemaphoreCreateMutexStatic()创建,且只能被同一个任务获取一次, 如果同一个任务想再次获取则会失败。


xSemaphoreCreateRecursiveMutex()用于创建一个递归互斥量。
它可以被同一个任务获取很多次,获取多少次就需要释放多少次。递归信号量与互斥量一样,都实现了优先级继承机制,可以降低优先级反转的危害。
如果创建成功则返回一个递归互斥量句柄,用于访问创建的递归互斥量。 如果创建不成功则返回 NULL。

#if( ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) && ( configUSE_RECURSIVE_MUTEXES == 1 ) )#define xSemaphoreCreateRecursiveMutex() 	xQueueCreateMutex( queueQUEUE_TYPE_RECURSIVE_MUTEX )
#endif /* configSUPPORT_DYNAMIC_ALLOCATION */

不管是 xSemaphoreCreateMutex() 或 xSemaphoreCreateMutexStatic(),还是xSemaphoreCreateRecursiveMutex(),都是在xQueueCreateMutex()函数下做宏扩展。

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, uxMutexSize, ucQueueType );
xQueueGenericCreate()为消息队列创建函数,uxMutexLength为队列长度,uxMutexSize为单个消息大小,ucQueueType为队列类型。

xQueueGenericCreate()用到了prvInitialiseMutex( ),prvInitialiseMutex( )如下

#define pxMutexHolder					pcTail
#define uxQueueType						pcHead
#define queueQUEUE_IS_MUTEX				NULL#if( configUSE_MUTEXES == 1 )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();}}#endif /* configUSE_MUTEXES */

xSemaphoreCreateMutex()函数实例

SemaphoreHandle_t MuxSem_Handle;void vATask( void * pvParameters )
{/* 创建一个互斥量 */MuxSem_Handle= xSemaphoreCreateMutex();if (MuxSem_Handle!= NULL ) {/* 互斥量创建成功 */}
}

xSemaphoreCreateRecursiveMutex()函数实例

SemaphoreHandle_t xMutex;void vATask( void * pvParameters )
{/* 创建一个递归互斥量 */xMutex = xSemaphoreCreateRecursiveMutex();if ( xMutex != NULL ) {/* 递归互斥量创建成功 */}}


删除互斥量

vSemaphoreDelete()用于删除一个信号量,包括二值信号量,计数信号量,互斥量和递归互斥量。 如果有任务阻塞在该信号量上,那么不要删除该信号量。 
删除信号量过程其实就是删除消息队列过程, 因为信号量其实就是消息队列, 只不过是无法存储消息的队列而已。

#define vSemaphoreDelete( xSemaphore ) vQueueDelete( ( QueueHandle_t ) ( xSemaphore ) ) 

获取互斥量 

当互斥量处于开锁的状态,任务才能获取互斥量成功,当任务持有了某个互斥量的时候,其它任务就无法获取这个互斥量,需要等到持有互斥量的任务进行释放后,其他任务才能获取成功,任务通过互斥量获取函数来获取互斥量的所有权。 
任务对互斥量的所有权是独占的,任意时刻互斥量只能被一个任务持有,如果互斥量处于开锁状态,那么获取该互斥量的任务将成功获得该互斥量,并拥有互斥量的使用权;
如果互斥量处于闭锁状态,获取该互斥量的任务将无法获得互斥量,任务将被挂起,在任务被挂起之前,会进行优先级继承,如果当前任务优先级比持有互斥量的任务优先级高,那么将会临时提升持有互斥量任务的优先级。

#define xSemaphoreTake( xSemaphore, xBlockTime )		xQueueGenericReceive( ( QueueHandle_t ) ( xSemaphore ), NULL , (xBlockTime ) , pdFALSE ) 

如果获取的对象是互斥量,那么这个函数就拥有优先级继承算法,如果获取对象不是互斥量,就没有优先级继承机制。

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;/* 拷贝消息到用户指定存放区域 pvBuffer */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();}}taskEXIT_CRITICAL();return pdPASS;}	/* 消息队列中没有消息可读 */		else {if ( xTicksToWait == ( TickType_t ) 0 ) {/* 不等待,直接返回 */taskEXIT_CRITICAL();traceQUEUE_RECEIVE_FAILED( pxQueue );return errQUEUE_EMPTY;} else if ( xEntryTimeSet == pdFALSE ) {/* 初始化阻塞超时结构体变量,初始化进入阻塞的时间 xTickCount 和溢出次数 xNumOfOverflows */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/* 将当前任务添加到队列的等待接收列表中以及阻塞延时列表,阻塞时间为用户指定的超时时间 xTicksToWait */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 ) {/* 如果队列还是空的,返回错误代码 errQUEUE_EMPTY */traceQUEUE_RECEIVE_FAILED( pxQueue );return errQUEUE_EMPTY;} else {mtCOVERAGE_TEST_MARKER();}}}}

vTaskPriorityInherit()函数进行优先级继承,vTaskPriorityInherit()如下

#if ( configUSE_MUTEXES == 1 )void vTaskPriorityInherit( TaskHandle_t const pxMutexHolder )
{TCB_t * const pxTCB = ( TCB_t * ) pxMutexHolder; if ( pxMutexHolder != NULL ) {/* 判断当前任务与持有互斥量任务的优先级 */if ( pxTCB->uxPriority < pxCurrentTCB->uxPriority ) 	//进行优先级继承{//持有互斥量的任务在等待事件列表中,就调整互斥锁持有者等待的事件列表项的优先级,因为待会会暂时修改持有互斥量任务的优先级。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 ); } else {mtCOVERAGE_TEST_MARKER();}/* 如果被提升优先级的任务处于就绪列表中,如果修改了任务的优先级,那么在就绪列表中的任务也要重新排序*/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();/* 暂时提升持有互斥量任务的优先级,提升到与当前任务优先级一致*/pxTCB->uxPriority = pxCurrentTCB->uxPriority; /* 再插入就绪列表中 */prvAddTaskToReadyList( pxTCB ); } else {/* 如果任务不是在就绪列表中,就仅仅是提升任务优先级即可 */pxTCB->uxPriority = pxCurrentTCB->uxPriority; }traceTASK_PRIORITY_INHERIT( pxTCB, pxCurrentTCB->uxPriority );} else {mtCOVERAGE_TEST_MARKER();}} else {mtCOVERAGE_TEST_MARKER();}}
}
#endif /* configUSE_MUTEXES */	

xSemaphoreTake()函数实例 

static void HighPriority_Task(void* parameter)
{BaseType_t xReturn = pdTRUE;	/* 定义一个创建信息返回值,默认为 pdTRUE */while (1) {printf("HighPriority_Task 获取信号量\n"); //获取互斥量 MuxSem,没获取到则一直等待xReturn = xSemaphoreTake(MuxSem_Handle,portMAX_DELAY); if (pdTRUE == xReturn)printf("HighPriority_Task Runing\n");LED1_TOGGLE;//处理临界资源printf("HighPriority_Task 释放信号量!\r\n");xSemaphoreGive( MuxSem_Handle );//释放互斥量vTaskDelay(1000);}}

xSemaphoreTakeRecursive()用于获取递归互斥量。互斥量之前必须由xSemaphoreCreateRecursiveMutex()这个函数创建。

要注意的是该函数不能用于获取由函数xSemaphoreCreateMutex()创建的互斥量。

#if( configUSE_RECURSIVE_MUTEXES == 1 )#define xSemaphoreTakeRecursive( xMutex, xBlockTime )	xQueueTakeMutexRecursive( ( xMutex ), ( xBlockTime ) )
#endif 

xBlockTime,如果不是持有互斥量的任务去获取无效的互斥量,那么任务将进行等待用户指定超时时间,单位为 tick(即系统节拍周期)。 
获取成功则返回 pdTRUE, 在超时之前没有获取成功则返回 errQUEUE_EMPTY。

#if ( configUSE_RECURSIVE_MUTEXES == 1 )
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->pxMutexHolder == ( void * ) xTaskGetCurrentTaskHandle())		/* 如果持有互斥量的任务就是当前任务 */{/* u.uxRecursiveCallCount 自加,表示调用了多少次递归互斥量获取 */( pxMutex->u.uxRecursiveCallCount )++;xReturn = pdPASS;	} else 	/* 如果持有递归互斥量的任务不是当前任务,就只能等待递归互斥量被释放 */{	xReturn = xQueueGenericReceive( pxMutex, NULL, xTicksToWait, pdFALSE ); if ( xReturn != pdFAIL ) {/* 获取递归互斥量成功,记录递归互斥量的获取次数 */( pxMutex->u.uxRecursiveCallCount )++; } else {traceTAKE_MUTEX_RECURSIVE_FAILED( pxMutex );}}return xReturn;
}
#endif 

释放互斥量

任务想要访问某个资源的时候,需要先获取互斥量,然后进行资源访问,在任务使用完该资源的时候,必须要及时归还互斥量,这样别的任务才能对资源进行访问。
xSemaphoreGive()用于释放互斥量。互斥量的释放只能在任务中, 不允许在中断中释放互斥量。

#define xSemaphoreGive( xSemaphore )		xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ), NULL, semGIVE_BLOCK_TIME, queueSEND_TO_BACK )

xQueueGenericSend()使用prvCopyDataToQueue()恢复任务的初始优先级
prvCopyDataToQueue()使用xTaskPriorityDisinherit()恢复任务的初始优先级

xSemaphoreGiveRecursive()用于释放递归互斥量。已经获取递归互斥量的任务可以重复获取该递归互斥量。

#if( configUSE_RECURSIVE_MUTEXES == 1 )#define xSemaphoreGiveRecursive( xMutex )	xQueueGiveMutexRecursive( ( xMutex ) )
#endif 

使用 xSemaphoreTakeRecursive() 函数成功获取几次递归互斥量,就要使用 xSemaphoreGiveRecursive()函数返还几次,在此之前递归互斥量都处于无效状态,别的任务就无法获取该递归互斥量。
只有已持有互斥量所有权的任务才能释放递归互斥量,每释放一次该递归互斥量,它的计数值就减 1。当该互斥量的计数值为 0 时(即持有任务已经释放所有的持有操作),互斥量则变为开锁状态,等待在该互斥量上的任务将被唤醒。 
如果任务的优先级被互斥量的优先级翻转机制临时提升,那么当互斥量被释放后, 任务的优先级将恢复为原本设定的优先级。

#if ( configUSE_RECURSIVE_MUTEXES == 1 )BaseType_t xQueueGiveMutexRecursive( QueueHandle_t xMutex )
{BaseType_t xReturn;Queue_t * const pxMutex = ( Queue_t * ) xMutex;configASSERT( pxMutex ); /* 判断任务是否持有这个递归互斥量,只有拥有这个递归互斥量所有权的任务才能对其进行释放操作 */if ( pxMutex->pxMutexHolder == (void *)xTaskGetCurrentTaskHandle() ){ traceGIVE_MUTEX_RECURSIVE( pxMutex );/* 调用次数的计数值减一 */( pxMutex->u.uxRecursiveCallCount )--; /* 如果计数值减到 0 ,表明这个递归互斥量已经可以变得有效了*/if ( pxMutex->u.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 /* configUSE_RECURSIVE_MUTEXES */

互斥量和递归互斥量的最大区别在于一个递归互斥量可以被已经获取这个递归互斥量的任务重复获取,而不会形成死锁。 
这个递归调用功能是通过队列结构体成员 u.uxRecursiveCallCount 实现的,这个变量用于存储递归调用的次数,每次获取递归互斥量后,这个变量加 1,在释放递归互斥量后,这个变量减 1。
只有这个变量减到 0,即释放和获取的次数相等时,互斥量才能变成有效状态,然后才允许使用 xQueueGenericSend()函数释放一个递归互斥量。

xSemaphoreTakeRecursive()和xSemaphoreGiveRecursive()函数实例 

SemaphoreHandle_t xMutex = NULL;void vATask( void * pvParameters )
{/* 创建一个递归互斥量用于保护共享资源 */xMutex = xSemaphoreCreateRecursiveMutex();
}void vAnotherTask( void * pvParameters ){/* 其他功能代码 */if ( xMutex != NULL ) {/* 尝试获取递归互斥量,如果不可用则等待 10 个 ticks */if(xSemaphoreTakeRecursive(xMutex,( TickType_t ) 10 )== pdTRUE) {/* 获取到递归信号量,可以访问共享资源 *//* ... 其他功能代码 *//* 重复获取递归互斥量 */xSemaphoreTakeRecursive( xMutex, ( TickType_t ) 10 );xSemaphoreTakeRecursive( xMutex, ( TickType_t ) 10 );/* 释放递归互斥量,获取了多少次就要释放多少次 */xSemaphoreGiveRecursive( xMutex );xSemaphoreGiveRecursive( xMutex );xSemaphoreGiveRecursive( xMutex );/* 现在递归互斥量可以被其他任务获取 */} else {/* 没能成功获取互斥量,所以不能安全的访问共享资源 */}}}

互斥量实验

模拟优先级翻转实验

在 FreeRTOS 中创建了三个任务与一个二值信号量, 任务分别是高优先级任务,中优先级任务,低优先级任务, 用于模拟产生优先级翻转。 
低优先级任务在获取信号量的时候,被中优先级打断,中优先级的任务执行时间较长,因为低优先级还未释放信号量,那么高优先级任务就无法取得信号量继续运行,此时就发生了优先级翻转,任务在运行中,使用串口打印出相关信息。

#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
#include "bsp_led.h"
#include "bsp_usart.h"
#include "bsp_key.h"static TaskHandle_t AppTaskCreate_Handle 		= NULL;	/* 创建任务句柄 */
static TaskHandle_t LowPriority_Task_Handle 	= NULL;	/*LowPriority_Task 任务句柄 */
static TaskHandle_t MidPriority_Task_Handle 	= NULL;	/* MidPriority_Task 任务句柄 */
static TaskHandle_t HighPriority_Task_Handle 	= NULL;	/* HighPriority_Task 任务句柄 */SemaphoreHandle_t BinarySem_Handle = NULL;int main(void)
{BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS */BSP_Init();/* 创建 AppTaskCreate 任务 */xReturn = xTaskCreate(	(TaskFunction_t )AppTaskCreate,				/* 任务入口函数 */(const char* )"AppTaskCreate",				/* 任务名字 */(uint16_t )512, 							/* 任务栈大小 */(void* )NULL,								/* 任务入口函数参数 */(UBaseType_t )1,							/* 任务的优先级 */(TaskHandle_t* )&AppTaskCreate_Handle);		/* 任务控制块指针 *//* 启动任务调度 */if (pdPASS == xReturn)vTaskStartScheduler(); /* 启动任务,开启调度 */elsereturn -1;while (1); /* 正常不会执行到这里 */}static void AppTaskCreate(void)
{BaseType_t xReturn = pdPASS;	/* 定义一个创建信息返回值,默认为 pdPASS */taskENTER_CRITICAL(); 			//进入临界区/* 创建 Test_Queue */BinarySem_Handle = xSemaphoreCreateBinary();if (NULL != BinarySem_Handle)printf("BinarySem_Handle 二值信号量创建成功!\r\n");xReturn = xSemaphoreGive( BinarySem_Handle );			//给出二值信号量/* 创建 LowPriority_Task 任务 */xReturn = xTaskCreate(	(TaskFunction_t )LowPriority_Task, 			/* 任务入口函数 */(const char* )"LowPriority_Task",			/*任务名字 */(uint16_t )512, 							/* 任务栈大小 */(void* )NULL, 								/* 任务入口函数参数 */(UBaseType_t )2, 							/* 任务的优先级 */(TaskHandle_t* )&LowPriority_Task_Handle);	/*任务控制块指针 */if (pdPASS == xReturn)printf("创建 LowPriority_Task 任务成功!\r\n");/* 创建 MidPriority_Task 任务 */xReturn = xTaskCreate(	(TaskFunction_t )MidPriority_Task, 			/* 任务入口函数 */(const char* )"MidPriority_Task",			/* 任务名字 */(uint16_t )512, 							/* 任务栈大小 */(void* )NULL,								/* 任务入口函数参数 */(UBaseType_t )3,							/* 任务的优先级 */(TaskHandle_t*)&MidPriority_Task_Handle);	/*任务控制块指针 */if (pdPASS == xReturn)printf("创建 MidPriority_Task 任务成功!\n");/* 创建 HighPriority_Task 任务 */xReturn = xTaskCreate(	(TaskFunction_t )HighPriority_Task, 		/* 任务入口函数 */(const char* )"HighPriority_Task",			/* 任务名字 */(uint16_t )512, 							/* 任务栈大小 */(void* )NULL,								/* 任务入口函数参数 */(UBaseType_t )4, 							/* 任务的优先级 */(TaskHandle_t* )&HighPriority_Task_Handle);	/*任务控制块指针 */if (pdPASS == xReturn)printf("创建 HighPriority_Task 任务成功!\n\n");vTaskDelete(AppTaskCreate_Handle); //删除 AppTaskCreate 任务taskEXIT_CRITICAL(); //退出临界区
}static void LowPriority_Task(void* parameter)
{static uint32_t i;BaseType_t xReturn = pdPASS;	/* 定义一个创建信息返回值,默认为 pdPASS */while (1) {printf("LowPriority_Task 获取信号量\n");//获取二值信号量 xSemaphore,没获取到则一直等待xReturn = xSemaphoreTake( BinarySem_Handle , portMAX_DELAY ); if ( xReturn == pdTRUE )printf("LowPriority_Task Runing\n\n");for (i=0; i<2000000; i++) 	//模拟低优先级任务占用信号量{ taskYIELD();			//发起任务调度}printf("LowPriority_Task 释放信号量!\r\n");xReturn = xSemaphoreGive( BinarySem_Handle );	//给出二值信号量LED1_TOGGLE;vTaskDelay(500);}
}static void MidPriority_Task(void* parameter)
{while (1) {printf("MidPriority_Task Runing\n");vTaskDelay(500);}
}static void HighPriority_Task(void* parameter)
{BaseType_t xReturn = pdTRUE;	/* 定义一个创建信息返回值,默认为 pdPASS */while (1){printf("HighPriority_Task 获取信号量\n");//获取二值信号量 xSemaphore,没获取到则一直等待xReturn = xSemaphoreTake( BinarySem_Handle , portMAX_DELAY ); if (pdTRUE == xReturn)printf("HighPriority_Task Runing\n");LED1_TOGGLE;xReturn = xSemaphoreGive( BinarySem_Handle );	//给出二值信号量vTaskDelay(500);}
}

互斥量实验

#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
#include "bsp_led.h"
#include "bsp_usart.h"
#include "bsp_key.h"static TaskHandle_t AppTaskCreate_Handle 		= NULL;	/* 创建任务句柄 */
static TaskHandle_t LowPriority_Task_Handle 	= NULL;	/*LowPriority_Task 任务句柄 */
static TaskHandle_t MidPriority_Task_Handle 	= NULL;	/* MidPriority_Task 任务句柄 */
static TaskHandle_t HighPriority_Task_Handle 	= NULL;	/* HighPriority_Task 任务句柄 */SemaphoreHandle_t BinarySem_Handle = NULL;int main(void)
{BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS */BSP_Init();/* 创建 AppTaskCreate 任务 */xReturn = xTaskCreate(	(TaskFunction_t )AppTaskCreate,				/* 任务入口函数 */(const char* )"AppTaskCreate",				/* 任务名字 */(uint16_t )512, 							/* 任务栈大小 */(void* )NULL,								/* 任务入口函数参数 */(UBaseType_t )1,							/* 任务的优先级 */(TaskHandle_t* )&AppTaskCreate_Handle);		/* 任务控制块指针 *//* 启动任务调度 */if (pdPASS == xReturn)vTaskStartScheduler(); /* 启动任务,开启调度 */elsereturn -1;while (1); /* 正常不会执行到这里 */
}static void AppTaskCreate(void)
{BaseType_t xReturn = pdPASS;	/* 定义一个创建信息返回值,默认为 pdPASS */taskENTER_CRITICAL(); 			//进入临界区/* 创建 MuxSem */MuxSem_Handle = xSemaphoreCreateMutex();if (NULL != MuxSem_Handle)printf("MuxSem_Handle 互斥量创建成功!\r\n");xReturn = xSemaphoreGive( MuxSem_Handle );			//给出互斥量/* 创建 LowPriority_Task 任务 */xReturn = xTaskCreate(	(TaskFunction_t )LowPriority_Task, 			/* 任务入口函数 */(const char* )"LowPriority_Task",			/*任务名字 */(uint16_t )512, 							/* 任务栈大小 */(void* )NULL, 								/* 任务入口函数参数 */(UBaseType_t )2, 							/* 任务的优先级 */(TaskHandle_t* )&LowPriority_Task_Handle);	/*任务控制块指针 */if (pdPASS == xReturn)printf("创建 LowPriority_Task 任务成功!\r\n");/* 创建 MidPriority_Task 任务 */xReturn = xTaskCreate(	(TaskFunction_t )MidPriority_Task, 			/* 任务入口函数 */(const char* )"MidPriority_Task",			/* 任务名字 */(uint16_t )512, 							/* 任务栈大小 */(void* )NULL,								/* 任务入口函数参数 */(UBaseType_t )3,							/* 任务的优先级 */(TaskHandle_t*)&MidPriority_Task_Handle);	/*任务控制块指针 */if (pdPASS == xReturn)printf("创建 MidPriority_Task 任务成功!\n");/* 创建 HighPriority_Task 任务 */xReturn = xTaskCreate(	(TaskFunction_t )HighPriority_Task, 		/* 任务入口函数 */(const char* )"HighPriority_Task",			/* 任务名字 */(uint16_t )512, 							/* 任务栈大小 */(void* )NULL,								/* 任务入口函数参数 */(UBaseType_t )4, 							/* 任务的优先级 */(TaskHandle_t* )&HighPriority_Task_Handle);	/*任务控制块指针 */if (pdPASS == xReturn)printf("创建 HighPriority_Task 任务成功!\n\n");vTaskDelete(AppTaskCreate_Handle); //删除 AppTaskCreate 任务taskEXIT_CRITICAL(); //退出临界区
}static void LowPriority_Task(void* parameter)
{static uint32_t i;BaseType_t xReturn = pdPASS;	/* 定义一个创建信息返回值,默认为 pdPASS */while (1) {printf("LowPriority_Task 获取信号量\n");//获取互斥量 MuxSem ,没获取到则一直等待xReturn = xSemaphoreTake( MuxSem_Handle , portMAX_DELAY ); if ( xReturn == pdTRUE )printf("LowPriority_Task Runing\n\n");for (i=0; i<2000000; i++) 	//模拟低优先级任务占用信号量{ taskYIELD();			//发起任务调度}printf("LowPriority_Task 释放信号量!\r\n");	xReturn = xSemaphoreGive( MuxSem_Handle );	//给出互斥量LED1_TOGGLE;vTaskDelay(1000);}
}static void MidPriority_Task(void* parameter)
{while (1) {printf("MidPriority_Task Runing\n");vTaskDelay(1000);}
}static void HighPriority_Task(void* parameter)
{BaseType_t xReturn = pdTRUE;	/* 定义一个创建信息返回值,默认为 pdPASS */while (1){printf("HighPriority_Task 获取信号量\n");//获取二值信号量 xSemaphore,没获取到则一直等待xReturn = xSemaphoreTake( MuxSem_Handle , portMAX_DELAY ); if (pdTRUE == xReturn)printf("HighPriority_Task Runing\n");LED1_TOGGLE;printf("HighPriority_Task 释放信号量!\r\n");xReturn = xSemaphoreGive( MuxSem_Handle );	//给出互斥量vTaskDelay(1000);}
}

至此,互斥量内容就已经结束,多数函数都已经被官方封装好,我们直接用就行。
xSemaphoreCreateMutex()                                              创建互斥量
xSemaphoreCreateRecursiveMutex()                              创建递归互斥量
        
vSemaphoreDelete( xSemaphore )                                  删除互斥量

xSemaphoreTake( xSemaphore, xBlockTime )                获取互斥量
xSemaphoreTakeRecursive( xMutex, xBlockTime )         获取递归互斥量

xSemaphoreGive( xSemaphore )                                    释放互斥量
xSemaphoreGiveRecursive( xMutex )                             释放递归互斥量


http://chatgpt.dhexx.cn/article/I1ZdOsYj.shtml

相关文章

C++ 互斥锁原理以及实际使用介绍

兄弟姐妹们&#xff0c;我又回来了&#xff0c;今天带来实际开发中都需要使用的互斥锁的内容&#xff0c;主要聊一聊如何使用互斥锁以及都有哪几种方式实现互斥锁。实现互斥&#xff0c;可以有以下几种方式&#xff1a;互斥量&#xff08;Mutex&#xff09;、递归互斥量&#x…

MySQL mutex互斥锁

在事务机制中&#xff0c;锁机制是为了保证高并发&#xff0c;数据一致性的重要实现方式。MySQL除了Innodb引擎层面的行锁&#xff0c;还有latch锁。latch锁的作用资源协调处理。包含表句柄&#xff0c;线程&#xff0c;cpu线程&#xff0c;内存池等。其保证非常短时间内快速处…

uCOSii中的互斥信号量

uCOSii中的互斥信号量 一、互斥型信号量项管理 (MUTUAL EXCLUSION SEMAPHORE MANAGEMENT) OSMutexAccept() 无条件等待地获取互斥型信号量 OSMutexCreate() 建立并初始化一个互斥型信号量 OSMutexDel() 删除互斥型信号量 OSMutexPend() 等待一个互斥型信号量 OSMutexPost…

互斥信号量

目录 1、Creat 2、Delete 3、Wait 4、Post 5、Statu 互斥信号量 在介绍二进制信号量时&#xff0c;曾讨论到如果二进制信号量创建时设置参数 bInitValue 为TRUE&#xff0c;则可以用于互斥访问共享资源。实际上&#xff0c;SylixOS 的二进制信号量实现的互斥性是将一个变量…

UCOS-III 互斥量

互斥量 一、互斥量基本概念二、互斥量优先级继承机制三、互斥量应用场景四、互斥量运作机制五、互斥量创建流程1、定义互斥量2、创建互斥量 六、互斥量接口函数1、创建互斥量函数OSMutexCreate()2、删除互斥量函数 OSMutexDel()3、获取互斥量函数 OSMutexPend()4、释放互斥量函…

互斥量知识

文章目录 互斥量1、基本概念2、互斥量的优先级继承机制3、互斥量应用场景4、互斥量运行机制5、互斥量控制块6、互斥量函数接口&#xff08;1&#xff09;互斥量创建函数 xSemaphoreCreateMutex()&#xff08;2&#xff09;递归互斥量创建函数 xSemaphoreCreateRecursiveMutex()…

同步和互斥

同步和互斥 竞争与协作 在单核 CPU 系统里&#xff0c;为了实现多个程序同时运行的假象&#xff0c;操作系统通常以时间片调度的方式&#xff0c;让每个进程执行每次执行一个时间片&#xff0c;时间片用完了&#xff0c;就切换下一个进程运行&#xff0c;由于这个时间片的时间很…

多线程的同步与互斥(互斥锁、条件变量、读写锁、自旋锁、信号量)

文章目录 一、同步与互斥的概念二、互斥锁&#xff08;同步&#xff09;三、条件变量&#xff08;同步&#xff09;1、线程的条件变量实例12、线程的条件变量实例23、虚假唤醒(spurious wakeup) 四、读写锁&#xff08;同步&#xff09;五、自旋锁&#xff08;同步&#xff09;…

同步和互斥区别

互斥的概念 由于多线程执行操作共享变量的这段代码可能会导致竞争状态&#xff0c;因此我们将此段代码称为临界区&#xff08;critical section&#xff09;&#xff0c;它是访问共享资源的代码片段&#xff0c;一定不能给多线程同时执行。 我们希望这段代码是互斥&#xff0…

操作系统——互斥的定义及实现

一、进程互斥的定义 所谓进程互斥,指的是对某个系统资源,一个进程正在使用它,另外一个想用它的进程就必须等待,而不能同时使用 。进程互斥是多道程序系统中进程间存在的一种源于资源共享的制约关系,也称间接制约关系,主要是由被共享资源的使用性质所决定的。 二、互斥…

Fisher判别分析详解

Fisher判别分析 将高维度空间的样本投影到低维空间上&#xff0c;使得投影后的样本数据在新的子空间上有最小的类内距离以及最大的类间距离&#xff0c;使得在该子空间上有最佳的可分离性 可以看出右侧投影后具有更好的可分离性。 Fisher判别分析和PCA差别 刚学完感觉两个很…

基于spss的多元统计分析 之 聚类分析+判别分析(2/8)

实验目的&#xff1a; 1&#xff0e;掌握聚类分析及判别分析的基本原理&#xff1b; 2&#xff0e;熟悉掌握SPSS软件进行聚类分析及判别分析的基本操作&#xff1b; 3&#xff0e;利用实验指导的实例数据&#xff0c;上机熟悉聚类分析及判别分析方法。 实验前预习&#xff1a;…

机器学习——线性判别分析

目录 线性判别分析 LDA的降维过程 案例&#xff1a;鸢尾花(Iris) 代码演示 数据集 局部线性嵌入 线性判别分析 线性判别分析&#xff08;LDA&#xff09;是一种有监督的线性降维算法。与PCA不同&#xff0c;LDA是为了使降维后的数据点尽可能地容易被区分。 线性判别分析…

MATLAB判别分析例题,判别分析的matlab实现案例.doc

判别分析的matlab实现案例.doc 读取EXAMP10_01XLS中数据&#xff0c;进行距离判别读取数据读取文件EXAMP10_01XLS的第1个工作表中C2F51范围的数据&#xff0c;即全部样本数据&#xff0c;包括未判企业SAMPLEXLSREAD EXAMP10_01XLS , , C2F51 读取文件EXAMP10_01XLS的第1个工作…

SAS数据分析之判别分析

判别分析与聚类分析有非常类似的特性&#xff0c;因此&#xff0c;在多数数据分析的教材中&#xff0c;这两章是一前一后出现的&#xff0c;简而言之&#xff0c;聚类分析&#xff0c;其实是判别分析的基础&#xff0c;即在聚类分析的基础上&#xff0c;总结出各类的权值&#…

线性判别分析

线性判别分析&#xff08;LDA&#xff09;是一种经典的线性学习方法。其思想是&#xff1a;给定训练样例集&#xff0c;设法将样例投影到一条直线上&#xff0c;使得同类样例的投影点尽可能接近、异类样例的投影点尽可能远离。如图所示的二分类示意图&#xff1a; 损失函数的…

sas判别分析

#判别分析两大类&#xff1a;Fisher&Bayes #neighbbor&#xff1a;马氏距离和欧氏距离&#xff1b; #典型判别分析&#xff0c;联系典型相关分析#组变量相关&#xff0c;最后得到的每组内变量的线性组合作为典型变量 #协方差矩阵&#xff0c;举个栗子,设一组特征x(a1,a…

【数模】判别分析

文章目录 判别分析简介SPSS操作步骤输出结果分析 判别分析简介 判别分析又称“分辨法”&#xff0c;是在分类确定的条件下&#xff0c;根据某一研究对象的各种特征值判别其类型归属问题的一种多变量统计分析方法。 其基本原理是按照一定的判别准则&#xff0c;建立一个或多个判…

fisher线性判别分析matlab,线性判别分析LDA

首先搞清楚什么叫判别分析&#xff1f;Discriminant Analysis就是根据研究对象的 各种特征值判别其类型归属问题的一种多变量统计分析方法。 根据判别标准不同&#xff0c;可以分为距离判别、Fisher判别、Bayes判别法等。比如在KNN中用的就是距离判别&#xff0c;当然这里的“距…

spssfisher判别分析步骤_spss进行判别分析步骤_spss进行判别分析

1.Discriminant Analysis判别分析主对话框 如图 1-1 所示 图 1-1 Discriminant Analysis 主对话框 (1)选择分类变量及其范围 在主对话框中左面的矩形框中选择表明已知的观测量所属类别的变量(一定是离散变量), 按上面的一个向右的箭头按钮,使该变量名移到右面的Groupin…