hubeiluhua 发表于 2025-7-8 10:13

“全局标志”来实现 ISR 和主循环 对一个全局数组的互斥访问。为什么是不可行的

本帖最后由 hubeiluhua 于 2025-7-8 10:21 编辑

你想用一个“全局标志”来实现 ISR 和主循环 对一个全局数组的互斥访问。

✅ 理想设想是这样的:
volatile uint8_t lock = 0;            // 全局互斥锁
volatile uint8_t buffer;            // 共享数组

void ISR_Handler(void)
{
    if (lock == 0) {
      lock = 1;
      buffer = 0x55; // 修改数组
      lock = 0;
    }
}

int main(void)
{
    while (1) {
      if (lock == 0) {
            lock = 1;
            buffer = 0xAA;// 也修改数组
            lock = 0;
      }
    }
}
看起来两个地方都判断 lock == 0 再访问数组,设置为 1 表示“加锁”,用完后再释放。

❌ 实际问题在哪里?为什么这个“全局标志互斥”不可靠?
❗关键点:中断随时可能打断主程序执行,中断不管你是否“加锁”了
❌ 错误执行顺序可能如下:

假设 main() 正在进入临界区:
if (lock == 0) {       // lock = 0,准备进入临界区
    lock = 1;          // 这里准备加锁…
此时,突然发生中断!

ISR 进入执行:
if (lock == 0)         // lock 已经设为 1 了,但 ISR 是异步执行,它可能看到 lock 还没改
{
    lock = 1;          // ISR 也以为自己“安全”,其实错了
    buffer = 0x55;// 与 main 同时访问 buffer
    lock = 0;
}

中断返回后,main() 继续执行:
buffer = 0xAA;      // 和 ISR 的访问冲突!
lock = 0;
❗最终结果:lock 根本无法阻止中断函数访问内存!
✅ 总结本质问题:
项目                                                                           说明
主函数运行中判断 lock=0                                    还没来得及设为1就被中断
ISR 运行时不受“主循环中的 lock”限制                  因为中断是异步的,根本不会等待主函数“加完锁”
所以 ISR 和 main 同时访问数组                            出现数据竞争、破坏、错乱

✅ 正确做法:使用关中断实现真正的互斥访问
volatile uint8_t buffer;

void ISR_Handler(void)
{
    buffer = 0x55;// 直接写,因为 main 会关中断保护
}

int main(void)
{
    while (1) {
      __disable_irq();         // 关闭中断
      buffer = 0xAA;      // 安全访问
      __enable_irq();          // 恢复中断
    }
}
在 main 中访问共享数据时禁止中断,中断中再怎么激活也不会打断当前访问。这样就是真正的互斥。

❌ 为什么“全局标志互斥锁”行不通?
原因                                                                               解释
中断是异步优先级高                                                       不管主函数有没有“设置锁”,中断随时都能发生
主函数不能阻止 ISR 执行                                             只能靠“关中断”机制
标志变量本身不受保护                                             改它的过程也会被中断打断
判断 lock==0 和 lock=1 不是原子操作                     可能中断正好打断两个操作之间

✅ 什么时候可以用全局标志?
✔ 用于主循环等待中断事件:
volatile uint8_t data_ready = 0;

void USART_IRQHandler(void)
{
    buffer = USART_ReceiveData();
    data_ready = 1;
}

int main(void)
{
    while (1) {
      if (data_ready) {
            __disable_irq();   // 保护临界区
            process(buffer);
            data_ready = 0;
            __enable_irq();
      }
    }
}
✅ 总结
用法                                                      是否可行      说明
主循环用 lock 标志尝试互斥 ISR      ❌ 不行            ISR 是异步的,随时会抢占 main,不受 lock 控制
在主循环访问共享数据时关中断         ✅ 推荐            可以真正阻止 ISR 执行
中断中访问共享数据,无需加锁         ✅ 可行            假设主循环有保护
使用全局标志同步而不是互斥(如 data_ready)      ✅ 可以      适合事件通知、状态切换

如果你有特定的代码片段或共享结构体内容,我可以帮你逐句分析需要不需要加锁,或者怎么用关中断保护临界区。

ayb_ice 发表于 2025-7-11 08:28

这个加锁实际是可行的,
页: [1]
查看完整版本: “全局标志”来实现 ISR 和主循环 对一个全局数组的互斥访问。为什么是不可行的