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 对比
| vTaskSuspendAll | taskENTER_CRITICAL | |
|---|---|---|
| 关中断 | ❌ | ✅ |
| 关调度 | ✅ | ✅pendsv 是最低优先级的中断 |
| ISR 中使用 | ❌ | ✅(FROM_ISR 版) |
| 嵌套 | ✅ | ✅ |
| 开销 | 极低(变量自增) | 低(写 BASEPRI) |
| 适用场景 | 仅任务间竞争 | 任务 + ISR 间竞争 |
4:使用条件(全部满足才能用)
共享资源没有任何 ISR 访问
临界区内不调用可能阻塞的 API
代码块一条直线走到尾,不中途 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));