2025最新超详细FreeRTOS入门教程:第十三章 FreeRTOS临界区与原子操作
摘要
在多任务系统中,多个任务可能会同时访问同一资源(如全局变量、外设寄存器、缓冲区)。如果没有采取保护措施,可能会发生:
- 数据竞争(Race Condition)
- 任务/中断交叉修改数据
- 死锁
临界区(Critical Section) 和 原子操作(Atomic Operation) 是解决上述问题的基础机制。
在 FreeRTOS 中,它们提供了一种轻量级的数据保护方式,与互斥量、信号量相比,开销更低,效率更高。

文章目录
- [2025最新超详细FreeRTOS入门教程:第十三章 FreeRTOS临界区与原子操作](#2025最新超详细FreeRTOS入门教程:第十三章 FreeRTOS临界区与原子操作)
-
- 摘要
- 一、什么是临界区?
- [二、FreeRTOS 提供的临界区机制](#二、FreeRTOS 提供的临界区机制)
-
- [1. 任务级临界区](#1. 任务级临界区)
- [2. 支持嵌套的临界区](#2. 支持嵌套的临界区)
- [3. 调度器挂起/恢复](#3. 调度器挂起/恢复)
- 三、原子操作
- [四、临界区 vs 互斥量](#四、临界区 vs 互斥量)
- 五、使用示例
- 六、临界区的工作机制
- 七、调试与优化
- 八、常见问题与解决方法
- 九、经验总结
- 十、总结
一、什么是临界区?
临界区指一段需要 独占访问共享资源 的代码。在多任务系统中,为了保证这段代码不被打断,必须采取措施:
- 禁止任务切换(避免其他任务抢占 CPU)
- 禁止中断访问(避免 ISR 修改共享数据)
访问共享变量 访问共享变量 访问共享变量 任务A 临界区 任务B 中断 冲突: 数据错乱
二、FreeRTOS 提供的临界区机制
1. 任务级临界区
c
taskENTER_CRITICAL();
// 临界区代码
taskEXIT_CRITICAL();
特点:
- 禁止 中断和任务切换
- 不可嵌套使用(多次调用会造成问题)
2. 支持嵌套的临界区
c
UBaseType_t uxSavedStatus = taskENTER_CRITICAL_FROM_ISR();
// 临界区代码
taskEXIT_CRITICAL_FROM_ISR(uxSavedStatus);
特点:
- 可在 ISR 中使用
- 可多次嵌套,退出时恢复原状态
3. 调度器挂起/恢复
c
vTaskSuspendAll();
// 临界区代码(调度器暂停)
xTaskResumeAll();
特点:
- 只禁止任务切换
- 中断仍然可用
- 比
taskENTER_CRITICAL
粒度更细
三、原子操作
某些变量(如 8 位或 16 位寄存器)在大多数 MCU 上是原子访问的,但 32 位变量在 8/16 位 MCU 上不是原子操作。
FreeRTOS 提供了原子操作宏:
c
#define taskENTER_CRITICAL() portENTER_CRITICAL()
#define taskEXIT_CRITICAL() portEXIT_CRITICAL()
此外,用户也可以使用 C11 原子库 或 CMSIS 提供的 __LDREX / __STREX
指令。
四、临界区 vs 互斥量
特性 | 临界区 | 互斥量 |
---|---|---|
粒度 | CPU 级别(禁止中断/调度) | 任务级别(优先级继承) |
开销 | 极低 | 较高 |
可嵌套 | 支持(带 FROM_ISR 版本) | 不支持 |
使用场景 | 简单数据保护 | 共享外设、复杂资源 |
五、使用示例
示例1:任务中保护共享变量
c
volatile int sharedCounter = 0;
void vTaskIncrement(void *pvParameters)
{
for(;;)
{
taskENTER_CRITICAL();
sharedCounter++;
taskEXIT_CRITICAL();
vTaskDelay(100);
}
}
示例2:中断与任务共享数据
c
volatile uint32_t ulISRValue = 0;
void vTaskHandler(void *pvParameters)
{
for(;;)
{
taskENTER_CRITICAL();
printf("共享值=%lu\n", ulISRValue);
taskEXIT_CRITICAL();
vTaskDelay(500);
}
}
void TIM2_IRQHandler(void)
{
BaseType_t xHigherPriorityTaskWoken;
uint32_t oldStatus;
oldStatus = taskENTER_CRITICAL_FROM_ISR();
ulISRValue++;
taskEXIT_CRITICAL_FROM_ISR(oldStatus);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
六、临界区的工作机制
taskENTER_CRITICAL 阻止ISR和任务切换 taskEXIT_CRITICAL 正常调度 临界区 禁止中断 临界区结束
七、调试与优化
- 避免长时间占用临界区
- 临界区代码必须简短
- 否则会影响系统实时性
- 使用嵌套版本
- 推荐使用
taskENTER_CRITICAL_FROM_ISR
- 确保中断退出时状态恢复正确
- 推荐使用
- 避免死锁
- 不要在临界区中使用阻塞 API(如
xQueueReceive()
)
- 不要在临界区中使用阻塞 API(如
八、常见问题与解决方法
问题 | 原因 | 解决方法 |
---|---|---|
系统响应变慢 | 临界区占用过长 | 缩短临界区代码 |
死锁 | 临界区中调用阻塞 API | 避免阻塞调用 |
中断丢失 | 过度使用临界区 | 优化设计,缩短屏蔽时间 |
九、经验总结
📌 开发建议
- 临界区适合保护简单的共享数据(如变量 ++)
- 对于复杂外设访问,用 互斥量 更合适
- 临界区必须尽可能短,避免影响实时性
- ISR 中推荐使用
taskENTER_CRITICAL_FROM_ISR
十、总结
通过本章学习,你已经掌握:
- FreeRTOS 临界区的基本概念
taskENTER_CRITICAL
与vTaskSuspendAll
的区别- 临界区与互斥量的对比
- 如何在 ISR 中正确保护共享数据
临界区与原子操作是 FreeRTOS 最底层的数据保护机制,理解它们才能设计出高效、稳定的系统。
🔗 FreeRTOS专栏 👉 下一章:2025最新超详细FreeRTOS入门教程:第十四章 FreeRTOS空闲任务与钩子函数 ------我们将学习 FreeRTOS 的 Idle 任务、空闲任务钩子、Tick Hook、Malloc Failed Hook。