欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 房产 > 家装 > FreeRTOS - 互斥量

FreeRTOS - 互斥量

2025/7/18 6:26:49 来源:https://blog.csdn.net/luckyme_/article/details/143028703  浏览:    关键词:FreeRTOS - 互斥量

在学习FreeRTOS过程中,结合韦东山-FreeRTOS手册和视频、野火-FreeRTOS内核实现与应用开发、及网上查找的其他资源,整理了该篇文章。如有内容理解不正确之处,欢迎大家指出,共同进步。

1. 互斥量特性

1.1 互斥量基本概念

互斥量 是一种特殊的二值信号量,它和信号量不同的是,它支持互斥量所有权、递归访问以及防止优先级翻转的特性,用于实现对临界资源的独占式处理。

  • 量:值为0、1
  • 互斥:用来实现互斥访问

它的核心在于:谁上锁,就只能由谁开锁。

很奇怪的是,FreeRTOS的互斥锁,并没有在代码上实现这点:

  • 即使任务A获得了互斥锁,任务B竟然也可以释放互斥锁。
  • 谁上锁、谁释放:只是约定。

任意时刻互斥量的状态只有两种,开锁或闭锁。

当互斥量被任务持有时, 该互斥量处于闭锁状态,这个任务获得互斥量的所有权。

当该任务释放这个互斥量时,该互斥量处于开锁状态,任务失去该互斥量的所有权。

当一个任务持有互斥量时,其他任务将不能再对该互斥量进行开锁或持有。持有该互斥量的任务也能够再次获得这个锁而不被挂起,这就是递归访问,也就是递归互斥量的特性,这个特性与一般的信号量有很大的不同,在信号量中,由于已经不存在可用的信号量,任务递归获取信号量时会发生主动挂起 任务最终形成死锁。

信号量和互斥量均能用于临界资源的保护:

  • 但信号量会导致任务优先级翻转;
  • 互斥量可以通过优先级继承算法,可以降低优先级翻转问题产生的影响
1.2 互斥量的优先级继承机制

**优先级继承机制:**暂时提高某个占有某种资源的低优先级任务的优先级,使之与在所有等待该资源的任务中优先级最高那个任务的优先级相等,而当这个低优先级任务执行完毕释放该资源时,优先级重新回到初始设定值。继承优先级的任务避免了系统资源被任何中间优先级的任务抢占。

**优先级翻转:**任务的优先级在创建时就是设置好的,高优先级的任务可以打断低优先级的任务,抢占CPU的使用权。但是某些资源只有一个,当低优先级任务正在占用该资源的时候,即便高优先级任务也只能乖乖等待低优先级任务使用完该资源后释放资源。这里高优先级任务无法运行而低优先级任务可以运行的现象称为“优先级翻转”。

**为什么说优先级翻转在操作系统中是危害很大?**因为在我们一开始创造这个系统的时候,我们就已经设置好了任务的优先级了,越重要的任务优先级越高。但是发生优先级翻转,对我们操作系统是致命的危害,会导致系统的高优先级任务阻塞时间过长。

优先级继承:

  • 假设持有互斥锁的是任务A,如果更高优先级的任务B也尝试获得这个锁
  • 任务B说:你既然持有宝剑,又不给我,那就继承我的愿望吧
  • 于是任务A就继承了任务B的优先级
  • 这就叫:优先级继承
  • 等任务A释放互斥锁时,它就恢复为原来的优先级
  • 互斥锁内部就实现了优先级的提升、恢复
1.3 互斥量的使用场合

在多任务系统中,任务A正在使用某个资源,还没用完的情况下任务B也来使用的话,就可能导致问题。

比如对于串口,任务A正使用它来打印,在打印过程中任务B也来打印,客户看到的结果就是A、B的信息混杂在一起。

这种现象很常见:

  • 访问外设:刚举的串口例子

  • 读、修改、写操作导致的问题

  • 对变量的非原子化访问

修改变量、设置结构体、在16位的机器上写32位的变量,这些操作都是非原子的。也就是它们的操作过程都可能被打断,如果被打断的过程有其他任务来操作这些变量,就可能导致冲突。

  • 函数重入

