中断响应流程:从请求到返回的完整链路

中断响应,说白了就是CPU怎么知道外设有事找它,它怎么放下手里的活去处理,处理完怎么回来继续干活。这个流程看起来简单,但每个环节都可能藏着性能陷阱。我这些年调过的中断问题,十有八九都出在这几个环节的衔接处。

中断请求:谁在敲门?

中断请求是流程的起点。外设通过拉高或拉低中断线,告诉CPU:“我有事!”

这里分两种:

  • 电平触发:信号一直保持,直到CPU响应。我遇到过一个问题——外设的中断信号没及时清除,导致CPU反复进中断,系统直接卡死。
  • 边沿触发:只在信号跳变时触发一次。省资源,但容易丢中断,尤其是高噪声环境。
我的习惯: 能用电平触发就别用边沿触发。电平触发虽然要多做一次清除操作,但可靠性高得多。尤其是电机控制这类场景,丢一次中断可能就出事故。

中断响应:CPU如何“接电话”?

CPU收到中断请求后,不是立刻就去处理。它得先把手头的事收个尾,这个过程叫中断响应。

典型步骤:

  1. 关中断(防止嵌套乱套)
  2. 保存当前上下文(PC、寄存器等压栈)
  3. 查找中断向量表,找到对应的ISR入口地址
  4. 跳转到ISR执行

这里有个关键指标——中断延迟。从请求发出到CPU开始执行ISR的第一条指令,这个时间差就是延迟。我见过一个项目,中断延迟从5μs飙到50μs,查了半天发现是关中断时间太长——一个临界区里放了耗时操作。

注意: 中断响应不是越快越好。太快可能连当前指令都没执行完,导致数据不一致。ARM Cortex-M系列有个“咬尾中断”机制,能省掉一些压栈操作,但前提是你的ISR够短。

中断服务函数(ISR):干活要快,姿势要对

ISR怎么写,直接决定系统稳定性。我总结了几条铁律:

原则 说明 反面案例(我踩过的坑)
短小精悍 ISR里只做最必要的事 在ISR里调printf,直接卡死
无阻塞 不能有等待、轮询、延时 ISR里等SPI传输完成,其他中断全堵死
可重入 避免使用全局变量,或用锁保护 两个中断共享一个计数器,数据全乱
清除中断标志 退出前必须清标志,否则一直进 忘了清标志,CPU跑死在ISR里

我个人习惯是:ISR里只做三件事——读数据、清标志、发信号。真正的处理逻辑扔到任务级去做。你想想看,ISR里跑个复杂算法,其他中断全得等着,系统实时性从哪来?

// 好的ISR写法
void TIM2_IRQHandler(void) {
    // 1. 读数据
    uint32_t val = TIM2->SR;
    // 2. 清标志
    TIM2->SR = ~TIM_SR_UIF;
    // 3. 发信号给任务
    osSemaphoreRelease(mySem);
}

// 坏的ISR写法
void TIM2_IRQHandler(void) {
    // 千万别这么干!
    while(SPI->SR & SPI_SR_BSY);  // 阻塞等待
    HAL_Delay(10);                 // 延时
    printf("interrupt!\n");        // 重入问题
}

中断返回:怎么“挂电话”才不摔?

ISR执行完,CPU要恢复现场,回到被中断的地方继续执行。这个过程叫中断返回。

关键操作:

  • 恢复寄存器(出栈)
  • 开中断
  • 跳回原程序继续执行

这里有个容易忽略的点——中断嵌套。如果在ISR里开了中断,高优先级中断可以打断当前ISR。好处是紧急事件能及时处理,坏处是栈空间可能爆掉。我见过一个产品,嵌套深度到了7层,栈直接溢出,系统随机死机。

经验之谈: 中断嵌套不是必须的。大多数场景下,关中断处理完再开,比嵌套更安全。除非你有硬实时要求,比如高优先级中断必须在10μs内响应。

中断延迟与抖动:性能的“隐形杀手”

这两个指标直接决定系统的实时性:

  • 中断延迟:从请求到ISR开始执行的时间。影响因素包括:关中断时间、指令执行时间、总线仲裁等。
  • 抖动:延迟的最大波动范围。抖动大,说明系统响应不稳定。

我曾经调试一个音频采集系统,采样率48kHz,每个采样周期必须10μs内响应。结果抖动达到了±3μs,音频直接破音。查到最后,发现是DMA传输占用了总线,导致中断响应被延迟。

影响延迟和抖动的因素:

因素 影响 优化方向
关中断时间 延迟增加 缩短临界区,或使用无锁设计
ISR执行时间 延迟+抖动 ISR只做轻量操作
总线竞争 抖动 调整DMA优先级,或使用专用总线
缓存未命中 抖动 ISR代码和数据放在紧耦合内存
中断嵌套 延迟+抖动 限制嵌套深度,或禁用嵌套
避坑指南: 我曾经在ISR里用了一个大数组,结果每次进ISR都要从Flash加载,延迟直接多了几十个周期。后来把数组放到RAM里,问题解决。记住:ISR里用的数据,尽量放在最快的内存里。

完整流程:一张图看懂

下面这张图把整个中断响应流程串起来了。从外设发请求,到CPU响应、执行ISR、最后返回,每个环节都标出了关键动作和潜在风险点。

中断响应完整流程图 中断响应完整流程图 外设发出中断请求 CPU响应中断 关中断 保存上下文 查中断向量表 执行ISR 读数据/清标志 发信号给任务 清除中断标志 中断返回 关键指标:中断延迟(请求→ISR开始) | 抖动(延迟波动范围)

嗯,这里要注意:图中每个环节都可能引入延迟。关中断时间太长、ISR里做了耗时操作、总线被DMA占着——这些都会让中断延迟变大。我调试时习惯在每个环节打时间戳,用逻辑分析仪抓出来看,哪里慢了改哪里。

警告: 中断返回时如果忘了开中断,系统就再也进不了中断了。我见过一个同事调试了两天,最后发现是返回前少了一句开中断指令。这种低级错误,用代码审查就能避免。

中断响应流程,说白了就是一套“放下-处理-回来”的机制。每个环节都有优化空间,但前提是你得先理解它。我建议你下次调试中断问题时,别急着改代码,先拿逻辑分析仪把整个流程抓出来看看——延迟在哪、抖动在哪,一目了然。