hubeiluhua 发表于 2025-7-5 10:21

裸机中断与主程序通信中“真正致命”的BUG示例

本帖最后由 hubeiluhua 于 2025-7-5 10:23 编辑

裸机中断与主程序通信中“真正致命”的BUG示例

【BUG 1】共享变量非原子操作,数据撕裂(数据一半新一半旧)场景:MCU 32位,操作一个64位数据,裸机环境。c
复制编辑


volatile uint64_t shared_value = 0;

void ISR_Handler(void) {
    shared_value = 0x1122334455667788;
}

void main(void) {
    while (1) {
      uint64_t temp = shared_value;
      printf("%08X %08X\n", (uint32_t)(temp >> 32), (uint32_t)(temp & 0xFFFFFFFF));
    }
}

致命点:
[*]shared_value 是64位,STM32F1/F4是32位总线,CPU拆成两次访问:

[*]先读取高32位
[*]然后低32位
[*]假设中断恰好在两次访问之间发生,中断更新了shared_value,结果:
txt
复制编辑


高32位 = 0x11223344   (旧数据)低32位 = 0x55667788   (新数据)


[*]打印输出数据撕裂,完全错误,数据不可信。
根源:
[*]多次非原子性访问
[*]中断抢占破坏数据一致性
解决:
[*]禁用中断包裹整个读取过程:
c
复制编辑


__disable_irq();
uint64_t temp = shared_value;
__enable_irq();

✅ 这样,确保整个64位数据在读取过程中不中断,数据完整。


【BUG 2】标志位判断顺序错误,数据丢失场景:中断采集数据,主程序轮询处理:c
复制编辑


volatile int data_ready = 0;
volatile int data = 0;

void ISR_Handler(void) {
    data = read_sensor();
    data_ready = 1;
}

void main(void) {
    while (1) {
      if (data_ready) {
            process(data);
            data_ready = 0;
      }
    }
}


致命点:
[*]data_ready 设为1后,主程序尚未判断,中断又触发:

[*]data = 新数据2
[*]data_ready = 1
[*]主程序此时:

[*]判断data_ready为1
[*]处理数据,其实处理的是新数据2
[*]结果:

[*]新数据1完全丢失
[*]只有最新数据保留,导致数据遗漏,尤其对ADC、传感器连续采集极其致命

解决:
[*]使用缓冲区,避免数据覆盖:
c
复制编辑


#define BUF_SIZE 4
volatile int buffer;
volatile int head = 0;
volatile int tail = 0;

void ISR_Handler(void) {
    buffer = read_sensor();
    head = (head + 1) % BUF_SIZE;
}

void main(void) {
    while (1) {
      if (tail != head) {
            process(buffer);
            tail = (tail + 1) % BUF_SIZE;
      }
    }
}


✅ 数据多次采样绝不丢失,先进先出。【BUG 3】编译器优化假死,变量未加volatile场景:中断置标志,主程序等待:c
复制编辑


int flag = 0;

void ISR_Handler(void) {
    flag = 1;
}

void main(void) {
    while (1) {
      if (flag) {
            flag = 0;
            do_something();
      }
    }
}


致命点:
[*]未加volatile,编译器优化:
c
复制编辑


if (flag) {   // 可能编译器提前缓存flag,循环内只读一次}


[*]中断虽置位,主程序却看不到新值
[*]程序假死,do_something()***不执行
解决:c
复制编辑


volatile int flag = 0;

✅ 告诉编译器,变量随时可能变,强制每次重新读内存。【BUG 4】中断滥用禁用,导致丢失其他重要中断场景:c
复制编辑


void ISR1(void) {
    __disable_irq();
    delay_ms(10);// 假设长时间操作
    __enable_irq();
}


致命点:
[*]在中断内禁用全局中断
[*]如果其他中断(如串口接收、定时器)在这期间触发,全部丢失
[*]系统无法实时响应,严重影响可靠性
正确思路:
[*]避免中断内长时间禁用中断
[*]中断快速退出,必要保护时,在主程序使用:
c
复制编辑


__disable_irq();
critical_data = new_value;
__enable_irq();


✅ 保证重要中断不被滥用禁用影响。【BUG 5】共享结构体多字段不一致场景:c
复制编辑


typedef struct {
    int x;
    int y;
} Point;

volatile Point shared_point = {0, 0};

void ISR_Handler(void) {
    shared_point.x = get_x();
    shared_point.y = get_y();
}


致命点:
[*]中断在两次赋值之间,主程序读取,得到:
c
复制编辑


x = 新值,y = 旧值


[*]结构体整体失效,数据不匹配
解决:
[*]禁用中断包裹整体结构体读写:
c
复制编辑


__disable_irq();
Point temp = shared_point;
__enable_irq();


✅ 确保结构体数据整体一致。

终极总结:真正致命BUG分类

错误类型具体表现后果正确做法
非原子多次访问数据撕裂错误数据,难以排查禁用中断确保整体读写
标志位顺序错误覆盖未处理数据,数据丢失重要数据漏掉使用缓冲区
缓存优化假死主程序读不到中断修改的数据系统逻辑假死变量加volatile
中断滥用禁用中断内长时间禁用全局中断丢失重要中断,系统卡死简化中断逻辑,主程序做耗时操作
结构体不一致多字段更新不同步,数据逻辑混乱结构体整体失效保护整体操作



页: [1]
查看完整版本: 裸机中断与主程序通信中“真正致命”的BUG示例