“可重入的函数"是指:多个任务同时调用它、任务和中断同时调用它,函数的运行也是安全的。可重入的函数也被称为"线程安全”(thread safe)。

每个任务都维持自己的栈、自己的CPU寄存器,如果一个函数只使用局部变量,那么它就是线程安全的。

函数中一旦使用了全局变量、静态变量、其他外设,它就不是"可重入的",如果该函数正在被调用,就必须阻止其他任务、中断再次调用它。

上述问题的解决方法是:任务A访问这些全局变量、函数代码时,独占它,就是上个锁。这些全局变量、函数代码必须被独占地使用,它们被称为临界资源。

互斥量也被称为互斥锁,使用过程如下:

  • 互斥量初始值为1
  • 任务A想访问临界资源,先获得并占有互斥量,然后开始访问
  • 任务B也想访问临界资源,也要先获得互斥量:被别人占有了,于是阻塞
  • 任务A使用完毕,释放互斥量;任务B被唤醒、得到并占有互斥量,然后开始访问临界资源
  • 任务B使用完毕,释放互斥量

正常来说:在任务A占有互斥量的过程中,任务B、任务C等等,都无法释放互斥量。 但是FreeRTOS未实现这点:任务A占有互斥量的情况下,任务B也可释放互斥量。

1.4 互斥量运作机制

1.5 获取互斥量和释放互斥量的流程

释放互斥量:give(要么成功,要么失败,不可能休眠)

  1. 关中断;
  2. 判断是否需要复位优先级(现有的优先级uxPriority != 原有uxBasePriority)
    1. 将其从现有优先级(与高任务优先级相等)的ReadyList中移除;
    2. 恢复原优先级
    3. 放入原优先级的ReadyList
  3. count++;
  4. 如果有任务在等待该互斥量:则将其唤醒
    • xTasksWaitingToReceive;中的第 1 个任务移除
    • 将其从 DelayList——> ReadList 中
    • 如果恢复任务的优先级 > 当前任务优先级
      • 任务切换

获取互斥量:Take

  1. 关中断;
  2. 判断 count > 0 ?
    • 否:
      • 返回 ERR(不等待)
      • 休眠
        • (1)若 持有互斥量任务(A)的优先级 < 当前任务(B)的优先级
          • 将任务 A 从其优先级的ReadyList中移除;
          • 提升任务 A 的优先级 与 B 的一致
          • 将 任务 A 放入当前任务 B 优先级的ReadyList中;
        • (2)将 当前获取互斥量不成功的任务 B 放入xTasksWaitingToReceive
          * ReadList ——> DelayList中
  3. 是(count > 0):本来count > 0;休眠被唤醒(有任务释放互斥量了)
    • count --;
    • 当前任务即为互斥量的持有者
    • 有任务在等待释放
      • xTasksWaitingToSend中移除
      • DelayList ——> ReadList
      • 如果恢复的任务优先级 > 当前任务的优先级
        • 任务切换
    • return ok;

2. 互斥量函数

2.1 互斥量控制块
typedef struct QueueDefinition
{int8_t *pcHead;					int8_t *pcTail;					int8_t *pcWriteTo;				union							{int8_t *pcReadFrom;			UBaseType_t uxRecursiveCallCount;}u;List_t xTasksWaitingToSend;		List_t xTasksWaitingToReceive;	volatile UBaseType_t uxMessagesWaiting;UBaseType_t uxLength;			UBaseType_t uxItemSize;			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;
  • pcReadFrom 与 uxRecursiveCallCount:
    • 是一对互斥变量,使用联合体用来确保两个互斥的结构体成员不会同时出现。
    • 当结构体用于互斥量时,uxRecursiveCallCount 用于计数,记录递归互斥量被 “调用”的次数。
  • uxMessagesWaiting : 表示有效互斥量个数
    • 1:表示互斥量有效,0 :表示互斥量无效。
  • uxLength : 最大的信号量可用个数
    • 最大为 1,因为信号量要么是有效的,要么是无效的。
  • uxItemSize: 无需存储空间,为 0 。
2.2 互斥量创建函数 xSemaphoreCreateMutex()
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )#define xSemaphoreCreateMutex()\xQueueCreateMutex( queueQUEUE_TYPE_MUTEX )
#endif
#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 */

