不能直接用__disable_irq()和__enable_irq()包裹临界区,因其仅操作PRIMASK、不保存原状态、不支持嵌套调用,易导致中断提前恢复、逻辑失控或RTOS调度失效;推荐使用RAII封装+嵌套计数的临界区类,并在RTOS中改用taskENTER_CRITICAL()等适配宏。

为什么不能直接用 __disable_irq() 和 __enable_irq() 包裹临界区
很多刚从 ARM Cortex-M(比如 STM32)转到通用 C++ 单片机开发的人,会下意识把裸机习惯搬过来:用 __disable_irq() 关总中断、执行完再 __enable_irq()。但问题在于——__disable_irq() 是编译器内置函数(GCC/ARMCC),它只在特定架构和编译环境下有效,且不保证可重入、不保存原中断状态、不处理嵌套调用。
常见错误现象:
– 多层函数嵌套调用临界区时,外层 __enable_irq() 提前恢复中断,导致内层被中断打断
– 中断已处于关闭状态时再次调用 __disable_irq(),无副作用但逻辑失控
– 在 FreeRTOS 或其他 RTOS 下直接调用,可能破坏调度器的中断屏蔽管理
-
__disable_irq()只是写 PRIMASK=1,不保存旧值 - 没有上下文感知,无法用于递归或中断服务程序(ISR)中安全嵌套
- C++ 对象构造/析构、STL 容器操作等隐式行为可能跨中断边界,仅关中断不能保证 C++ 层语义原子性
推荐做法:用 RAII 封装中断状态并支持嵌套计数
真正可靠的临界区保护,在 C++ 单片机项目里应基于 RAII(资源获取即初始化)+ 状态计数,而不是裸调指令。核心思路是:每次进入临界区时读取并保存当前 PRIMASK/FAULTMASK 值,退出时仅当计数归零才恢复。
示例(适用于 Cortex-M3/M4/M7,需 或对应头文件):
class CriticalSection {
static uint32_t s_nest_count;
uint32_t m_old_primask;
public:
CriticalSection() {
m_old_primask = __get_PRIMASK();
if (s_nest_count++ == 0) {
__disable_irq();
}
}
~CriticalSection() {
if (--s_nest_count == 0) {
__set_PRIMASK(m_old_primask);
}
}
};
uint32_t CriticalSection::s_nest_count = 0;
使用方式简洁安全:
void uart_tx_handler() {
CriticalSection cs; // 进入自动关中断
tx_buffer.push(data); // 保护共享缓冲区
// 离开作用域自动恢复
}
- 支持函数内多处声明、支持栈上对象生命周期管理
- 嵌套调用时不会误开中断,也不会重复关中断导致“锁死”
- 若已在中断中(PRIMASK=1),构造函数仍能正确记录原始状态,析构时精准还原
遇到 FreeRTOS 或其他 RTOS 时必须改用其 API
在 RTOS 环境下,__disable_irq() 是危险操作——调度器依赖 PendSV 和 SysTick 中断,粗暴关总中断会导致任务切换失效、看门狗复位、甚至死锁。
FreeRTOS 提供了配套宏,它们内部做了适配(如对 Cortex-M 使用 BASEPRI 而非 PRIMASK):
- 进入临界区:
taskENTER_CRITICAL()/portENTER_CRITICAL() - 退出临界区:
taskEXIT_CRITICAL()/portEXIT_CRITICAL() - 注意:这些宏不是函数,不能传参;且必须成对出现在同一作用域(否则宏展开会出错)
如果硬要用 RAII 风格,可封装为:
class RTOSCriticalsection {
public:
RTOSCriticalsection() { taskENTER_CRITICAL(); }
~RTOSCriticalsection() { taskEXIT_CRITICAL(); }
};
但要清楚:taskENTER_CRITICAL() 默认禁用优先级 ≥ configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 的中断,不是全关——这是为了平衡实时性和安全性。
容易被忽略的 C++ 特殊情况:构造函数、异常、内联与优化
单片机 C++ 工程常禁用异常和 RTTI,但仍需警惕编译器优化和对象生命周期带来的“伪并发”问题:
- 全局对象构造函数在
main()前执行,此时中断可能已开启,但临界区类尚未就绪 → 不要在全局作用域创建CriticalSection实例 - 编译器可能将临界区内联后重排指令(尤其 -O2/-O3),需在关键变量上加
volatile或用std::atomic_thread_fence(std::memory_order_seq_cst)(若支持) -
__disable_irq()后紧接着的内存访问,某些平台需要__DSB()+__ISB()确保指令顺序,RAII 类中应一并包含
最稳妥的做法是:所有共享数据结构(队列、标志位、计数器)都显式用 std::atomic(若编译器支持)或加锁封装,临界区只是兜底手段,而非唯一保障。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











