中断丢失问题:中断丢失的常见原因
中断丢失,说白了就是CPU该响应中断的时候没响应。我刚开始做嵌入式系统时,总觉得中断嘛,优先级设好就行了。直到有一次产品在现场频繁死机,查了三天才发现是中断丢了。嗯,这问题其实比想象中常见。
中断丢失的核心原因,我归纳为三类:屏蔽、优先级反转、处理耗时过长。咱们一个一个拆开看。
3.1 屏蔽导致的中断丢失
屏蔽是最直接的原因。你想想看,中断被关了,CPU自然收不到信号。但问题往往出在「不该关的时候关了」或者「关了忘开」。
典型场景:
- 全局关中断(
cli/disable_irq)后,长时间不恢复 - 在中断处理函数里又调了关中断函数,导致嵌套丢失
- 外设的中断使能位被意外清零
我记得有个项目,用的是某款ARM Cortex-M3芯片。驱动库里有个函数,进去就关了总中断,出来时忘了开。结果系统跑着跑着,定时器中断就丢了,看门狗复位。查代码查了两天,最后发现是那个函数在异常路径上漏了enable_irq。
我的习惯:关中断操作一定要成对出现。写代码时,我习惯用宏封装:
#define ENTER_CRITICAL() do { old_level = disable_irq_save(); } while(0)
#define EXIT_CRITICAL() do { restore_irq(old_level); } while(0)
这样能保证每个出口都恢复中断状态。
3.2 优先级反转导致的中断丢失
优先级反转这个词,做RTOS的人都不陌生。但很多人以为它只影响任务调度,其实中断也会被它坑。
举个例子:
- 高优先级中断A(比如定时器,优先级0)
- 低优先级中断B(比如串口,优先级2)
- 中优先级中断C(比如GPIO,优先级1)
如果B正在处理,A来了,按理说A应该抢占B。但如果B在临界区里关了高优先级中断,A就被堵住了。等B出来,A可能已经错过了采样窗口——中断丢失。
| 场景 | 是否丢失 | 原因 |
|---|---|---|
| B在临界区,A到达 | 可能丢失 | A被屏蔽,B退出后A的脉冲已过 |
| B在临界区,C到达 | 不丢失 | C优先级高于B,但低于A,不影响A |
| B在临界区,A和C同时到达 | A可能丢失 | C先响应,A被屏蔽时间更长 |
我曾经踩过的坑:在一个多中断系统中,串口中断里用了spin_lock_irqsave,锁住了所有中断。结果高优先级的ADC中断来了,被硬生生堵了200微秒。ADC采样周期是100微秒,直接丢了两个采样点。后来我把锁改成了spin_lock,只锁当前CPU,不关其他中断,问题解决。
3.3 处理耗时过长导致的中断丢失
这个原因最隐蔽。你想想看,中断处理函数执行时间太长,后面的中断排队等着。如果中断源是边沿触发或者单脉冲,等CPU腾出手来,信号已经没了。
我一般把中断处理分成两部分:
- 上半部(Top Half):只做最紧急的事,比如读寄存器、清标志、记录时间
- 下半部(Bottom Half):把耗时操作丢给任务或工作队列
经验数据:
- 中断处理函数执行时间 < 中断源最小间隔的 1/3
- 如果中断源间隔是 100μs,处理函数最好控制在 30μs 以内
- 超过这个阈值,丢失概率急剧上升
我做过一个电机控制项目,霍尔传感器中断每50微秒来一次。一开始我在中断里做了PID计算和PWM更新,算下来要80微秒。结果电机转起来一顿一顿的——中断丢了。后来我把PID计算挪到任务里,中断里只读霍尔值和清标志,处理时间降到5微秒,问题消失。
3.4 三种原因的对比与诊断
| 原因 | 典型表现 | 诊断方法 | 修复方向 |
|---|---|---|---|
| 屏蔽 | 中断完全不响应 | 检查中断使能寄存器 | 成对操作、避免长临界区 |
| 优先级反转 | 高优先级中断偶尔丢失 | 加GPIO翻转测延迟 | 缩小锁范围、使用优先级继承 |
| 处理耗时过长 | 周期性中断丢失 | 测量中断处理时间 | 拆分为上下半部 |
3.5 核心逻辑图
下面这张图,我把三种原因和它们的关系画出来了。你看一眼就能明白:
我的诊断流程:
- 先看中断使能寄存器——确认中断没被关
- 用逻辑分析仪抓中断引脚和CPU响应引脚——测延迟
- 如果延迟稳定但丢中断,查处理时间
- 如果延迟忽大忽小,查优先级反转
中断丢失这个问题,说白了就是「该响应的时候没响应」。屏蔽、优先级反转、处理耗时过长,这三兄弟经常一起出现。我建议你在设计阶段就把它们考虑进去,别等到现场出问题了再查——那滋味,我尝过,不好受。