2.3 递归互斥量创建函数 xSemaphoreCreateRecursiveMutex()

xSemaphoreCreateRecursiveMutex()用于创建一个递归互斥量。 可以被同一个任务获取很多次,获取多少次就需要释放多少次。递归信号量与 互斥量一样,都实现了优先级继承机制,可以降低优先级反转的危害。

2.4 互斥量删除函数 vSemaphoreDelete()

互斥量的本质是信号量 , 直接调用 vSemaphoreDelete()函数进行删除即可 。

#define vSemaphoreDelete( xSemaphore )\vQueueDelete( ( QueueHandle_t ) ( xSemaphore ) )
2.5 互斥量获取函数 xSemaphoreTake()

当前互斥量数为 1,则任务获取互斥量成功,当任务持有了某个互斥量的时候,其他任务就无法获取这个互斥量,需要等到持有互斥量的任务 进行释放后, 其他任务才能获取成功,任务通过互斥量获取函数来获取互斥量的所有权。

如果互斥量处于开锁状态,那 么获取该互斥量的任务将成功获得该互斥量,并拥有互斥量的使用权;如果互斥量处于闭 锁状态,想获取该互斥量的任务将无法成功获得互斥量,任务将被挂起,在任务被挂起之前,会进行优先级继承,如果当前任务优先级比持有互斥量的任务优先级高,那么将会临时提升 持有互斥量任务的优先级。由xTaskPriorityInherit函数实现,该函数在xQueueSemaphoreTake()中被调用。

