ARTICLE · 1006167
FreeRTOS 任务优先级机制源码剖析
平台:STM32F103RET6(Cortex-M3)内核:FreeRTOS V11.1.0(RVDS/ARM_CM3 移植层,SMP-capable 新版 tasks.c,本工程按单核编译)本文所有代码均取自本工程实际源码
一、优先级是什么:一个反直觉的事实
FreeRTOS 任务优先级数值越大,优先级越高——和 STM32 的 NVIC 中断优先级正好相反(NVIC 数值越小越优先)。写过裸机中断的人第一次用 FreeRTOS,十个里有五个在这里栽跟头。
/* 本工程 FreeRTOSConfig.h */#define configMAX_PRIORITIES 5 /* 合法范围 0 ~ 4 */
优先级 0 保留给空闲任务( tskIDLE_PRIORITY),应用任务别用 0;本工程 configMAX_PRIORITIES=5,传 5 及以上的优先级:本工程开了 assert,会先触发configASSERT死机(创建路径与vTaskPrioritySet的断言都在钳位之前,见第四节要点 4);软件定时器服务任务占 configMAX_PRIORITIES-1 = 4(本工程配置),意味着它比一切应用任务都高——定时器回调里干重活会饿死你的应用。
任务优先级与中断优先级是两套独立体系:中断永远抢占任务,再高的任务优先级也压不过 NVIC 数值更小的中断。任务优先级只决定"CPU 在任务之间给谁"。
1.1 configMAX_PRIORITIES 可以填多大?
硬上限 32。这个宏本质是就绪链表数组的大小:
PRIVILEGED_DATA static List_t pxReadyTasksLists[ configMAX_PRIORITIES ];上限的根源在 configUSE_PORT_OPTIMISED_TASK_SELECTION=1 的优化实现:CM3 移植层用一个 32 位变量当位图,portGET_HIGHEST_PRIORITY 靠 CLZ(前导零计数)指令选任务——位图只有 32 位,所以 FreeRTOS 头文件里有静态断言 configMAX_PRIORITIES ≤ 32。
本工程 configUSE_PORT_OPTIMISED_TASK_SELECTION=0,连位图都不用,理论上填多大都行,代价只是 RAM(每个 List_t 在 32 位平台约 20 字节):
uxTopReadyPriority(UBaseType_t,32 位)放得下任何 ≤32 的值,xEventListItem 里存的补数 configMAX_PRIORITIES - priority 是 TickType_t(32 位)也不会溢出。改成 32 以内任意值只需重编译,不用动其他代码。但要注意三点:
- 定时器任务优先级跟着变
—— configTIMER_TASK_PRIORITY = configMAX_PRIORITIES - 1,从 4 变 14 后依然是最高,相对关系不变,但排查问题时要记得这个数变了; - 优先级是"序"不是"档"
——建议留空档(如 1/3/5/7),将来插中间优先级的任务不用全盘重排; - 填满 ≠ 用满
——15 个任务挤 15 个不同优先级意味着完全无同级轮转(本工程时间片还关着),任何阻塞时机变化都会放大调度顺序的敏感度。
二、优先级存储在哪里
2.1 TCB 里的三个字段
// task.c#if ( configUSE_MUTEXES == 1 )UBaseType_t uxBasePriority; /* 创建/设置时的"本名"优先级——继承机制的基准 */UBaseType_t uxMutexesHeld; /* 当前持有的互斥量计数——决定何时归还继承的优先级 */#endifvolatile UBaseType_t uxPriority; /* 当前生效的优先级——调度器实际看这个 */
为什么要有两个优先级?优先级继承:低任务持有互斥量时会被临时抬到高任务的优先级,但"抬完还得还"——uxBasePriority 就是还的依据,uxPriority 是实时值。两者相等 = 没有继承发生。
2.2 就绪链表数组:pxReadyTasksLists[]
// task.c/* 每个优先级一条双向链表,同优先级任务挂在同一条上(尾插) */PRIVILEGED_DATA static List_t pxReadyTasksLists[ configMAX_PRIORITIES ];/* "当前最高就绪优先级"缓存——调度的起点 */PRIVILEGED_DATA static volatile UBaseType_t uxTopReadyPriority = tskIDLE_PRIORITY;
存储布局:优先级是数组下标,同优先级任务在一条链表上轮转。查找最高优先级任务 = 从 uxTopReadyPriority 往下找第一条非空链表,O(1) 起步、最坏 O(configMAX_PRIORITIES)。
2.3 事件链表项里存的"补数"
任务阻塞在队列/信号量上时,xEventListItem 的排序值存的不是优先级本身,而是补数:
// task.c/* 值越小 = 优先级越高 → 链表升序插入后,高优先级任务排在前面 */listSET_LIST_ITEM_VALUE( &( pxNewTCB->xEventListItem ),configMAX_PRIORITIES - uxPriority );
这样事件到来时 vTaskRemoveFromUnorderedEventList 按序值升序唤醒,优先级高的阻塞任务先被救活。
三、调度如何用优先级:三段核心代码
3.1 入队时记录最高优先级:taskRECORD_READY_PRIORITY
本工程 configUSE_PORT_OPTIMISED_TASK_SELECTION=0:
// task.c#define taskRECORD_READY_PRIORITY( uxPriority ) \do { \if( ( uxPriority ) > uxTopReadyPriority ) \{ \uxTopReadyPriority = ( uxPriority ); \} \} while( 0 )
只升不降——所以它是个"缓存上界",真实最高就绪优先级可能比它低(任务被删/阻塞后不清零),查找时靠向下扫描兜底。
3.2 挂入就绪链表:prvAddTaskToReadyList
// task.c#define prvAddTaskToReadyList( pxTCB ) \do { \traceMOVED_TASK_TO_READY_STATE( pxTCB ); \taskRECORD_READY_PRIORITY( ( pxTCB )->uxPriority ); \listINSERT_END( &( pxReadyTasksLists[ ( pxTCB )->uxPriority ] ), &( ( pxTCB )->xStateListItem ) ); \} while ( 0 )
尾插(listINSERT_END)保证同优先级任务先来先轮转——这就是时间片轮询的链表基础。
3.3 选出该跑的任务:taskSELECT_HIGHEST_PRIORITY_TASK
// task.c#define taskSELECT_HIGHEST_PRIORITY_TASK() \do { \UBaseType_t uxTopPriority = uxTopReadyPriority; \/* 从缓存点向下扫,找到第一条非空就绪链表 */ \while( listLIST_IS_EMPTY( &( pxReadyTasksLists[ uxTopPriority ] ) ) != pdFALSE ) \{ \configASSERT( uxTopPriority ); \--uxTopPriority; \} \/* 同优先级轮转取下一个(游标式遍历链表) */ \listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, \&( pxReadyTasksLists[ uxTopPriority ] ) ); \uxTopReadyPriority = uxTopPriority; /* 回写缓存,下次从这里起步 */ \} while ( 0 )
listGET_OWNER_OF_NEXT_ENTRY 每次取"游标指向的下一个"并移动游标——同优先级任务由此均分 CPU。这段宏在 PendSV 的 vTaskSwitchContext 里执行,即每次上下文切换的心脏。
四、vTaskPrioritySet:运行时改优先级(tasks.c)
// task.cvoidvTaskPrioritySet( TaskHandle_t xTask, UBaseType_t uxNewPriority ){TCB_t * pxTCB;UBaseType_t uxCurrentBasePriority, uxPriorityUsedOnEntry;BaseType_t xYieldRequired = pdFALSE;configASSERT( uxNewPriority < configMAX_PRIORITIES );/* 软钳位:越界值压回最高合法优先级 */if( uxNewPriority >= ( UBaseType_t ) configMAX_PRIORITIES ){uxNewPriority = ( UBaseType_t ) configMAX_PRIORITIES - 1U;}taskENTER_CRITICAL();{pxTCB = prvGetTCBFromHandle( xTask ); /* NULL = 改自己 */#if ( configUSE_MUTEXES == 1 )uxCurrentBasePriority = pxTCB->uxBasePriority;#endifif( uxCurrentBasePriority != uxNewPriority ){if( uxNewPriority > uxCurrentBasePriority ){/* 抬别的任务且高过当前运行任务 → 需要让位 */if( pxTCB != pxCurrentTCB &&uxNewPriority > pxCurrentTCB->uxPriority ){xYieldRequired = pdTRUE;}/* 抬自己:本来就是最高,不用 yield */}else if( taskTASK_IS_RUNNING( pxTCB ) == pdTRUE ){/* 压低正在运行的自己 → 可能要让给别人 */xYieldRequired = pdTRUE;}uxPriorityUsedOnEntry = pxTCB->uxPriority;#if ( configUSE_MUTEXES == 1 ){/* 关键:正在持有互斥量(继承着别人的高优先级)时,只有新优先级更高才动 uxPriority;本名优先级无条件更新 */if( ( pxTCB->uxBasePriority == pxTCB->uxPriority ) ||( uxNewPriority > pxTCB->uxPriority ) ){pxTCB->uxPriority = uxNewPriority;}pxTCB->uxBasePriority = uxNewPriority;}#endif/* 事件链表排序值同步更新(补数) */listSET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ),( TickType_t ) configMAX_PRIORITIES - uxNewPriority );/* 就绪态 → 摘下来挂到新优先级的链表 */if( listIS_CONTAINED_WITHIN( &( pxReadyTasksLists[ uxPriorityUsedOnEntry ] ),&( pxTCB->xStateListItem ) ) != pdFALSE ){uxListRemove( &( pxTCB->xStateListItem ) );prvAddTaskToReadyList( pxTCB ); /* taskRECORD 更新缓存 */}/* 阻塞/挂起态 → 只改字段,醒来自然进新链表 */}}taskEXIT_CRITICAL();if( xYieldRequired != pdFALSE ){taskYIELD_TASK_CORE_IF_USING_PREEMPTION( pxTCB ); /* PendSV 立即切换 */}}
四个要点:
- 整个操作在临界区内
——改优先级就是改三处存储(TCB 字段、链表、缓存),必须原子; - 升别人 / 降自己才 yield
:升自己不用(已经是最高);降别人不用(反正压不过我); - 互斥量保护
:继承期间改本名优先级,只更新 uxBasePriority,不动uxPriority——防止把继承来的高优先级意外弄丢,否则持有者提前被压下去,优先级反转又会回来; 注意 configASSERT( uxNewPriority < configMAX_PRIORITIES )在钳位之前——本工程开了 assert,直接传越界值会断言死机,而不是被静默钳位(钳位是给关闭 assert 的工程的保险)。
五、优先级继承与返还:互斥量的灵魂
这正对应本工程 main.c 的优先级反转对照实验(时序图见 8.2,实验代码见 9.2)。
5.1 继承:xTaskPriorityInherit(tasks.c)
高任务 Take 互斥量失败、把持锁的低任务抬上来:
// task.cBaseType_t xTaskPriorityInherit( TaskHandle_t const pxMutexHolder ){TCB_t * const pxMutexHolderTCB = pxMutexHolder;BaseType_t xReturn = pdFALSE;/* 中断里不会持有互斥量,持有者必为任务 */if( pxMutexHolder != NULL ){/* 持有者比我低 → 它临时继承我的优先级 */if( pxMutexHolderTCB->uxPriority < pxCurrentTCB->uxPriority ){/* 就绪态:先从旧优先级链表摘除 */if( listIS_CONTAINED_WITHIN(&( pxReadyTasksLists[ pxMutexHolderTCB->uxPriority ] ),&( pxMutexHolderTCB->xStateListItem ) ) != pdFALSE ){uxListRemove( &( pxMutexHolderTCB->xStateListItem ) );/* 先继承,再挂新链表——顺序不能反 */pxMutexHolderTCB->uxPriority = pxCurrentTCB->uxPriority;prvAddTaskToReadyList( pxMutexHolderTCB );}else{/* 阻塞态:只改字段即可 */pxMutexHolderTCB->uxPriority = pxCurrentTCB->uxPriority;}xReturn = pdTRUE; /* 发生了继承 */}}return xReturn;}
注意:只动 uxPriority,绝不动 uxBasePriority——本名留着还。(节选省略了事件链表排序值同步与 trace 钩子,完整逻辑见源码。)
5.1.1 内核怎么知道"继承谁的优先级":两个身份的场景
一个常被问到的问题:继承发生时,内核怎么知道要把谁抬起来、又抬到哪个优先级(即继承谁的)?答案是调用点就在现场,两个身份都直接从当前上下文里读,不需要"查"也不需要"遍历"。
触发继承的调用点在 queue.c,是 xQueueSemaphoreTake() 里"互斥量已经被别人持有,当前任务即将进入阻塞态"之前的一刹那:
/* queue.c —— xQueueSemaphoreTake() 内部 */if( prvIsQueueEmpty( pxQueue ) != pdFALSE ) /* 量空 = 互斥量被人拿着 */{#if ( configUSE_MUTEXES == 1 ){if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX ){taskENTER_CRITICAL();{/* 传参:持有者身份(来自互斥量结构体,Give/Take 成功时写入)*/xInheritanceOccurred = xTaskPriorityInherit(pxQueue->u.xSemaphore.xMutexHolder);}taskEXIT_CRITICAL();}}#endif/* 之后才是真正把当前任务挂到等待队列的 vTaskPlaceOnEventList */}
两个身份的来源:
pxQueue->u.xSemaphore.xMutexHolder | prvIsQueueEmpty → 锁不为空 → 此字段必非空(除非中断里拿了互斥量但那是非法路径直接判 NULL) | ||
pxCurrentTCB | xQueueSemaphoreTake、拿不到锁、即将被挂起的这个任务自己 | pxCurrentTCB 不可能错 |
所以继承的真正含义,用一句话说就是:当前运行的高任务要拿锁、发现被别人占了 → 以互斥量里登记的持有者 TCB 为目标,把它的 uxPriority 改成当前任务(=请求者)的 uxPriority。真正的"继承动作"就是这一行赋值(tasks.c 继承函数里):
/* 持有者就绪态路径 */pxMutexHolderTCB->uxPriority = pxCurrentTCB->uxPriority;
三个边界行为要一起理解,才不会搞错"抬到多少":
- 严格小于才抬
: pxMutexHolderTCB->uxPriority<pxCurrentTCB->uxPriority。等号不触发。
已经被别的高任务抬过(如低任务 1 → 已被任务 3 抬到 3),再来一个优先级 3 的请求者要锁 → 3 vs 3 不抬。 反向:请求者比持有者优先级低 → 路径上不会走到这里,低优先级任务根本抢不过正在运行的高优先级持有者,只有高任务醒来/解除阻塞后 Take 失败才会触发继承。
- 多层继承不需要栈,直接覆盖
:低任务 L(1) 持锁 → M(3) 来要 → 抬到 3 → 更高的 H(4) 也来要 → 再次执行 uxPriority = 4,直接覆盖上一次继承值,不需要记录继承层。返还时只看uxBasePriority(本名 1),一步归位。 - 持有者当前是阻塞态也照样继承
:else 分支(tasks.c:6641)只改字段不挂链表,等它从阻塞里醒来的那一刻,挂就绪链表用的就是已经抬高的优先级。
所以 5.2 节里 uxMutexesHeld==0才降级的判断就是这个机制的对称面:不管中间被继承抬了几层、也不管同时拿了几把互斥量,只有最后一把锁还完才回本名优先级——否则中途一降级,另一把锁的等待者立刻回到反转状态。
5.2 返还:xTaskPriorityDisinherit(tasks.c)
持有者 Give 互斥量时,把偷来的优先级还回去:
// task.cBaseType_t xTaskPriorityDisinherit( TaskHandle_t const pxMutexHolder ){TCB_t * const pxTCB = pxMutexHolder;configASSERT( pxTCB == pxCurrentTCB ); /* 还锁的一定是运行中的自己 */( pxTCB->uxMutexesHeld )--;/* 当前优先级 ≠ 本名优先级 → 说明继承过 */if( pxTCB->uxPriority != pxTCB->uxBasePriority ){/* 持有的互斥量全还完了才降级(还持有别的锁就不动) */if( pxTCB->uxMutexesHeld == ( UBaseType_t ) 0 ){/* 从高优先级链表摘除 */uxListRemove( &( pxTCB->xStateListItem ) );/* 归还本名优先级 */pxTCB->uxPriority = pxTCB->uxBasePriority;/* 事件链表排序值同步刷新 */listSET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ),( TickType_t ) configMAX_PRIORITIES - pxTCB->uxPriority );prvAddTaskToReadyList( pxTCB );xReturn = pdTRUE; /* 可能需要切换(比如刚放跑了一个更高优先级的等锁者) */}}return xReturn;}
两个防御性设计值得看两遍:
configASSERT( pxTCB == pxCurrentTCB ):互斥量禁止在 ISR 里 Give(Give 的必须是持有者本人),这也是"互斥量不能用于中断"这一硬约束的源码根源; uxMutexesHeld计数:嵌套/多锁场景下,最后一锁还完才降级——中途降级会让另一把锁的等待者重新暴露在反转风险里。
六、时间片:本工程关掉了它
6.1 tick 里的同优先级轮转(tasks.c)
/* Tasks of equal priority to the currently running task will shareprocessing time (time slice) if preemption is on... */#if ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) ){if( listCURRENT_LIST_LENGTH(&( pxReadyTasksLists[ pxCurrentTCB->uxPriority ] ) ) > 1U ){xSwitchRequired = pdTRUE; /* 同优先级还有别人 → 切 */}}#endif
逻辑很直白:当前优先级的就绪链表长度 > 1 就切换。配合 listGET_OWNER_OF_NEXT_ENTRY 的游标轮转,同优先级任务在每个 tick 边界轮流坐庄。
6.2 本工程的特殊配置
/* FreeRTOSConfig.h */#define configUSE_PREEMPTION 1#define configUSE_TIME_SLICING 0 /* ← 关闭时间片! */#define configIDLE_SHOULD_YIELD 1
时间片关闭意味着:同优先级任务之间,运行中的那个不主动让出(阻塞/延时)就永远独占 CPU,tick 到了也不切。好处是省去无谓切换、时间行为更确定;代价是同优先级任务必须自觉让出。另外 configIDLE_SHOULD_YIELD=1 是独立开关:空闲任务每轮循环发现还有优先级 0 的应用任务就绪时主动让位——注意它让的只是同优先级的,压不过优先级 1 及以上的任务。
七、注意事项(踩坑清单)
configASSERT 死机;关 assert 才走钳位 | |
configIDLE_SHOULD_YIELD 开着还能被带一带,关了就饿死 | |
| 没有 FromISR 版本 | |
configASSERT( pxTCB == pxCurrentTCB ) | |
configUSE_TIME_SLICING=0,同优先级不让出就独占 | |
configMAX_PRIORITIES-1 = 本工程最高(4),回调慢会拖垮所有应用任务 | |
vTaskPrioritySetuxBasePriority(见第四节) | |
uxTaskPriorityGet | uxPriority——继承期间读到的是抬高的值,本名在 uxBasePriority(无 API 可读) |
任务优先级 vs 中断优先级再强调一次:本工程 configKERNEL_INTERRUPT_PRIORITY=15<<4(SysTick/PendSV 为最低),configMAX_SYSCALL_INTERRUPT_PRIORITY=5<<4。数值 0~4 的中断永远不被内核屏蔽、不能调任何 FreeRTOS API;只有 5~15 的中断才能用 FromISR 系列。这套数值方向和任务优先级完全相反,混着记必出事。
八、时序图
8.1 vTaskPrioritySet 全流程

8.2 优先级反转 vs 继承(本工程 main.c 实验)

8.3 同优先级轮转(时间片,本工程已关闭)

九、用例(可直接编译验证)
9.1 运行时改优先级 + 查询
// main.c/* 观察:就绪态任务被抬高优先级 → 摘旧链表挂新链表 → 立即抢占运行中的任务 *//* worker:忙循环占着 CPU(优先级1,就绪态) */voidworker_task( void *pvParameters ){(void)pvParameters;volatile uint32_t ulCount = 0;while( uxTaskPriorityGet( NULL ) == 1 ) /* 优先级还是1就一直忙转 */{ulCount++;}/* 被抬到4的瞬间抢上台,落到这里 */printf( "worker: promoted to %u!\r\n",( unsigned ) uxTaskPriorityGet( NULL ) );vTaskDelay( pdMS_TO_TICKS( 100 ) ); /* 一阻塞,controller 立刻回来 */vTaskDelete( NULL );}voidcontroller_task( void *pvParameters ){(void)pvParameters;TaskHandle_t xWorker = NULL;xTaskCreate( worker_task, "worker", 128, NULL, 1, &xWorker );vTaskDelay( pdMS_TO_TICKS( 1000 ) ); /* 期间 worker(1) 独占 CPU 忙转 */printf( "worker priority = %u\r\n",( unsigned ) uxTaskPriorityGet( xWorker ) ); /* 读实时值:1 */vTaskPrioritySet( xWorker, 4 ); /* worker 就绪态:摘1号链表挂4号链表,且4 > 本任务2 → 立即抢占 *//* ——worker(4) 立刻上台打印;它一睡,下面这行才执行—— */printf( "back in controller\r\n" );vTaskPrioritySet( NULL, 0 ); /* 把自己压到0:低于几乎所有任务,小心用 */vTaskDelete( NULL ); /* 没有更高优先级任务就绪时才轮到这行 */}
9.2 优先级反转对照实验(本工程 main.c 实际代码)
// main.c#define HIGH_PRIORITY_TASK_PRIORITY ( tskIDLE_PRIORITY + 3 ) /* = 3 */#define MEDIUM_PRIORITY_TASK_PRIORITY ( tskIDLE_PRIORITY + 2 ) /* = 2 */#define LOW_PRIORITY_TASK_PRIORITY ( tskIDLE_PRIORITY + 1 ) /* = 1 *//* 高优先级任务:t=300ms 醒来要锁,记录等了多久 */voidhigh_priority_task( void *pvParameters ){(void)pvParameters;vTaskDelay( pdMS_TO_TICKS( 300 ) ); /* 等低任务把锁拿到手 */printf( "high: 要锁...\n" );TickType_t t0 = xTaskGetTickCount();xSemaphoreTake( xSemaphore, portMAX_DELAY ); /* 阻塞:锁在低任务手里 */printf( "high: 拿到锁! 等了 %u ms\n",(unsigned)( xTaskGetTickCount() - t0 ) * portTICK_PERIOD_MS );xSemaphoreGive( xSemaphore );vTaskDelete( NULL );}/* 中优先级任务:t=500ms 开始忙等 3s——反转的帮凶 */voidmedium_priority_task( void *pvParameters ){(void)pvParameters;vTaskDelay( pdMS_TO_TICKS( 500 ) ); /* 等高任务已经开始等锁 */printf( "medium: 开始忙等 3s 捣乱\n" );TickType_t xStart = xTaskGetTickCount();while ( xTaskGetTickCount() - xStart < pdMS_TO_TICKS( 3000 ) ){}vTaskDelete( NULL );}/* 低优先级任务:开局拿锁,边睡边磨蹭约 2s */voidlow_priority_task( void *pvParameters ){(void)pvParameters;xSemaphoreTake( xSemaphore, portMAX_DELAY ); /* 开局就拿到锁 */printf( "low: 拿到锁,慢慢干\n" );for ( int i = 0; i < 10; i++ ){printf( "low %d\n", i );vTaskDelay( pdMS_TO_TICKS( 200 ) ); /* 持锁磨蹭约 2s */}xSemaphoreGive( xSemaphore );vTaskDelete( NULL );}intmain( void ){NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 ); /* FreeRTOS 推荐 */USART1_Init( 115200 );/* 对照实验开关:换回 xSemaphoreCreateBinary() 即复现反转 */xSemaphore = xSemaphoreCreateMutex();xSemaphoreGive( xSemaphore );xTaskCreate( high_priority_task, "high", 256, NULL, HIGH_PRIORITY_TASK_PRIORITY, NULL );xTaskCreate( medium_priority_task, "medium", 256, NULL, MEDIUM_PRIORITY_TASK_PRIORITY, NULL );xTaskCreate( low_priority_task, "low", 256, NULL, LOW_PRIORITY_TASK_PRIORITY, NULL );vTaskStartScheduler();for( ;; );}
实测结论(串口打印的 high: 拿到锁! 等了 xxx ms):
- 二值信号量:高任务等锁时间 = 中任务忙等剩余时间 + 低任务磨完剩余循环——被无关的中任务拖长(反转);- 互斥量:高任务等锁时间 ≈ 低任务持锁的固有磨蹭时间(约 2s),不被中任务额外拖长——继承让低任务每次醒来都能压过中任务推进。
十、总结
1. 优先级数值越大越高(与 NVIC 相反),0 是空闲任务专属,本工程合法范围 0~4;2. 存储三处:TCB 双字段(实时+本名,为继承而生)、就绪链表数组(优先级作下标+同级轮转)、事件链表补数(阻塞队列按优先级降序唤醒);3. 改优先级 = 临界区内原子迁移三处存储 + 视情况 yield;互斥量的继承/返还只动 uxPriority并以uxBasePriority为归还依据——这就是二值信号量会反转、互斥量不会的源码根源。
十一、与工程踩坑记录的关联
xTaskPriorityDisinheritpxTCB == pxCurrentTCB(本文 5.2) | |
xTaskPriorityInherit | |
configMAX_PRIORITIES-1(本文第一节) | |
vTaskPrioritySet |
— 写在最后 —
如果这篇文章对你有帮助,欢迎点赞、在看、转发三连支持,这是我持续更新的最大动力。
关注我,更多硬核技术剖析持续更新中,下一期再见。