当前位置: 首页 > news >正文

FreeRTOS互斥量

目录

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

img

  • 量:值为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 中定义:

#define configUSE_MUTEXES 1

2.1.1 动态创建

SemaphoreHandle_t xSemaphoreCreateMutex(void);
  • 此函数内部会调用内存分配函数,为互斥量分配存储结构。
  • 创建成功后返回一个非 NULL 的句柄,否则返回 NULL。
  • 互斥量的初始状态为“未占用”(相当于值为 1),但内部还会跟踪哪个任务获得了锁。

2.1.2 静态创建

SemaphoreHandle_t xSemaphoreCreateMutexStatic(StaticSemaphore_t *pxMutexBuffer);
  • 用户必须预先定义一个 StaticSemaphore_t 类型的变量,用来存储互斥量的结构。
  • 此函数不会动态分配内存,而是利用用户提供的内存来构建互斥量。
  • 返回非 NULL 的句柄表示成功。

2.2 删除

void vSemaphoreDelete(SemaphoreHandle_t xSemaphore);
  • 用于删除互斥量并释放动态分配的内存(对于动态创建的互斥量)。
  • 静态创建的互斥量,由用户管理其内存,所以调用删除函数后不释放内存。

2.3 释放(Give)

BaseType_t xSemaphoreGive(SemaphoreHandle_t xSemaphore);
  • 该函数用于释放一个已获得的互斥量,即将互斥量“归还”给系统。
  • 内部会检查调用任务是否为当前持有互斥量的任务(互斥量拥有所有权),确保只有拥有者才能释放锁。
  • 如果释放成功返回 pdTRUE,否则返回错误(例如,如果互斥量已经处于未被占用状态)。

互斥量设计为只允许获得它的任务释放它,以防止出现多个任务错误地释放互斥量。

2.4 获取(Take)

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.常规使用流程

  1. 创建互斥量:
    使用 xSemaphoreCreateMutex()xSemaphoreCreateMutexStatic() 得到互斥量句柄。
  2. 获得互斥量:
    在访问共享资源(临界区)之前,任务调用 xSemaphoreTake(),并在必要时阻塞等待,直到获得互斥量。
  3. 访问临界资源:
    一旦获得互斥量,任务即可安全访问共享资源,执行读/修改/写等操作。
  4. 释放互斥量:
    操作完成后,任务调用 xSemaphoreGive() 释放互斥量,使得其他等待该资源的任务能继续执行。
  5. 删除互斥量:
    如果互斥量不再需要(通常在任务结束或系统关闭时),可以调用 vSemaphoreDelete() 删除动态分配的互斥量。

4.和二进制信号量的对比

一开始就提到了,FreeRTOS的互斥量并没有实现“谁上锁,就由谁解锁”,只能靠程序员的自觉了。

那这样互斥量和二进制信号量岂不是一样??并不是

  • 互斥量能解决优先级反转的问题
  • 能解决递归上锁解锁的问题 ---- 递归锁,特殊的互斥量

什么是优先级反转?举个栗子:

设系统中有三个任务:

  • 任务L(Low Priority):低优先级任务
  • 任务M(Medium Priority):中优先级任务
  • 任务H(High Priority):高优先级任务
  1. 任务L首先获得了一个共享资源的互斥锁(比如访问串口或共享变量)。
  2. 任务H随后因某种原因被触发,需要访问该共享资源,但由于任务L还在使用该资源,任务H必须等待。
  3. 此时,任务M开始运行。任务M虽然不需要共享资源,但由于其优先级介于任务H和任务L之间,它可以抢占任务L的执行。
  4. 任务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。
  1. 递归上锁:
  • 当同一任务再次请求该互斥量时,检测到请求者与拥有者相同,于是直接增加计数器(例如从1增加到2),返回成功,不会阻塞任务。
  1. 递归解锁:
  • 每次调用解锁函数,计数器减1。只有当计数器降为0时,互斥量才真正释放,使得其他任务可以获得该资源。

同一任务可以在多个层级安全地调用受保护的函数,而不必担心死锁问题。


5.2.3 示例

假设使用 FreeRTOS 提供的递归互斥量接口(在 FreeRTOS 中,递归互斥量需要用 xSemaphoreCreateRecursiveMutex 来创建,并使用 xSemaphoreTakeRecursivexSemaphoreGiveRecursive 来操作)。

伪代码:

// 创建递归互斥量
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 函数

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

名字不一样,其它的其实和普通互斥量差不多

6.内部机制

6.1 创建

先来看下创建xSemaphoreCreateMutex

#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继续看:

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

#define xSemaphoreTake( xSemaphore, xBlockTime )    xQueueSemaphoreTake( ( xSemaphore ), ( xBlockTime ) )

这里只摘除关键部分,其它具体的去看之前队列和信号量的章节

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;}//省略}}

img

pxQueue->uxQueueType = queueQUEUE_IS_MUTEX就是之前创建的时候初始化的,条件成立,实现优先级继承

来继续看一下这个优先级继承函数: xInheritanceOccurred = xTaskPriorityInherit( pxQueue->u.xSemaphore.xMutexHolder );

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;
}

既然有优先级继承,那么肯定也有恢复

img

img
具体的实现代码也在上面有放出,这里截图出来以便观察。其中vTaskPriorityDisinheritAfterTimeout就是用于恢复当前被提升了优先级的任务的原本优先级:

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();}
}

代码做了很详细的注释了,这里也不赘述了

相关文章:

  • Docker部署minio
  • 归并排序详解:优雅的分治艺术
  • 31.Python编程实战:自动化批量压缩与解压文件
  • 地址解析协议(ARP):交换机中的深度解析与实战指南
  • 扁平风格职场商务通用PPT模版分享
  • 企业级RAG系统架构设计与实现指南(基于Java技术栈)
  • 【技巧】win10和ubuntu互相挂在共享文件夹
  • Python爬虫实战:研究cssselect相关技术
  • Master PDF Editor:全能PDF编辑工具
  • 医院重症监护系统 ICU重症病房管理系统 重症监护软件
  • 根据 LiDAR 株高数据计算植被生物量
  • DeviceNet转Modbus RTU网关在玻璃制造中的关键应用
  • QT第一课 —— 设置CMake路径
  • 大语言模型的局限性与RAG基本框架和工作流实例
  • Oracle中10个索引优化
  • 【MySQL数据库 | 第三篇】DDL数据库操作
  • SCADA|信创KingSCADA4.0与KingSCADA3.8的几点不同
  • 高效文献阅读翻译软件推荐:精准翻译,助力科研提速
  • 51LA数据分析遇瓶颈?免费统计工具——悟空统计
  • python基本语法元素
  • 软件开发学院/百度搜索引擎seo
  • 廊坊网站建设价格/网络推广工作内容怎么写
  • 帮助做APP的网站公司/国内搜索引擎大全
  • 重庆专业平台推广公司/路由优化大师
  • metinfo怎么做网站/百度问问
  • jsq项目做网站/晚上国网app