#define xSemaphoreTake( xSemaphore, xBlockTime )\xQueueSemaphoreTake( ( xSemaphore ), ( xBlockTime ) )
for( ;; ){taskENTER_CRITICAL();{/* Semaphores are queues with an item size of 0, and where thenumber of messages in the queue is the semaphore's count value. */const UBaseType_t uxSemaphoreCount = pxQueue->uxMessagesWaiting;/* Is there data in the queue now?  To be running the calling taskmust be the highest priority task wanting to access the queue. */if( uxSemaphoreCount > ( UBaseType_t ) 0 ){traceQUEUE_RECEIVE( pxQueue );/* Semaphores are queues with a data size of zero and where themessages waiting is the semaphore's count.  Reduce the count. */pxQueue->uxMessagesWaiting = uxSemaphoreCount - ( UBaseType_t ) 1;#if ( configUSE_MUTEXES == 1 ){if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX ){/* 当前互斥量 > 0,所以当前任务可以获得互斥量,因此当前任务成为互斥量的持有者 */pxQueue->pxMutexHolder = ( int8_t * ) pvTaskIncrementMutexHeldCount(); /*lint !e961 Cast is not redundant as TaskHandle_t is a typedef. */}else{mtCOVERAGE_TEST_MARKER();}}#endif /* configUSE_MUTEXES */// 互斥量无效,当前任务获取互斥量不成功,进入阻塞
vTaskSuspendAll();
prvLockQueue( pxQueue );/* Update the timeout state to see if it has expired yet. */
if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE )
{/* A block time is specified and not expired.  If the semaphorecount is 0 then enter the Blocked state to wait for a semaphore tobecome available.  As semaphores are implemented with queues thequeue being empty is equivalent to the semaphore count being 0. */if( prvIsQueueEmpty( pxQueue ) != pdFALSE ){traceBLOCKING_ON_QUEUE_RECEIVE( pxQueue );#if ( configUSE_MUTEXES == 1 ){if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX ){taskENTER_CRITICAL();{  /* 互斥量无效(不可获取,被别的任务持有),判断互斥量的持有者的优先级是否小于当前任务的优先级,若是,则进行优先级继承 */xInheritanceOccurred = xTaskPriorityInherit( ( void * ) pxQueue->pxMutexHolder );}taskEXIT_CRITICAL();}else{mtCOVERAGE_TEST_MARKER();}}#endif
2.5.1 优先级继承xTaskPriorityInherit
#if ( configUSE_MUTEXES == 1 )BaseType_t xTaskPriorityInherit( TaskHandle_t const pxMutexHolder )
{
TCB_t * const pxMutexHolderTCB = ( TCB_t * ) pxMutexHolder;
BaseType_t xReturn = pdFALSE;if( pxMutexHolder != NULL ){/* 判断持有互斥量任务与当前任务的优先级 */if( pxMutexHolderTCB->uxPriority < pxCurrentTCB->uxPriority ){/* Adjust the mutex holder state to account for its newpriority.  Only reset the event list item value if the value isnot being used for anything else. */if( ( listGET_LIST_ITEM_VALUE( &( pxMutexHolderTCB->xEventListItem ) ) & taskEVENT_LIST_ITEM_VALUE_IN_USE ) == 0UL ){/* 调整互斥锁持有者等待的事件列表项的优先级 */listSET_LIST_ITEM_VALUE( &( pxMutexHolderTCB->xEventListItem ),( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) pxCurrentTCB->uxPriority ); }else{mtCOVERAGE_TEST_MARKER();}/* 如果被提升优先级的任务(互斥量的持有者)处于就绪列表中*/if( listIS_CONTAINED_WITHIN( &( pxReadyTasksLists[ pxMutexHolderTCB->uxPriority ] ),&( pxMutexHolderTCB->xStateListItem ) ) != pdFALSE ){/* 将其从其原始优先级的就绪列表中移除 */if( uxListRemove( &( pxMutexHolderTCB->xStateListItem ) ) == ( UBaseType_t ) 0 ){taskRESET_READY_PRIORITY( pxMutexHolderTCB->uxPriority );}else{mtCOVERAGE_TEST_MARKER();}/* 暂时提升持有互斥量任务的优先级,提升到与当前任务优先级一致,放入优先级Readylist中 */pxMutexHolderTCB->uxPriority = pxCurrentTCB->uxPriority;prvAddTaskToReadyList( pxMutexHolderTCB );}else{/* 如果任务不是在就绪列表中,就仅仅是提升任务优先级即可 */pxMutexHolderTCB->uxPriority = pxCurrentTCB->uxPriority;}traceTASK_PRIORITY_INHERIT( pxMutexHolderTCB, pxCurrentTCB->uxPriority );/* Inheritance occurred. */xReturn = pdTRUE;}else{if( pxMutexHolderTCB->uxBasePriority < pxCurrentTCB->uxPriority ){/* The base priority of the mutex holder is lower than thepriority of the task attempting to take the mutex, but thecurrent priority of the mutex holder is not lower than thepriority of the task attempting to take the mutex.Therefore the mutex holder must have already inherited apriority, but inheritance would have occurred if that hadnot been the case. */xReturn = pdTRUE;}else{mtCOVERAGE_TEST_MARKER();}}}else{mtCOVERAGE_TEST_MARKER();}return xReturn;
}#endif /* configUSE_MUTEXES */
2.6 递归互斥量获取函数 xSemaphoreTakeRecursive()

2.7 互斥量释放函数 xSemaphoreGive()

使用该函数接口时,只有已持有互斥量所有权的任务才能释放它,当任务调用xSemaphoreGive()函数时会将互斥量变为开锁状态,等待获取该互斥量的任务将被唤醒。如果任务的优先级被互斥量的优先级翻转机制临时提升,那么当互斥量被释放后,任务的优先级将恢复为原本设定的优先级,

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

在释放互斥量的时候我们需要恢 复任务的初始优先级,所以,下面我们来看看具体在哪恢复任务的优先级,其实就是 prvCopyDataToQueue()这个函数,该函数xQueueGenericSend()中被调用。

代码已删减,只保留互斥量部分:

static BaseType_t prvCopyDataToQueue( Queue_t * const pxQueue, const void *pvItemToQueue, const BaseType_t xPosition )
{if( pxQueue->uxItemSize == ( UBaseType_t ) 0 ){#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 */}pxQueue->uxMessagesWaiting = uxMessagesWaiting + ( UBaseType_t ) 1;
}

真正恢复任务的优先级函数其实是调 用 xTaskPriorityDisinherit(),而且系统会将结构体的pxMutexHolder 成员变量指向 NULL, 表示暂时没有任务持有改互斥量,对结构体成员uxMessagesWaiting 加 1 操作就代表了释放互斥量 ,表示此时互斥量是有效的,其他任务可以来获取 。

2.7.1 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 ){configASSERT( pxTCB == pxCurrentTCB );configASSERT( pxTCB->uxMutexesHeld );( pxTCB->uxMutexesHeld )--;/* 判断优先级是否被临时提升 */if( pxTCB->uxPriority != pxTCB->uxBasePriority ){/* 如果任务没有持有其他互斥量 */if( pxTCB->uxMutexesHeld == ( UBaseType_t ) 0 ){/* 将任务从当前优先级的ReadyList中删除 */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 ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. *//* 将任务重新添加到原始优先级的就绪列表中 */prvAddTaskToReadyList( pxTCB );xReturn = pdTRUE;} else{mtCOVERAGE_TEST_MARKER();}}else{mtCOVERAGE_TEST_MARKER();}} else{mtCOVERAGE_TEST_MARKER();}return xReturn;
}#endif /* configUSE_MUTEXES */

3. 互斥量实验

3.1 优先级继承实验

上一个代码中问题在于,car1低优先级任务获得了锁,但是它优先级太低而无法运行。

如果能提升car1任务的优先级,让它能尽快运行、释放锁,"优先级反转"的问题不就解决了吗?

把car1任务的优先级提升到什么水平?car3也想获得同一个互斥锁,不成功而阻塞时,它会把car1的优先级提升得跟car3一样。

3.1.1 代码实现:

主要看nwatch\game2.c。

  1. freertos.c代码:
void game1_task(void *params);static SemaphoreHandle_t g_xI2CMutex;void GetI2C(void)
{/* 等待一个互斥量 */xSemaphoreTake(g_xI2CMutex, portMAX_DELAY);
}void PutI2C(void)
{/* 释放互斥量 */xSemaphoreGive(g_xI2CMutex);
}void MX_FREERTOS_Init(void) 
{/* USER CODE BEGIN Init *//* 定义一个互斥量 */g_xI2CMutex = xSemaphoreCreateMutex();
}
  1. draw.c代码
extern void GetI2C(void);
extern void PutI2C(void);void draw_flushArea(byte x, byte y, byte w, byte h)
{
//    while (bInUsed);//taskENTER_CRITICAL();
//    bInUsed = 1;GetI2C();LCD_FlushRegion(x, y, w, h);PutI2C();
//    bInUsed = 0;//taskEXIT_CRITICAL();
}
  1. game2.c代码
static void Car1Task(void *params)
{/* 获取信号量:获取到票后才能进城 */xSemaphoreTake(g_xSemTicks, portMAX_DELAY);while(1){vTaskDelay(50);if (pcar->x == g_xres - CAR_LENGTH){/* 释放信号量 */xSemaphoreGive(g_xSemTicks);vTaskDelete(NULL);}	}}
}static void Car2Task(void *params)
{/* 故意延迟1000,即1s后再让它运行 */vTaskDelay(1000);/* 获取信号量:获取到票后才能进城 *///xSemaphoreTake(g_xSemTicks, portMAX_DELAY);while(1){	//vTaskDelay(50);mdelay(50);// 不会进入阻塞状态if (pcar->x == g_xres - CAR_LENGTH){/* 释放信号量 *///xSemaphoreGive(g_xSemTicks);vTaskDelete(NULL);}	}
}
static void Car3Task(void *params)
{/* 延迟2000,即2s*/vTaskDelay(2000);/* 获取信号量:获取到票后才能进城 */xSemaphoreTake(g_xSemTicks, portMAX_DELAY);while(1){vTaskDelay(50);if (pcar->x == g_xres - CAR_LENGTH){/* 释放信号量 */xSemaphoreGive(g_xSemTicks);vTaskDelete(NULL);}}
}void car_game(void)
{//int x;int i,j;g_framebuffer = LCD_GetFrameBuffer(&g_xres, &g_yres, &g_bpp);draw_init();draw_end();/* 互斥量 */g_xSemTicks = xSemaphoreCreateMutex();xTaskCreate(Car1Task, "car1", 128, &g_cars[0], osPriorityNormal, NULL);xTaskCreate(Car2Task, "car2", 128, &g_cars[1], osPriorityNormal+2, NULL);xTaskCreate(Car3Task, "car3", 128, &g_cars[2], osPriorityNormal+3, NULL);	}

创建的是互斥量:

259 void car_game(void)260 {261	int x;262	int i, j;263	g_framebuffer = LCD_GetFrameBuffer(&g_xres, &g_yres, &g_bpp);264	draw_init();265	draw_end();267	268	//g_xSemTicks = xSemaphoreCreateCounting(1, 1);269	g_xSemTicks = xSemaphoreCreateMutex();

把第268行打开、第269行去掉,就会有优先级反转的问题。

把第268行去掉、第269行打开,就解决了优先级反转的问题。

22_mutex_priority_inversion的实验现象为:car1先运行一会;然后car2运行一会;接着car3任务启动,但是它无法获得互斥量而阻塞,并且提升了car1的优先级;于是:car1、car2交替运行(虽然car1的优先级高于car2,但是car1会使用vTaskDelay阻塞,car2就有机会运行了);当car1运行到终点,是否了互斥量,car3就可以运行了。

信号量-优先级反转:

car1:优先级为 0;

car2:优先级为 2;

car3:优先级为 3;

car1:获取了信号量(take),释放信号量(give)

car2:延迟1s(即进入阻塞)后再运行,无获取信号量(take),无释放信号量(give)

car3:延迟2s(即进入阻塞),获取信号量(take),释放信号量(give)

car1先获得信号量,所以car1先运行,跑了1s后,car2运行,过了一会car3任务启动,但它无法获得信号量而阻塞。当car2跑到最右边后,car1继续运行,跑到最右边后,释放信号量(give),car3获得信号量,跑到最右边,释放信号量

互斥量-优先级继承:

car1先获得信号量,所以car1先运行,跑了1s后,car2也开始运行。接着car3任务启动,但它无法获得互斥量而阻塞,并且提升了car1的优先级;于是car1和car2交替运行,car1先到最右边,释放互斥量,car3就可以运行了,当car3运行到终点,car2继续运行。

3.2 递归锁
3.2.1 死锁的概念

日常生活的死锁:我们只招有工作经验的人!我没有工作经验怎么办?那你就去找工作啊!

假设有2个互斥量M1、M2,2个任务A、B:

  • A获得了互斥量M1
  • B获得了互斥量M2
  • A还要获得互斥量M2才能运行,结果A阻塞
  • B还要获得互斥量M1才能运行,结果B阻塞
  • A、B都阻塞,再无法释放它们持有的互斥量
  • 死锁发生!
3.2.2 自我死锁

假设这样的场景:

  • 任务A获得了互斥锁M
  • 它调用一个库函数
  • 库函数要去获取同一个互斥锁M,于是它阻塞:任务A休眠,等待任务A来释放互斥锁!
  • 死锁发生!
3.2.3 函数

怎么解决这类问题?可以使用递归锁(Recursive Mutexes),它的特性如下:

  • 任务A获得递归锁M后,它还可以多次去获得这个锁
  • "take"了N次,要"give"N次,这个锁才会被释放

递归锁的函数和一般互斥量的函数名不一样,参数类型一样,列表如下:

/* 创建一个递归锁,返回它的句柄。** 此函数内部会分配互斥量结构体* * 返回值: 返回句柄,非NULL表示成功**/SemaphoreHandle_t xSemaphoreCreateRecursiveMutex( void );*/ 释放 */BaseType_t xSemaphoreGiveRecursive( SemaphoreHandle_t xSemaphore );*/ 获得 */BaseType_t xSemaphoreTakeRecursive(SemaphoreHandle_t xSemaphore,TickType_t xTicksToWait);

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词