无RTOS中断驱动代码同步问题:环形缓冲区竞态与同步方案
无RTOS主循环架构下环形缓冲区的中断同步问题
竞态条件确实存在
你的代码里的if (tail != head)判断存在两个核心问题:
- 缓冲区满/空逻辑错误:原代码用
tail != head作为可写入的条件,但环形缓冲区中tail == head表示空,而满的正确条件应该是next_ring_pos(head, BUF_LEN) == tail——原逻辑会导致缓冲区满时仍执行写入,覆盖已有数据。 - 共享变量未同步:
head和tail是ISR与主循环共享的变量,未加volatile修饰时,编译器可能会将它们缓存到寄存器中,导致判断时读取的是旧值;同时,在判断条件和后续指针修改之间,ISR可能打断主循环并修改共享指针,导致判断结果与实际缓冲区状态不一致。
举个具体场景:put_sample中判断tail != head为真后,主循环触发process_sample修改了tail,此时ISR仍会执行写入操作,直接导致缓冲区溢出。
无等待原语下的同步解决方案
针对无RTOS的中断-主循环架构,可通过以下方式实现安全同步:
1. 用volatile修饰共享变量
volatile关键字强制编译器每次直接从内存读写变量,避免寄存器缓存旧值,确保读取的是最新状态。
2. 修正环形缓冲区的空/满判断逻辑
明确规则:
- 空缓冲区:
tail == head - 满缓冲区:
next_ring_pos(head, BUF_LEN) == tail
3. 读取共享变量的快照
在判断前先读取共享变量的快照,确保整个判断逻辑基于同一时刻的缓冲区状态,避免ISR中途修改变量导致判断不一致。
修正后的代码实现
#define BUF_LEN 10 uint16_t buffer[BUF_LEN]; volatile int head = 0, tail = 0; // volatile修饰共享指针 int next_ring_pos(int pos, int len) { return (pos + 1) % len; } void process_sample() { uint16_t sample_value; // 先获取head的快照,避免ISR中途修改导致判断不一致 int current_head = head; if (tail != current_head) { sample_value = buffer[tail]; // tail赋值是原子操作(int类型在多数MCU架构中是原子的),无需担心中断干扰 tail = next_ring_pos(tail, BUF_LEN); // 处理采样值的逻辑 } } void put_sample(uint16_t sample_value) { int next_head = next_ring_pos(head, BUF_LEN); // 获取tail的快照,避免主循环中途修改导致判断错误 int current_tail = tail; if (next_head != current_tail) { buffer[head] = sample_value; // head赋值是原子操作 head = next_head; } else { // 缓冲区溢出处理逻辑 } }
额外注意事项
- 原子操作:对于多数MCU(如ARM Cortex-M系列),int类型的变量赋值是原子操作,不会出现部分修改的情况,因此修改
head和tail时无需关闭中断。 - 内存屏障:如果是复杂架构(如部分RISC-V或多核MCU),可能需要插入内存屏障指令(如ARM的
__DSB())来确保内存操作顺序,但普通单核MCU通常不需要——中断触发时会自动刷新流水线,保证内存访问一致性。
内容的提问来源于stack exchange,提问作者Leonid P.
相关产品推荐
相关产品推荐

