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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/diannao/86646.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ThinkPad 交换 Ctrl 键和 Fn 键

概述 不知道那个大聪明设计的将fn设置在最左边&#xff0c;xxx&#xff0c;我服了&#xff0c;你这个老六真恶心。 方法 一&#xff1a;BIOS/UEFI 设置&#xff08;推荐&#xff09; 重启 你的 ThinkPad。 在启动时按下 F1&#xff08;或 Enter&#xff0c;再按 F1&#xff0…

`dispatch_source_t` 计时器 vs `NSTimer`:核心差异一览

维度GCD 计时器 (dispatch_source_t)NSTimer依赖机制直接挂在 GCD 队列;底层走 Mach 内核定时源挂在 RunLoop,必须指定 RunLoop & mode线程上下文哪个队列就在哪条线程回调(例中用 dispatch_get_main_queue())总在定时器所在的 RunLoop 线程(默认主线程 & NSDefau…

ubuntu22.04系统安装部署docker和docker compose全过程!

更新系统包 首先&#xff0c;确保系统包是最新的&#xff1a; sudo apt updatesudo apt upgrade -y安装依赖 安装 Docker 所需的依赖包&#xff1a; sudo apt install -y apt-transport-https ca-certificates curl software-properties-common添加 Docker 官方 GPG 密钥 添加…

企业如何增强终端安全?

在数字化转型加速的今天&#xff0c;企业的业务运行越来越依赖于终端设备。从员工的笔记本电脑、智能手机&#xff0c;到工厂里的物联网设备、智能传感器&#xff0c;这些终端构成了企业与外部世界连接的 “神经末梢”。然而&#xff0c;随着远程办公的常态化和设备接入的爆炸式…

VS2017----打开ui文件几秒后闪退

问题描述 在vs2017中双击ui文件能够打开,但是几秒后就闪退了,提示报错 问题解决 QT VS tools ----Options,把这个设置为True保存即可

深入解析Docker网桥模式:从docker0到容器网络的完整通信链路

1. 简介docker 网桥模式 Docker 启动时默认创建 docker0 虚拟网桥&#xff08;Linux bridge&#xff09;&#xff0c;并分配私有 IP 地址范围&#xff08;如 172.17.42.1/16&#xff09;&#xff0c;它的作用相当于一个虚拟交换机&#xff0c;让宿主机和多个容器之间可以通信。…

Proof of Talk专访CertiK联创顾荣辉:全周期安全方案护航Web3生态

6月10日&#xff0c;CertiK联合创始人兼CEO顾荣辉在Proof of Talk 2025举办期间&#xff0c;接受大会官方专访&#xff0c;分享了他对Web3安全现状的观察以及CertiK的安全战略布局。 顾荣辉指出&#xff0c;虽然安全的重要性被广泛认可&#xff0c;但许多创业者和开发者仍存在…

再说一说LangChain Runnable接口

之前我们介绍过LangChain通过Runnable和LCEL来实现各个组件的快捷拼装&#xff0c;整个过程就像拼积木一样。 今天我们深入剖析Runnable接口的底层实现逻辑。 往期文章推荐: 16.Docker实战&#xff1a;5分钟搞定MySQL容器化部署与最佳实践15.Ollama模板全解析&#xff1a;从基…

LLaMA-Factory微调Qwen3模型完了,怎么直接用vllm推理模型?

环境&#xff1a; LLaMA-Factory vllm0.8.5 Qwen3-8b 问题描述&#xff1a; LLaMA-Factory微调Qwen3模型完了,怎么直接用vllm推理模型&#xff1f; 解决方案&#xff1a; 一、合并 LoRA 权重与基础模型 vLLM 需要完整的模型文件&#xff08;含合并后的权重&#xff09;…

C#AES加密

一、AES 加密概念 定义 &#xff1a;AES&#xff08;Advanced Encryption Standard&#xff0c;高级加密标准&#xff09;是一种对称加密算法&#xff0c;由美国国家标准与技术研究院&#xff08;NIST&#xff09;于 2001 年发布&#xff0c;用于替代之前的 DES&#xff08;数据…

搞了两天的win7批处理脚本问题

目录 问题 原因&#xff1a; 经过各种对比 解决方法 问题 比如 echo "yes" | find /c /v "" 这个统计非空串的行数&#xff0c;在其它系统都是 1&#xff1b;但在win7里非正常的反应&#xff0c;为空。 原因&#xff1a; 在wvpCheckStart.bat 首…

手阳明大肠经之温溜穴

个人主页&#xff1a;云纳星辰怀自在 座右铭&#xff1a;“所谓坚持&#xff0c;就是觉得还有希望&#xff01;” 温溜又名&#xff1a; 1.《针灸甲乙经》&#xff1a;逆注、蛇头。 2.《资生》&#xff1a;池头。 所属经络&#xff1a;手阳明大肠经 定位 屈肘&#xff0c;在前臂…

传统Web应用和RESTful API模式

传统Web应用和RESTful API 传统模式 传统模式没有实现前后端分离&#xff0c;服务器处理完业务后直接返回完整的HTML页面&#xff0c;每次操作都要刷新整个页面。类似下面的情况&#xff1a; Controller public class UserController {RequestMapping("/addUser")…

JS开发node包并发布流程

开发一个可发布到 npm 的 JavaScript 插件&#xff0c;需要遵循标准的开发、测试、打包和发布流程。以下是详细步骤指南&#xff1a; 1. 初始化项目 创建项目目录并初始化 package.json mkdir my-js-plugin cd my-js-plugin npm init -y手动修改 package.json&#xff0c;确保…

对比学习(Contrastive Learning)方法详解

对比学习&#xff08;Contrastive Learning&#xff09;方法详解 对比学习&#xff08;Contrastive Learning&#xff09;是一种强大的自监督或弱监督表示学习方法&#xff0c;其核心思想是学习一个嵌入空间&#xff0c;在这个空间中&#xff0c;相似的样本&#xff08;“正样…

1.6 http模块nodejs 对比 go

我们以go语言 原生实现 和浏览器交互.到现在学习 nodejs http模块. nodejs 对于请求分发,也需要我们自己处理. 我们应该也对 http 服务是建立在 tcp协议基础上.有更深入的体会了吧. 对于我们之后 学习 java web容器. 能有更深入的认知. 请求分发 请求分发是指 Web 框架或服务器…

护照阅读器在景区的应用

护照阅读器在景区的应用可以显著提升游客管理效率、增强安全性并优化游客体验。以下是其主要应用场景、优势及实施建议&#xff1a; 一、核心应用场景 快速入园核验 自动身份识别&#xff1a;通过扫描护照芯片&#xff08;MRZ码或NFC读取&#xff09;&#xff0c;1-3秒完成身份…

Prompt Tuning、P-Tuning、Prefix Tuning的区别

一、Prompt Tuning、P-Tuning、Prefix Tuning的区别 1. Prompt Tuning(提示调优) 核心思想:固定预训练模型参数,仅学习额外的连续提示向量(通常是嵌入层的一部分)。实现方式:在输入文本前添加可训练的连续向量(软提示),模型只更新这些提示参数。优势:参数量少(仅提…

什么是遥测数据?

遥测数据定义 遥测数据提供了关于系统性能的重要洞察&#xff0c;对主动解决问题和做出明智决策至关重要。要实现这一点&#xff0c;不能只依赖原始数据 —— 你需要实时的洞察&#xff0c;而这正是遥测数据提供的。 遥测是从远程来源&#xff08;如服务器、应用程序和监控设…

【JavaAPI搜索引擎】项目测试报告

JavaAPI搜索引擎测试报告 项目背景与项目介绍项目功能自动化测试单元测试测试ansj分词器测试能否获取到正确的URL测试能否正确解析文件中的正文 测试计划界面测试测试1 页面布局是否合理美观&#xff0c;元素是否正确显示测试2 测试是否可以正常显示出搜索结果测试3 点击搜索结…