RTOS-F429-HAL-临界段保护及任务调度器的挂起(2026/7/28)

目录

一:FreeRTOS 临界段保护机制完整梳理

一、为什么需要临界段?

二、底层硬件操作(Cortex-M)

三、两套 API 的划分依据

四、任务级:计数器模式

1:API

2:内部逻辑

3:嵌套示例

4:典型使用

五:中断级

1:API

2:内部逻辑

六:使用约束

二:任务调度器的挂起和恢复

1:函数原型

2:禁止了什么 vs 保留了什么

3:与 taskENTER_CRITICAL 对比

4:使用条件(全部满足才能用)

5:代码示例


一:FreeRTOS 临界段保护机制完整梳理

一、为什么需要临界段?

多任务 + 中断场景下,共享资源会被并发访问。比如:

// 任务 A 正在修改一个全局链表 // 中断来了,ISR 里也修改同一个链表 // 链表指针被破坏 → 崩溃

临界段通过临时屏蔽中断来保证一段代码的原子性,禁止其他任务或 ISR 抢占

二、底层硬件操作(Cortex-M)

核心寄存器是 BASEPRI:

BASEPRI 值效果
0不屏蔽任何中断
非 0屏蔽所有优先级 ≥ 该值的逻辑中断

关键设计:不是全局关中断,而是只屏蔽受 FreeRTOS 管理的中断(优先级 ≥configMAX_SYSCALL_INTERRUPT_PRIORITY)。高优先级的紧急中断(如电机刹车)不受影响。

三、两套 API 的划分依据

任务上下文中断上下文
有无"户口"✅ 有 TCB,可以维持状态❌ 没有,借道执行
状态存储位置TCB →uxCriticalNesting栈上局部变量UBaseType_t
工作模式计数器模式快照-恢复模式
嵌套支持✅ 支持❌ 不支持

四、任务级:计数器模式

1:API
void taskENTER_CRITICAL(void); void taskEXIT_CRITICAL(void);
2:内部逻辑
taskENTER_CRITICAL: if (uxCriticalNesting == 0) 写 BASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY // 真正关中断 uxCriticalNesting++ ​ taskEXIT_CRITICAL: uxCriticalNesting-- if (uxCriticalNesting == 0) 写 BASEPRI = 0 // 真正开中断
3:嵌套示例
uxCriticalNesting = 0 // 初始状态,中断开着 ​ taskENTER_CRITICAL() // 0→1 关中断 ✅ taskENTER_CRITICAL() // 1→2 不管 taskENTER_CRITICAL() // 2→3 不管 taskEXIT_CRITICAL() // 3→2 不管 taskEXIT_CRITICAL() // 2→1 不管 taskEXIT_CRITICAL() // 1→0 开中断 ✅ ← 归零!
4:典型使用
void TaskA(void *pv) { for (;;) { taskENTER_CRITICAL(); // 访问共享队列 / 全局变量 // 可以调用写临界区的子函数,自动形成嵌套 taskEXIT_CRITICAL(); } }

五:中断级 : 无定时器

1:API
UBaseType_t taskENTER_CRITICAL_FROM_ISR(void); void taskEXIT_CRITICAL_FROM_ISR(UBaseType_t uxSavedInterruptStatus);
2:内部逻辑
taskENTER_CRITICAL_FROM_ISR: 读 BASEPRI当前寄存器的值然后赋值给 uxSaved // 记下在中断进入临界区前对应的BASEPRI寄存器值 写 BASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY // 不用管其他内容,先关掉中断,进入临界区执行任务。 return uxSaved ​ taskEXIT_CRITICAL_FROM_ISR(uxSaved)://临界区干的事情结束后,把中断重新开启,退出临界区 写 BASEPRI = uxSaved // 按照片复原,不越权改变状态,把一开始记下的BASEPRI寄存器值写回给BASEPRI寄存器

六:使用约束

约束说明
临界区要短关中断 = 延迟所有同优先级的 ISR 响应,破坏实时性
不能阻塞临界区内调用vTaskDelay()、带超时的队列操作 → 死锁(调度器需要中断,中断被关了)
严格配对漏一个EXIT= 永久关中断,系统停摆
禁止混用ISR 里用任务级 → 读写的是不属于自己的 TCB 字段,未定义行为
禁止嵌套(中断级)没有计数器,重复 ENTER 会覆盖上一个uxSaved,第二个 EXIT 恢复的是第二个 ENTER 拍的照片,第一个的状态就丢了

二:任务调度器的挂起和恢复

1:函数原型

void vTaskSuspendAll(void) { ++uxSchedulerSuspended; // 只加一个全局计数器,不碰硬件 } ​ BaseType_t xTaskResumeAll(void) { --uxSchedulerSuspended; if (uxSchedulerSuspended == 0) { // 恢复挂起期间积压的任务就绪操作 // 可能立即触发上下文切换 } return pdTRUE / pdFALSE; // 是否发生了任务切换 }

2:禁止了什么 vs 保留了什么

任务调度 ❌ 停摆(不会切走当前任务) 任务切换 ❌ 冻结 时间片轮转 ❌ 暂停 中断 ✅ 正常响应 ISR 调度 ✅ 正常执行

3:与 taskENTER_CRITICAL 对比

vTaskSuspendAlltaskENTER_CRITICAL
关中断
关调度✅pendsv 是最低优先级的中断
ISR 中使用✅(FROM_ISR 版)
嵌套
开销极低(变量自增)低(写 BASEPRI)
适用场景仅任务间竞争任务 + ISR 间竞争

4:使用条件(全部满足才能用)

  1. 共享资源没有任何 ISR 访问

  2. 临界区内不调用可能阻塞的 API

  3. 代码块一条直线走到尾,不中途 return/break

5:代码示例

vTaskSuspendAll(); // 1. 挂起调度器 { access_shared_list(); // 2. 干正事 } BaseType_t wasSwitched = xTaskResumeAll(); // 3.恢复调度器:同时定义一个变量接受它的返回值 if(wasSwitched == pdTRUE) // 4. 返回pdTRUE ,说明恢复调度器的一瞬间,它其实已经切出去完成其他任务了,执行完回来执行 余下的代码 { // 例如:记录日志,或者增加一个“被抢占次数”的统计变量 ulPreemptionCount++; } //5.返回pdFALSE,说明恢复调度器的一瞬间,没有切去执行其他任务,那if 不满足 // 接下来执行余下的代码vTaskDelay(pdMS_TO_TICKS(10)); vTaskDelay(pdMS_TO_TICKS(10));