本帖最后由 hubeiluhua 于 2025-7-8 10:21 编辑
你想用一个“全局标志”来实现 ISR 和主循环 对一个全局数组的互斥访问。
✅ 理想设想是这样的:
volatile uint8_t lock = 0; // 全局互斥锁
volatile uint8_t buffer[10]; // 共享数组
void ISR_Handler(void)
{
if (lock == 0) {
lock = 1;
buffer[0] = 0x55; // 修改数组
lock = 0;
}
}
int main(void)
{
while (1) {
if (lock == 0) {
lock = 1;
buffer[0] = 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[0] = 0x55; // 与 main 同时访问 buffer[0]
lock = 0;
}
中断返回后,main() 继续执行:
buffer[0] = 0xAA; // 和 ISR 的访问冲突!
lock = 0;
❗最终结果:lock 根本无法阻止中断函数访问内存!
✅ 总结本质问题:
项目 说明
主函数运行中判断 lock=0 还没来得及设为1就被中断
ISR 运行时不受“主循环中的 lock”限制 因为中断是异步的,根本不会等待主函数“加完锁”
所以 ISR 和 main 同时访问数组 出现数据竞争、破坏、错乱
✅ 正确做法:使用关中断实现真正的互斥访问
volatile uint8_t buffer[10];
void ISR_Handler(void)
{
buffer[0] = 0x55; // 直接写,因为 main 会关中断保护
}
int main(void)
{
while (1) {
__disable_irq(); // 关闭中断
buffer[0] = 0xAA; // 安全访问
__enable_irq(); // 恢复中断
}
}
在 main 中访问共享数据时禁止中断,中断中再怎么激活也不会打断当前访问。这样就是真正的互斥。
❌ 为什么“全局标志互斥锁”行不通?
原因 解释
中断是异步优先级高 不管主函数有没有“设置锁”,中断随时都能发生
主函数不能阻止 ISR 执行 只能靠“关中断”机制
标志变量本身不受保护 改它的过程也会被中断打断
判断 lock==0 和 lock=1 不是原子操作 可能中断正好打断两个操作之间
✅ 什么时候可以用全局标志?
✔ 用于主循环等待中断事件:
volatile uint8_t data_ready = 0;
void USART_IRQHandler(void)
{
buffer[0] = USART_ReceiveData();
data_ready = 1;
}
int main(void)
{
while (1) {
if (data_ready) {
__disable_irq(); // 保护临界区
process(buffer[0]);
data_ready = 0;
__enable_irq();
}
}
}
✅ 总结
用法 是否可行 说明
主循环用 lock 标志尝试互斥 ISR ❌ 不行 ISR 是异步的,随时会抢占 main,不受 lock 控制
在主循环访问共享数据时关中断 ✅ 推荐 可以真正阻止 ISR 执行
中断中访问共享数据,无需加锁 ✅ 可行 假设主循环有保护
使用全局标志同步而不是互斥(如 data_ready) ✅ 可以 适合事件通知、状态切换
如果你有特定的代码片段或共享结构体内容,我可以帮你逐句分析需要不需要加锁,或者怎么用关中断保护临界区。
|