目录
- 1.使用场合
- 2.函数
-
- [2.1 创建](#2.1 创建)
-
- [2.1.1 动态创建](#2.1.1 动态创建)
- [2.1.2 静态创建](#2.1.2 静态创建)
- [2.2 删除](#2.2 删除)
- [2.3 释放(Give)](#2.3 释放(Give))
- [2.4 获取(Take)](#2.4 获取(Take))
- [2.5 ISR 版本注意事项](#2.5 ISR 版本注意事项)
- 3.常规使用流程
- 4.和二进制信号量的对比
- 5.递归锁
-
- [5.1 死锁](#5.1 死锁)
- [5.2 概念](#5.2 概念)
-
- [5.2.1 问题](#5.2.1 问题)
- [5.2.2 解决方案:递归互斥量](#5.2.2 解决方案:递归互斥量)
- [5.2.3 示例](#5.2.3 示例)
- [5.3 函数](#5.3 函数)
- 6.内部机制
-
- [6.1 创建](#6.1 创建)
- [6.2 take](#6.2 take)

- 量:值为0、1
- 互斥:用来实现互斥访问
它的核心在于:谁上锁,就只能由谁开锁。
很奇怪的是,FreeRTOS的互斥锁,并没有在代码上实现这点:即使任务A获得了互斥锁,任务B竟然也可以释放互斥锁。谁上锁、谁释放:只是约定。
FreeRTOS的互斥量并没有实现"谁上锁,就由谁解锁",只能靠程序员的自觉了。
1.使用场合
1. 保护共享外设
- 比如串口(UART)资源,任务A和任务B都需要使用串口打印信息。如果不加保护,两个任务可能在同一时刻同时发送数据,导致最终串口输出的字符混杂在一起,客户看到的信息就会乱序。
- 任务在访问串口前先获取互斥量(即"上锁"),完成打印后再释放互斥量("解锁"),确保一次只有一个任务能操作串口。
2. 保护共享数据或全局变量
- 对于共享的全局变量,比如一个整数变量
a
,多个任务同时修改该变量(例如执行a = a + 8;
)时,由于该操作实际上分为"读-修改-写"三个步骤,如果在此过程中被其它任务打断,就可能导致结果错误(如预期17却变为9,任务A在执行读值后被任务B抢占,任务B先完成整个操作后,任务A再完成剩下的修改,最终结果可能仍为9,而非预期的17 )。 - 在修改全局变量前,任务先获取互斥量,完成整个读、修改、写操作后释放互斥量,从而保证该操作是原子性的,不会被其他任务中断。
3. 保护非重入函数
- 某些函数如果使用了全局变量、静态变量或外设,即使它们看似只是一条语句,也可能包含多个步骤,如果多个任务或中断同时调用这些函数,会发生数据竞争,导致结果不正确。这些函数称为非重入(或非线程安全)函数。(如某个函数
WriteToDevice()
写入数据到外设,该函数内部使用了全局缓冲区。如果多个任务同时调用该函数,可能会导致缓冲区数据混乱,写入错误数据) - 在调用这些函数前,先获得互斥量,确保同一时间只有一个任务进入该函数执行,然后再释放互斥量,保证函数的调用是串行化的。
2.函数
- 动态创建: 使用
xSemaphoreCreateMutex()
;
静态创建: 使用xSemaphoreCreateMutexStatic()
。 - 操作函数包括获得(
xSemaphoreTake()
)、释放(xSemaphoreGive()
)和删除(vSemaphoreDelete()
)。 - 重要提醒: 互斥量不能在 ISR 中使用,应仅在任务上下文中调用,确保系统同步与资源保护的正确性。
2.1 创建
使用互斥量前必须先创建它,并获得一个句柄。创建互斥量有两种方式:动态分配内存和静态分配内存。使用互斥量之前,必须在配置文件 FreeRTOSConfig.h 中定义:
c
#define configUSE_MUTEXES 1
2.1.1 动态创建
c
SemaphoreHandle_t xSemaphoreCreateMutex(void);
- 此函数内部会调用内存分配函数,为互斥量分配存储结构。
- 创建成功后返回一个非 NULL 的句柄,否则返回 NULL。
- 互斥量的初始状态为"未占用"(相当于值为 1),但内部还会跟踪哪个任务获得了锁。
2.1.2 静态创建
c
SemaphoreHandle_t xSemaphoreCreateMutexStatic(StaticSemaphore_t *pxMutexBuffer);
- 用户必须预先定义一个
StaticSemaphore_t
类型的变量,用来存储互斥量的结构。 - 此函数不会动态分配内存,而是利用用户提供的内存来构建互斥量。
- 返回非 NULL 的句柄表示成功。
2.2 删除
c
void vSemaphoreDelete(SemaphoreHandle_t xSemaphore);
- 用于删除互斥量并释放动态分配的内存(对于动态创建的互斥量)。
- 静态创建的互斥量,由用户管理其内存,所以调用删除函数后不释放内存。
2.3 释放(Give)
c
BaseType_t xSemaphoreGive(SemaphoreHandle_t xSemaphore);
- 该函数用于释放一个已获得的互斥量,即将互斥量"归还"给系统。
- 内部会检查调用任务是否为当前持有互斥量的任务(互斥量拥有所有权),确保只有拥有者才能释放锁。
- 如果释放成功返回 pdTRUE,否则返回错误(例如,如果互斥量已经处于未被占用状态)。
互斥量设计为只允许获得它的任务释放它,以防止出现多个任务错误地释放互斥量。
2.4 获取(Take)
c
BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait);
-
用于请求并获得互斥量,即进入临界区保护共享资源。
-
参数
xTicksToWait
指定了任务等待互斥量的最大 Tick 数: -
- 0 表示不阻塞,立即返回;
portMAX_DELAY
表示无限等待,直到互斥量可用;- 其他值表示等待指定的 Tick 数量。
-
成功获得互斥量返回 pdTRUE,否则返回错误(如超时)。
-
内部记录了获得互斥量的任务,从而支持优先级继承机制。
2.5 ISR 版本注意事项
虽然 FreeRTOS 为一般信号量提供了 ISR 版本的 give/take 函数,如:
xSemaphoreGiveFromISR(...)
xSemaphoreTakeFromISR(...)
但互斥量不能在 ISR 中使用:
- 互斥量需要跟踪获得者(所有权),而 ISR 没有任务上下文,无法维护这一信息。
- 使用互斥量的主要目的是保护长时间运行的临界区,而 ISR 应尽可能短小,且应采用其他机制(如二进制信号量)实现中断与任务的同步。
3.常规使用流程
- 创建互斥量:
使用xSemaphoreCreateMutex()
或xSemaphoreCreateMutexStatic()
得到互斥量句柄。 - 获得互斥量:
在访问共享资源(临界区)之前,任务调用xSemaphoreTake()
,并在必要时阻塞等待,直到获得互斥量。 - 访问临界资源:
一旦获得互斥量,任务即可安全访问共享资源,执行读/修改/写等操作。 - 释放互斥量:
操作完成后,任务调用xSemaphoreGive()
释放互斥量,使得其他等待该资源的任务能继续执行。 - 删除互斥量:
如果互斥量不再需要(通常在任务结束或系统关闭时),可以调用vSemaphoreDelete()
删除动态分配的互斥量。
4.和二进制信号量的对比
一开始就提到了,FreeRTOS的互斥量并没有实现"谁上锁,就由谁解锁",只能靠程序员的自觉了。
那这样互斥量和二进制信号量岂不是一样??并不是
- 互斥量能解决优先级反转的问题
- 能解决递归上锁解锁的问题 ---- 递归锁,特殊的互斥量
什么是优先级反转?举个栗子:
设系统中有三个任务:
- 任务L(Low Priority):低优先级任务
- 任务M(Medium Priority):中优先级任务
- 任务H(High Priority):高优先级任务
- 任务L首先获得了一个共享资源的互斥锁(比如访问串口或共享变量)。
- 任务H随后因某种原因被触发,需要访问该共享资源,但由于任务L还在使用该资源,任务H必须等待。
- 此时,任务M开始运行。任务M虽然不需要共享资源,但由于其优先级介于任务H和任务L之间,它可以抢占任务L的执行。
- 任务L 无法运行,导致其持有的资源长时间未能释放。结果,任务H被迫等待,而系统实际上让中优先级任务(任务M)占据了CPU,这就使高优先级任务(任务H)等待时间不合理地延长了。
这种情况下,高优先级任务被低优先级任务"间接"阻塞,中优先级任务反而插入调度,导致系统响应延迟,这就是优先级反转问题。
而互斥量解决这种优先级反转的方法就是靠:优先级继承
- 当高优先级任务等待低优先级任务持有的互斥量时,系统会临时将低优先级任务的优先级提升到高优先级任务的级别。
- 这样,低优先级任务就可以优先运行,尽快完成对资源的访问并释放互斥量。
- 释放互斥量后,低优先级任务恢复原有的优先级,高优先级任务也能尽快获得资源并继续运行。
例子:打印机资源的调度:
- 任务L(低优先级):正在使用共享打印机打印一份报告,并持有打印机的互斥量。
- 任务H(高优先级):突然需要打印一份紧急文档,但发现打印机正在被任务L使用,因此必须等待。
- 任务M(中优先级):正在运行一项与打印机无关的后台数据处理任务,占用了大量CPU时间。
如果没有互斥量的优先级继承,任务L可能因为任务M不断抢占而长时间无法完成打印,从而让任务H也得不到及时的打印服务(优先级反转现象)。
采用互斥量后,当任务H等待打印机时,系统将自动将任务L的优先级提升,使任务L抢占任务M,迅速完成打印工作并释放互斥量。这样,任务H能更快地获得打印机资源,避免了优先级反转。
- 互斥量就是特殊的队列
- 互斥量更是特殊的信号量
- 互斥量实现了优先级继承
5.递归锁
相对比于普通的互斥量,递归锁内部就实现了谁持有谁释放的功能
5.1 死锁
日常生活的死锁:我们只招有工作经验的人!我没有工作经验怎么办?那你就去找工作啊!
假设有2个互斥量M1、M2,2个任务A、B:
- A获得了互斥量M1
- B获得了互斥量M2
- A还要获得互斥量M2才能运行,结果A阻塞
- B还要获得互斥量M1才能运行,结果B阻塞
- A、B都阻塞,再无法释放它们持有的互斥量死锁发生!
假设这样的场景:
- 任务A获得了互斥锁M
- 它调用一个库函数
- 库函数要去获取同一个互斥锁M,于是它阻塞:任务A休眠,等待任务A来释放互斥锁!
- 死锁发生!
怎么解决这类问题?可以使用递归锁(Recursive Mutexes),它的特性如下:
- 任务A获得递归锁M后,它还可以多次去获得这个锁
- "take"了N次,要"give"N次,这个锁才会被释放
5.2 概念
递归上锁解锁问题指的是当同一任务在已经获得某个互斥资源后,再次请求该资源时,如果使用的是非递归(普通)互斥量,就会导致死锁或阻塞。互斥量的递归特性允许同一任务对同一互斥量进行多次上锁,并在对应次数下释放后,互斥量才真正可供其他任务使用。
5.2.1 问题
假设有一个全局资源需要保护,比如一个共享变量或者一个外设。某个任务可能会调用多个函数,这些函数在执行过程中都需要访问该资源,并且各自会尝试获取互斥量。如果使用普通互斥量,第一次调用时任务成功获得互斥量,但当同一任务在还持有锁的情况下再调用一个需要上锁的函数时,第二次获取锁就会阻塞自己,造成死锁,任务永远等待自己释放资源。
- 任务A持有互斥量后,调用函数
Func1()
,而Func1()
又调用函数Func2()
。假设Func2()
也尝试获取同一个互斥量,如果使用的是非递归互斥量,任务A在Func2()
中将永远阻塞,因为它已经拥有该互斥量,却无法再次获得,从而导致死锁。 - **问题:**同一任务需要在多个层级调用中都保护共享资源,但如果锁不支持递归,第二次尝试上锁会使得任务自己被阻塞,整个系统无法正常运行。
5.2.2 解决方案:递归互斥量
递归互斥量的设计允许同一任务多次获取同一互斥量,而不会发生死锁。它内部会记录:
- 哪个任务当前拥有互斥量。
- 当前被该任务"嵌套"上锁的次数(递归计数)。
- 第一次上锁:
- 任务请求互斥量,成功获得后,记录该任务为拥有者,计数器设为1。
- 递归上锁:
- 当同一任务再次请求该互斥量时,检测到请求者与拥有者相同,于是直接增加计数器(例如从1增加到2),返回成功,不会阻塞任务。
- 递归解锁:
- 每次调用解锁函数,计数器减1。只有当计数器降为0时,互斥量才真正释放,使得其他任务可以获得该资源。
同一任务可以在多个层级安全地调用受保护的函数,而不必担心死锁问题。
5.2.3 示例
假设使用 FreeRTOS 提供的递归互斥量接口(在 FreeRTOS 中,递归互斥量需要用 xSemaphoreCreateRecursiveMutex
来创建,并使用 xSemaphoreTakeRecursive
和 xSemaphoreGiveRecursive
来操作)。
伪代码:
c
// 创建递归互斥量
SemaphoreHandle_t xRecursiveMutex = xSemaphoreCreateRecursiveMutex();
void Func2(void)
{
// Func2 需要访问共享资源,尝试上锁
if(xSemaphoreTakeRecursive(xRecursiveMutex, portMAX_DELAY) == pdTRUE)
{
// 访问共享资源
// ...
// 解锁
xSemaphoreGiveRecursive(xRecursiveMutex);
}
}
void Func1(void)
{
// Func1 需要访问共享资源,也上锁
if(xSemaphoreTakeRecursive(xRecursiveMutex, portMAX_DELAY) == pdTRUE)
{
// 执行一些操作...
Func2(); // 调用内部函数,Func2也会对同一个互斥量上锁
// 更多操作...
// 最后解锁
xSemaphoreGiveRecursive(xRecursiveMutex);
}
}
void TaskA(void *pvParameters)
{
while(1)
{
// 当任务A调用Func1时,可以安全嵌套调用Func2,
// 因为递归互斥量允许同一任务多次上锁
Func1();
vTaskDelay(100);
}
}
- 当任务A调用
Func1()
时,第一次调用xSemaphoreTakeRecursive
成功,持有互斥量且计数器为1。 Func1()
内部调用Func2()
,Func2()
再次调用xSemaphoreTakeRecursive
。由于当前任务A已持有互斥量,因此互斥量允许递归上锁,计数器增至2,任务继续执行。Func2()
完成后调用xSemaphoreGiveRecursive
,计数器减1(变为1),控制权返回到Func1()
。Func1()
执行完毕后,再调用xSemaphoreGiveRecursive
,计数器变为0,互斥量真正释放,其他任务可以获得该锁。
5.3 函数
c
/* 此函数内部会分配互斥量结构体
* 返回值: 返回句柄,非NULL表示成功*/
SemaphoreHandle_t xSemaphoreCreateRecursiveMutex( void );
/* 释放 */
BaseType_t xSemaphoreGiveRecursive( SemaphoreHandle_t xSemaphore );
/* 获得 */
BaseType_t xSemaphoreTakeRecursive(
SemaphoreHandle_t xSemaphore,TickType_t xTicksToWait
);
名字不一样,其它的其实和普通互斥量差不多
6.内部机制
6.1 创建
先来看下创建xSemaphoreCreateMutex
:
c
#if ( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
#define xSemaphoreCreateMutex() xQueueCreateMutex( queueQUEUE_TYPE_MUTEX )
#endif
QueueHandle_t xQueueCreateMutex( const uint8_t ucQueueType )
{
QueueHandle_t xNewQueue;
const UBaseType_t uxMutexLength = ( UBaseType_t ) 1, uxMutexSize = ( UBaseType_t ) 0;
xNewQueue = xQueueGenericCreate( uxMutexLength, uxMutexSize, ucQueueType );
prvInitialiseMutex( ( Queue_t * ) xNewQueue );
return xNewQueue;
}
也没啥好说的,互斥量就是特殊的队列,和信号量一样只是创建队列头Queue_t,并没有实际存储数据的队列buff
主要是初始化队列头有一些不同,进入prvInitialiseMutex
继续看:
c
static void prvInitialiseMutex( Queue_t * pxNewQueue )
{
if( pxNewQueue != NULL )
{
/* The queue create function will set all the queue structure members
* correctly for a generic queue, but this function is creating a
* mutex. Overwrite those members that need to be set differently -
* in particular the information required for priority inheritance. */
pxNewQueue->u.xSemaphore.xMutexHolder = NULL;
pxNewQueue->uxQueueType = queueQUEUE_IS_MUTEX;
/* In case this is a recursive mutex. */
pxNewQueue->u.xSemaphore.uxRecursiveCallCount = 0;
traceCREATE_MUTEX( pxNewQueue );
/* Start with the semaphore in the expected state. */
( void ) xQueueGenericSend( pxNewQueue, NULL, ( TickType_t ) 0U, queueSEND_TO_BACK );
}
else
{
traceCREATE_MUTEX_FAILED();
}
}
重点关注pxNewQueue->uxQueueType = queueQUEUE_IS_MUTEX;
,这个后续分析优先级继承的地方是个关键
6.2 take
take操作的函数实际上就是信号量的操作函数xSemaphoreTake
:
c
#define xSemaphoreTake( xSemaphore, xBlockTime ) xQueueSemaphoreTake( ( xSemaphore ), ( xBlockTime ) )
这里只摘除关键部分,其它具体的去看之前队列和信号量的章节
c
BaseType_t xQueueSemaphoreTake( QueueHandle_t xQueue,
TickType_t xTicksToWait )
{
BaseType_t xEntryTimeSet = pdFALSE;
TimeOut_t xTimeOut;
Queue_t * const pxQueue = xQueue;
//省略
for( ; ; )
{
//省略
/* 检查超时状态,看是否已超时 */
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();
{
xInheritanceOccurred = xTaskPriorityInherit( pxQueue->u.xSemaphore.xMutexHolder );
}
taskEXIT_CRITICAL();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_MUTEXES */
/* 将当前任务放入等待接收(获取信号量)的事件列表,并指定等待时间 */
vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToReceive ), xTicksToWait );
prvUnlockQueue( pxQueue ); /* 解锁队列 */
/* 恢复任务调度,如果有更高优先级任务需要运行,则进行上下文切换 */
if( xTaskResumeAll() == pdFALSE )
{
portYIELD_WITHIN_API();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}else
{
/* 如果等待超时 */
prvUnlockQueue( pxQueue );
( void ) xTaskResumeAll();
/* 如果此时信号量仍不可用,则返回超时错误 */
if( prvIsQueueEmpty( pxQueue ) != pdFALSE )
{
#if ( configUSE_MUTEXES == 1 )
{
/* 对于互斥量,如果曾经发生过优先级继承,
* 则需要执行优先级回退(撤销继承) */
if( xInheritanceOccurred != pdFALSE )
{
taskENTER_CRITICAL();
{
UBaseType_t uxHighestWaitingPriority;
/* 获取等待该互斥量的任务中最高的优先级,
* 并据此调整当前任务的优先级 */
uxHighestWaitingPriority = prvGetDisinheritPriorityAfterTimeout( pxQueue );
vTaskPriorityDisinheritAfterTimeout( pxQueue->u.xSemaphore.xMutexHolder, uxHighestWaitingPriority );
}
taskEXIT_CRITICAL();
}
}
#endif /* configUSE_MUTEXES */
traceQUEUE_RECEIVE_FAILED( pxQueue );
return errQUEUE_EMPTY;
}
//省略
}
}

pxQueue->uxQueueType = queueQUEUE_IS_MUTEX
就是之前创建的时候初始化的,条件成立,实现优先级继承
来继续看一下这个优先级继承函数: xInheritanceOccurred = xTaskPriorityInherit( pxQueue->u.xSemaphore.xMutexHolder );
c
BaseType_t xTaskPriorityInherit( TaskHandle_t const pxMutexHolder )
{
/* 将传入的互斥量持有者句柄转换为任务控制块指针 */
TCB_t * const pxMutexHolderTCB = pxMutexHolder;
BaseType_t xReturn = pdFALSE; /* 用于返回是否发生了优先级继承 */
/* 检查互斥量持有者是否为 NULL。
* 这通常用于防止在队列锁定期间,由于中断释放互斥量而导致持有者为空。
* _RB_ 注:随着中断不再使用互斥量,这个检查是否仍然必要值得讨论。 */
if( pxMutexHolder != NULL )
{
/* 如果互斥量当前持有者的优先级低于试图获得该互斥量的任务(当前任务)的优先级,
* 则需要进行优先级继承,提升持有者的优先级,防止优先级反转。 */
if( pxMutexHolderTCB->uxPriority < pxCurrentTCB->uxPriority )
{
/* 调整互斥量持有者的状态:
* 检查其事件列表项的值是否正在被其他用途占用。
* 如果没有被使用(即标志位未置位),则更新该值,
* 以反映新的优先级(使用 configMAX_PRIORITIES - 新优先级 的方式存储)。
* 这种方式便于在就绪列表中按数值排序,较低的数值代表较高优先级。 */
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 )
{
/* 将该任务从当前就绪列表中移除,并检查移除后是否列表长度为0,
* 如果为0,则直接调用端口层的宏重置最高就绪优先级。 */
if( uxListRemove( &( pxMutexHolderTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
{
portRESET_READY_PRIORITY( pxMutexHolderTCB->uxPriority, uxTopReadyPriority );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* 在重新插入前,继承优先级:将互斥量持有者的优先级更新为当前任务的优先级 */
pxMutexHolderTCB->uxPriority = pxCurrentTCB->uxPriority;
/* 将更新了优先级的任务重新加入到就绪列表中 */
prvAddTaskToReadyList( pxMutexHolderTCB );
}
else
{
/* 如果任务不在就绪列表中(例如,它可能在阻塞状态),
* 直接更新其优先级即可 */
pxMutexHolderTCB->uxPriority = pxCurrentTCB->uxPriority;
}
/* 记录优先级继承事件,便于调试和跟踪 */
traceTASK_PRIORITY_INHERIT( pxMutexHolderTCB, pxCurrentTCB->uxPriority );
/* 标记发生了优先级继承 */
xReturn = pdTRUE;
}
else
{
/* 如果互斥量持有者的当前优先级不低于当前任务,
* 但它的基础优先级(uxBasePriority)低于当前任务,
* 则说明它之前已经继承过较高优先级,
* 所以也认为发生了继承 */
if( pxMutexHolderTCB->uxBasePriority < pxCurrentTCB->uxPriority )
{
xReturn = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
else
{
mtCOVERAGE_TEST_MARKER(); /* 如果传入的互斥量持有者为 NULL,则不做任何操作 */
}
/* 返回是否发生了优先级继承 */
return xReturn;
}
既然有优先级继承,那么肯定也有恢复


具体的实现代码也在上面有放出,这里截图出来以便观察。其中vTaskPriorityDisinheritAfterTimeout
就是用于恢复当前被提升了优先级的任务的原本优先级:
c
void vTaskPriorityDisinheritAfterTimeout( TaskHandle_t const pxMutexHolder,
UBaseType_t uxHighestPriorityWaitingTask )
{
/* 将传入的互斥量持有者句柄转换为任务控制块指针 */
TCB_t * const pxTCB = pxMutexHolder;
UBaseType_t uxPriorityUsedOnEntry, uxPriorityToUse;
/* 定义一个常量,表示持有的互斥量数量为 1 的情况 */
const UBaseType_t uxOnlyOneMutexHeld = ( UBaseType_t ) 1;
/* 如果互斥量持有者不为 NULL */
if( pxMutexHolder != NULL )
{
/* 如果互斥量持有者不为 NULL,则该任务至少持有一个互斥量。
* 这里通过断言确保 uxMutexesHeld 不为 0。 */
configASSERT( pxTCB->uxMutexesHeld );
/* 计算恢复的目标优先级:
* 目标优先级应为持有者任务的基本优先级 (uxBasePriority) 与所有等待该互斥量的任务中最高优先级的较大者。
* 这样可以保证即使撤销继承,也不会低于等待者的最高优先级。 */
if( pxTCB->uxBasePriority < uxHighestPriorityWaitingTask )
{
uxPriorityToUse = uxHighestPriorityWaitingTask;
}
else
{
uxPriorityToUse = pxTCB->uxBasePriority;
}
/* 检查是否需要改变任务的当前优先级 */
if( pxTCB->uxPriority != uxPriorityToUse )
{
/* 只有在任务只持有一个互斥量时,才能撤销优先级继承。
* 如果任务同时持有多个互斥量,那么其他互斥量可能也引起了优先级继承,
* 这时不应单独撤销。 */
if( pxTCB->uxMutexesHeld == uxOnlyOneMutexHeld )
{
/* 如果一个任务因为等待自己已持有的互斥量而超时,则它不可能继承自己的优先级。
* 断言当前任务(试图获取互斥量的任务)不应与持有互斥量的任务相同。 */
configASSERT( pxTCB != pxCurrentTCB );
/* 撤销继承操作:
* 1. 调用跟踪宏记录撤销继承事件,同时保存任务进入此函数时的当前优先级。
* 2. 将任务的当前优先级设置为目标优先级,即 uxPriorityToUse。 */
traceTASK_PRIORITY_DISINHERIT( pxTCB, uxPriorityToUse );
uxPriorityUsedOnEntry = pxTCB->uxPriority;
pxTCB->uxPriority = uxPriorityToUse;
/* 如果任务的事件列表项当前没有被其他用途使用,则更新该项的值。
* 更新的值采用 (configMAX_PRIORITIES - uxPriorityToUse) 形式,便于就绪列表中排序。 */
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 ) uxPriorityToUse );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* 任务可能处于 Ready、Blocked 或 Suspended 状态。
* 只有当任务处于 Ready 状态(即在就绪列表中)时,
* 才需要将其从当前的就绪列表中移除,然后重新插入到对应新优先级的就绪列表中。
* 这可以确保任务根据新优先级正确排序。 */
if( listIS_CONTAINED_WITHIN( &( pxReadyTasksLists[ uxPriorityUsedOnEntry ] ),
&( pxTCB->xStateListItem ) ) != pdFALSE )
{
/* 从就绪列表中移除任务。uxListRemove 返回 0 表示就绪列表中没有更多该优先级的任务,
* 此时调用 portRESET_READY_PRIORITY 重置最高就绪优先级。 */
if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
{
portRESET_READY_PRIORITY( pxTCB->uxPriority, uxTopReadyPriority );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* 重新将任务加入到就绪列表中,按照新优先级排序 */
prvAddTaskToReadyList( pxTCB );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
/* 如果任务持有多个互斥量,则不进行优先级撤销(撤销继承),因为其他互斥量可能已引起继承 */
mtCOVERAGE_TEST_MARKER();
}
}
else
{
/* 如果任务的当前优先级已经等于目标优先级,则不需要改变 */
mtCOVERAGE_TEST_MARKER();
}
}
else
{
/* 如果传入的互斥量持有者为 NULL,则不执行任何操作 */
mtCOVERAGE_TEST_MARKER();
}
}
代码做了很详细的注释了,这里也不赘述了