11、互斥量
1 、互斥量基本概念
互斥量又称互斥信号量(本质是信号量),是一种特殊的二值信号量,它和信号量不同的是,它支持互斥量所有权、递归访问以及防止优先级翻转的特性,用于实现对临界资源的独占式处理。任意时刻互斥量的状态只有两种,开锁或闭锁。当互斥量被任务持有时,该互斥量处于闭锁状态,这个任务获得互斥量的所有权。当该任务释放这个互斥量时,该互斥量处于开锁状态, 任务失去该互斥量的所有权。当一个任务持有互斥量时,其他任务将不能再对该互斥量进行开锁或持有。 持有该互斥量的任务也能够再次获得这个锁而不被挂起,这就是递归访问,也就是递归互斥量的特性,这个特性与一般的信号量有很大的不同,在信号量中,由于已经不存在可用的信号量, 任务递归获取信号量时会发生主动挂起任务最终形成死锁。
如果想要用于实现同步(任务之间或者任务与中断之间),二值信号量或许是更好的选择,虽然互斥量也可以用于任务与任务、 任务与中断的同步,但是互斥量更多的是用于保护资源的互锁。
用于互锁的互斥量可以充当保护资源的令牌, 当一个任务希望访问某个资源时,它必须先获取令牌。当任务使用完资源后,必须还回令牌,以便其它任务可以访问该资源。是不是很熟悉,在我们的二值信号量里面也是一样的,用于保护临界资源,保证多任务的访问井然有序。当任务获取到信号量的时候才能开始使用被保护的资源,使用完就释放信号量,下一个任务才能获取到信号量从而可用使用被保护的资源。但是信号量会导致的另一个潜在问题,那就是任务优先级翻转。 而 FreeRTOS 提供的互斥量可以通过优先级继承算法,可以降低优先级翻转问题产生的影响,所以,用于临界资源的保护一般建议使用互斥量。
2、互斥量的优先级继承机制
在 FreeRTOS 操作系统中为了降低优先级翻转问题利用了优先级继承算法。优先级继承算法是指,暂时提高某个占有某种资源的低优先级任务的优先级,使之与在所有等待该资源的任务中优先级最高那个任务的优先级相等,而当这个低优先级任务执行完毕释放该资源时,优先级重新回到初始设定值。因此,继承优先级的任务避免了系统资源被任何中间优先级的任务抢占。
互斥量与二值信号量最大的不同是:互斥量具有优先级继承机制,而信号量没有。也就是说,某个临界资源受到一个互斥量保护,如果这个资源正在被一个低优先级任务使用,那么此时的互斥量是闭锁状态,也代表了没有任务能申请到这个互斥量,如果此时一个高优先级任务想要对这个资源进行访问,去申请这个互斥量,那么高优先级任务会因为申请不到互斥量而进入阻塞态,那么系统会将现在持有该互斥量的任务的优先级临时提升到与高优先级任务的优先级相同,这个优先级提升的过程叫做优先级继承。这个优先级继承机制确保高优先级任务进入阻塞状态的时间尽可能短,以及将已经出现的“优先级翻转”危害降低到最小。
没有理解?没问题,结合过程示意图再说一遍。我们知道任务的优先级在创建的时候就已经是设置好的,高优先级的任务可以打断低优先级的任务,抢占 CPU 的使用权。但是在很多场合中,某些资源只有一个,当低优先级任务正在占用该资源的时候,即便高优先级任务也只能乖乖的等待低优先级任务使用完该资源后释放资源。这里高优先级任务无法运行而低优先级任务可以运行的现象称为“优先级翻转”。
为什么说优先级翻转在操作系统中是危害很大?因为在我们一开始创造这个系统的时候,我们就已经设置好了任务的优先级了,越重要的任务优先级越高。但是发生优先级翻转,对我们操作系统是致命的危害,会导致系统的高优先级任务阻塞时间过长。
举个例子,现在有 3 个任务分别为 H 任务( High)、 M 任务( Middle)、 L 任务(Low), 3 个任务的优先级顺序为 H 任务>M 任务>L 任务。正常运行的时候 H 任务可以打断 M 任务与 L 任务, M 任务可以打断 L 任务,假设系统中有一个资源被保护了,此时该资源被 L 任务正在使用中,某一刻, H 任务需要使用该资源,但是 L 任务还没使用完, H任务则因为申请不到资源而进入阻塞态, L 任务继续使用该资源,此时已经出现了“优先级翻转” 现象,高优先级任务在等着低优先级的任务执行,如果在 L 任务执行的时候刚好M 任务被唤醒了,由于 M 任务优先级比 L 任务优先级高,那么会打断 L 任务,抢占了CPU 的使用权,直到 M 任务执行完,再把 CUP 使用权归还给 L 任务, L 任务继续执行,等到执行完毕之后释放该资源, H 任务此时才从阻塞态解除,使用该资源。这个过程,本来是最高优先级的 H 任务,在等待了更低优先级的 L 任务与 M 任务,其阻塞的时间是 M任务运行时间+L 任务运行时间,这只是只有 3 个任务的系统,假如很多个这样子的任务打断最低优先级的任务,那这个系统最高优先级任务岂不是崩溃了,这个现象是绝对不允许出现的,高优先级的任务必须能及时响应。所以,没有优先级继承的情况下,使用资源保护,其危害极大,具体见下图。
优先级翻转图解
(1): L 任务正在使用某临界资源, H 任务被唤醒,执行 H 任务。但 L 任务并未执行完毕,此时临界资源还未释放。
(2):这个时刻 H 任务也要对该临界资源进行访问,但 L 任务还未释放资源,由于保护机制, H 任务进入阻塞态, L 任务得以继续运行,此时已经发生了优先级翻转现象。
(3):某个时刻 M 任务被唤醒,由于 M 任务的优先级高于 L 任务, M 任务抢占了 CPU 的使用权, M 任务开始运行,此时 L 任务尚未执行完,临界资源还没被释放。
(4): M 任务运行结束,归还 CPU 使用权, L 任务继续运行。
(5): L 任务运行结束,释放临界资源, H 任务得以对资源进行访问, H 任务开始运行。
在这过程中, H 任务的等待时间过长,这对系统来说这是很致命的,所以这种情况不允许出现,而互斥量就是用来降低优先级翻转的产生的危害。
假如有优先级继承呢?那么,在 H 任务申请该资源的时候,由于申请不到资源会进入阻塞态,那么系统就会把当前正在使用资源的 L 任务的优先级临时提高到与 H 任务优先级相同,此时 M 任务被唤醒了,因为它的优先级比 H 任务低,所以无法打断 L 任务,因为此时 L 任务的优先级被临时提升到 H,所以当 L 任务使用完该资源了,进行释放,那么此时 H 任务优先级最高,将接着抢占 CPU 的使用权, H 任务的阻塞时间仅仅是 L 任务的执行时间,此时的优先级的危害降到了最低,看!这就是优先级继承的优势,具体见下图:
优先级继承
(1): L 任务正在使用某临界资源, L 任务正在使用某临界资源, H 任务被唤醒,执行 H 任务。但 L 任务并未执行完毕,此时临界资源还未释放。
(2):某一时刻 H 任务也要对该资源进行访问,由于保护机制, H 任务进入阻塞态。此时发生优先级继承,系统将 L 任务的优先级暂时提升到与 H 任务优先级相同, L任务继续执行。
(3):在某一时刻 M 任务被唤醒,由于此时 M 任务的优先级暂时低于 L 任务,所以 M 任务仅在就绪态,而无法获得 CPU 使用权。
(4): L 任务运行完毕, H 任务获得对资源的访问权, H 任务从阻塞态变成运行态,此时 L 任务的优先级会变回原来的优先级。
(5):当 H 任务运行完毕, M 任务得到 CPU 使用权,开始执行。
(6):系统正常运行,按照设定好的优先级运行。但是使用互斥量的时候一定需要注意:在获得互斥量后,请尽快释放互斥量,同时需要注意的是在任务持有互斥量的这段时间,不得更改任务的优先级。 FreeRTOS 的优先级继承机制不能解决优先级反转,只能将这种情况的影响降低到最小, 硬实时系统在一开始设计时就要避免优先级反转发生。
3、互斥量应用场景
互斥量的使用比较单一,因为它是信号量的一种,并且它是以锁的形式存在。在初始化的时候,互斥量处于开锁的状态,而被任务持有的时候则立刻转为闭锁的状态。
互斥量更适合于:可能会引起优先级翻转的情况。
递归互斥量更适用于:
任务可能会多次获取互斥量的情况下。这样可以避免同一任务多次递归持有而造成死锁的问题。
多任务环境下往往存在多个任务竞争同一临界资源的应用场景,互斥量可被用于对临界资源的保护从而实现独占式访问。另外,互斥量可以降低信号量存在的优先级翻转问题带来的影响。
比如有两个任务需要对串口进行发送数据,其硬件资源只有一个,那么两个任务肯定不能同时发送啦,不然导致数据错误,那么,就可以用互斥量对串口资源进行保护,当一个任务正在使用串口的时候,另一个任务则无法使用串口,等到任务使用串口完毕之后,另外一个任务才能获得串口的使用权。
另外需要注意的是互斥量不能在中断服务函数中使用,因为其特有的优先级继承机制只在任务起作用,在中断的上下文环境毫无意义。
4 、互斥量运作机制
多任务环境下会存在多个任务访问同一临界资源的场景,该资源会被任务独占处理。其他任务在资源被占用的情况下不允许对该临界资源进行访问,这个时候就需要用到FreeRTOS 的互斥量来进行资源保护,那么互斥量是怎样来避免这种冲突?
用互斥量处理不同任务对临界资源的同步访问时, 任务想要获得互斥量才能进行资源访问,如果一旦有任务成功获得了互斥量,则互斥量立即变为闭锁状态,此时其他任务会因为获取不到互斥量而不能访问这个资源, 任务会根据用户自定义的等待时间进行等待,直到互斥量被持有的任务释放后,其他任务才能获取互斥量从而得以访问该临界资源,此时互斥量再次上锁,如此一来就可以确保每个时刻只有一个任务正在访问这个临界资源,保证了临界资源操作的安全性。
互斥量运作机制
(1):因为互斥量具有优先级继承机制,一般选择使用互斥量对资源进行保护,如果资源被占用的时候,无论是什么优先级的任务想要使用该资源都会被阻塞。
(2):假如正在使用该资源的任务 1 比阻塞中的任务 2 的优先级还低,那么任务1 将被系统临时提升到与高优先级任务 2 相等的优先级(任务 1 的优先级从 L 变成 H)。
(3):当任务 1 使用完资源之后,释放互斥量,此时任务 1 的优先级会从 H 变回原来的 L。
(4)-(5): 任务 2 此时可以获得互斥量,然后进行资源的访问,当任务 2 访问了资源的时候,该互斥量的状态又为闭锁状态,其他任务无法获取互斥量
5、互斥量控制块
互斥量的 API 函数实际上都是宏,它使用现有的队列机制, 这些宏定义在 semphr.h 文件中, 如果使用互斥量,需要包含 semphr.h 头文件。 所以 FreeRTOS 的互斥量控制块结构体与消息队列结构体是一模一样的,只不过结构体中某些成员变量代表的含义不一样而已,我们会具体讲解一下哪里与消息队列不一样。先来看看结构体控制块,具体见代码清单。
注意:没说明的部分与消息队列一致。
typedef struct QueueDefinition { int8_t *pcHead; int8_t *pcTail; int8_t *pcWriteTo; union { int8_t *pcReadFrom; UBaseType_t uxRecursiveCallCount; (1) } u; List_t xTasksWaitingToSend; List_t xTasksWaitingToReceive; volatile UBaseType_t uxMessagesWaiting; (1) UBaseType_t uxLength; (2) UBaseType_t uxItemSize; (3) 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;
(1): pcReadFrom 与 uxRecursiveCallCount 是一对互斥变量, 使用联合体用来确保两个互斥的结构体成员不会同时出现。 当结构体用于队列时, pcReadFrom 指向出队消息空间的最后一个,见文知义,就是读取消息时候是从 pcReadFrom 指向的空间读取消息内容。 当结构体用于互斥量时,uxRecursiveCallCount 用于计数,记录递归互斥量被“调用” 的次数。 (2):如果控制块结构体是用于消息队列: uxMessagesWaiting 用来记录当前消息队列的消息个数; 如果控制块结构体被用于互斥量的时候, 这个值就表示有效互斥量个数,这个值是 1 则表示互斥量有效,如果是 0 则表示互斥量无效。 (3): 如果控制块结构体是用于消息队列: uxLength 表示队列的长度,也就是能存放多少消息; 如果控制块结构体被用于互斥量的时候, uxLength 表示最大的信号量可用个数, uxLength 最大为 1,因为信号量要么是有效的,要么是无效的。 (4): 如果控制块结构体是用于消息队列: uxItemSize 表示单个消息的大小; 如果控制块结构体被用于互斥量的时候, 则无需存储空间,为 0 即可。
6、 互斥量函数接口讲解
6.1 互斥量创建函数 xSemaphoreCreateMutex()
xSemaphoreCreateMutex()用于创建一个互斥量, 并返回一个互斥量句柄。 该句柄的原型是一个 void 型的指针,在使用之前必须先由用户定义一个互斥量句柄。要想使用该函数必须在 FreeRTOSConfig.h 中把宏 configSUPPORT_DYNAMIC_ALLOCATION 定义为 1,即开启动态内存分配, 其实该宏在 FreeRTOS.h 中默认定义为 1, 即所有 FreeRTOS 的对象在创建的时候都默认使用动态内存分配方案, 同时还需在 FreeRTOSConfig.h 中把configUSE_MUTEXES 宏定义打开, 表示使用互斥量。
/******************************************************************************************************* *@ 函数功能:创建互斥量 *@ 函数参数:无 *@ 返 回 值:成功返回一个互斥量句柄,失败返回NULL *******************************************************************************************************/ #if( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) #define xSemaphoreCreateMutex() xQueueCreateMutex( queueQUEUE_TYPE_MUTEX ) #endif
从 xSemaphoreCreateMutex() 函 数 原 型 就 可 以 看 出 , 创 建 互 斥 量 其 实 是 调 用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; //uxQueueLength 为 1 表示创建的队列长度为 1,其实用作互斥量就表示互斥量的最大可用个数 pxNewQueue = ( Queue_t * ) xQueueGenericCreate( uxMutexLength, uxMutexSize, ucQueueType ); (1) prvInitialiseMutex( pxNewQueue ); //调用 prvInitialiseMutex()函数进行初始胡互斥量 (2) return pxNewQueue; } #endif /* configUSE_MUTEXES */
这个函数是带条件编译的,只有将宏 configUSE_MUTEXES 定义为 1 才会编译这个函数。
(1)、 其实互斥量的创建也是调用 xQueueGenericCreate()函数进行创建。uxQueueLength 为 1 表示创建的队列长度为 1,其实用作互斥量就表示互斥量的最大可用个数,从前面的知识点我们就知道,互斥量要么是开锁(有效),要么是闭锁(无效),长度为 1 不正是这样子的表示吗?同时uxMutexSize 的值为 0,表示创建的消息空间(队列项)大小为 0,因为这个所谓的“消息队列”其实并不是用于存储消息的,而是被用作互斥量,因为我们根本无需关注消息内容是什么,只要知道互斥量是否有效即可, ucQueueType 表示的是创建队列的类型, 在 queue.h 中有定义,现在创建的是互斥量,其类型就是 queueQUEUE_TYPE_MUTEX,在前面的章节我们已经讲解了通用队列创 建函数,在此就不重复赘述。
(2)、 调用 prvInitialiseMutex()函数进行初始胡互斥量
6.2、递归互斥量创建函数 xSemaphoreCreateRecursiveMutex()
xSemaphoreCreateRecursiveMutex()用于创建一个递归互斥量,不是递归的互斥量由函数 xSemaphoreCreateMutex() 或 xSemaphoreCreateMutexStatic()创建(我们只讲解动态创建) ,且只能被同一个任务获取一次, 如果同一个任务想再次获取则会失败。递归信号量则相反,它可以被同一个任务获取很多次, 获取多少次就需要释放多少次。递归信号量与互斥量一样,都实现了优先级继承机制,可以减少优先级反转的反生。
要 想 使 用 该 函 数 必 须 在 FreeRTOSConfig.h 中 把 宏configSUPPORT_DYNAMIC_ALLOCATION 和 configUSE_RECURSIVE_MUTEXES 均定义为 1。宏 configSUPPORT_DYNAMIC_ALLOCATION 定义为 1 即表示开启动态内存分配,其实该宏在 FreeRTOS.h 中默认定义为 1, 即所有 FreeRTOS 的对象在创建的时候都默认使用 动 态 内 存 分 配 方 案 。
/******************************************************************************************************* *@ 函数功能:创建一个递归互斥量 *@ 函数参数:无 *@ 返 回 值:如果创建成功则返回一个递归互斥量句柄,用于访问创建的递归互斥量。 如果创建不成功则返回 NULL。 *******************************************************************************************************/ #if( ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) && ( configUSE_RECURSIVE_MUTEXES == 1 ) ) #define xSemaphoreCreateRecursiveMutex() xQueueCreateMutex( queueQUEUE_TYPE_RECURSIVE_MUTEX ) #endif
其实 xSemaphoreCreateRecursiveMutex()实际调用的函数就是 xQueueCreateMutex()函数,具 体 的 创 建 过 程 也 不 再 重 复 赘 述。
6.3 、互斥量删除函数 vSemaphoreDelete()
互斥量的本质是信号量,直接调用 vSemaphoreDelete()函数进行删除即可
6.4 、互斥量获取函数 xSemaphoreTake()
我们知道,当互斥量处于开锁的状态, 任务才能获取互斥量成功,当任务持有了某个互斥量的时候,其它任务就无法获取这个互斥量,需要等到持有互斥量的任务进行释放后,其他任务才能获取成功, 任务通过互斥量获取函数来获取互斥量的所有权。 任务对互斥量的所有权是独占的,任意时刻互斥量只能被一个任务持有,如果互斥量处于开锁状态,那么获取该互斥量的任务将成功获得该互斥量,并拥有互斥量的使用权;如果互斥量处于闭锁状态,获取该互斥量的任务将无法获得互斥量, 任务将被挂起,在任务被挂起之前,会进行优先级继承,如果当前任务优先级比持有互斥量的任务优先级高,那么将会临时提升持有互斥量任务的优先级。互斥量的获取函数是一个宏定义,实际调用的函数就是xQueueGenericReceive()。
/******************************************************************************************************* *@ 函数功能:该函数用于在任务中获取信号量,不带中断保护。获取的信号量对象可以是二值信号量、计数信号量和互斥量,但是递归互斥量并不能使用这个 API 函数获取。 *@ 函数参数:xSemaphore:要获取的信号量 xBlockTime:等待信号量可用的最大超时时间,单位为 tick(即系统节拍周期) 。如果宏 INCLUDE_vTaskSuspend 定义为 1 且形参 xTicksToWait 设置为portMAX_DELAY , 则任务将一直阻塞在该信号量上(即没有超时时间)。 *@ 返 回 值:获取成功则返回pdTRUE,在指定的超时时间中没有获取成功则返回errQUEUE_EMPTY *******************************************************************************************************/ #define xSemaphoreTake( xSemaphore, xBlockTime ) xQueueGenericReceive( ( QueueHandle_t ) ( xSemaphore ), NULL, ( xBlockTime ), pdFALSE )
6.5 、递归互斥量获取函数 xSemaphoreTakeRecursive()
xSemaphoreTakeRecursive()是一个用于获取递归互斥量的宏,与互斥量的获取函数一样, xSemaphoreTakeRecursive()也是一个宏定义,它最终使用现有的队列机制,实际执行的 函 数 是 xQueueTakeMutexRecursive() 。 互 斥 量 之 前 必 须 由xSemaphoreCreateRecursiveMutex()这个函数创建。 要注意的是该函数不能用于获取由函数xSemaphoreCreateMutex()创建的互斥量。 要想使用该函数必须在头文件 FreeRTOSConfig.h中把宏 configUSE_RECURSIVE_MUTEXES 定义为 1。
/******************************************************************************************************* *@ 函数功能:获取递归互斥量 *@ 函数参数:xMutex:信号量句柄 xBlockTime:如果不是持有互斥量的任务去获取无效的互斥量,那么任务将进行等待用户指定超时时间,单位为 tick(即系统节拍周期) 。如果宏 INCLUDE_vTaskSuspend 定义为 1 且形参 xTicksToWait 设置为portMAX_DELAY , 则任务将一直阻塞在该递归互斥量上(即没有超时时间)。 *@ 返 回 值:获取成功则返回 pdTRUE, 在超时之前没有获取成功则返回 errQUEUE_EMPTY *******************************************************************************************************/ #if( configUSE_RECURSIVE_MUTEXES == 1 ) #define xSemaphoreTakeRecursive( xMutex, xBlockTime ) xQueueTakeMutexRecursive( ( xMutex ), ( xBlockTime ) ) #endif
6.6、 互斥量释放函数 xSemaphoreGive()
任务想要访问某个资源的时候,需要先获取互斥量,然后进行资源访问,在任务使用完该资源的时候,必须要及时归还互斥量,这样别的任务才能对资源进行访问。在前面的讲解中,我们知道,当互斥量有效的时候, 任务才能获取互斥量,那么,是什么函数使得信号量变得有效呢? FreeRTOS 给我们提供了互斥量释放函数 xSemaphoreGive(), 任务可以调用 xSemaphoreGive()函数进行释放互斥量,表示我已经用完了,别人可以申请使用,互斥量的释放函数与信号量的释放函数一致,都是调用 xSemaphoreGive()函数, 但是要注意的是, 互斥量的释放只能在任务中, 不允许在中断中释放互斥量。使用该函数接口时,只有已持有互斥量所有权的任务才能释放它, 当任务调用xSemaphoreGive()函数时会将互斥量变为开锁状态,等待获取该互斥量的任务将被唤醒。
如果任务的优先级被互斥量的优先级翻转机制临时提升,那么当互斥量被释放后, 任务的优先级将恢复为原本设定的优先级。
/******************************************************************************************************* *@ 函数功能:在任务中释放二值信号量、 计数信号量、 互斥量 *@ 函数参数:xSemaphore:要释放的信号量 *@ 返 回 值:成功返回pdPASS;失败返回错误代码err_QUEUE_FULL *******************************************************************************************************/ #define xSemaphoreGive( xSemaphore ) xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ), NULL, semGIVE_BLOCK_TIME, queueSEND_TO_BACK )
6.7 、递归互斥量释放函数 xSemaphoreGiveRecursive()
xSemaphoreGiveRecursive()是一个用于释放递归互斥量的宏。要想使用该函数必须在头文件 FreeRTOSConfig.h 把宏 configUSE_RECURSIVE_MUTEXES 定义为 1。
/******************************************************************************************************* *@ 函数功能:释放一个递归互斥量 *@ 函数参数:xMutex:要释放的递归互斥量 *@ 返 回 值:成功返回pdPASS;失败返回错误代码err_QUEUE_FULL *******************************************************************************************************/ #if( configUSE_RECURSIVE_MUTEXES == 1 ) #define xSemaphoreGiveRecursive( xMutex ) xQueueGiveMutexRecursive( ( xMutex ) ) #endif
xSemaphoreGiveRecursive()函数用于释放一个递归互斥量。已经获取递归互斥量的任务可以重复获取该递归互斥量。使用 xSemaphoreTakeRecursive() 函数成功获取几次递归互斥量,就要使用 xSemaphoreGiveRecursive()函数返还几次,在此之前递归互斥量都处于无效状态, 别的任务就无法获取该递归互斥量。 使用该函数接口时,只有已持有互斥量所有权的任务才能释放它,每释放一次该递归互斥量,它的计数值就减 1。当该互斥量的计数值为 0 时(即持有任务已经释放所有的持有操作), 互斥量则变为开锁状态,等待在该互斥量上的任务将被唤醒。 如果任务的优先级被互斥量的优先级翻转机制临时提升,那么当互斥量被释放后, 任务的优先级将恢复为原本设定的优先级。