中断基础概念:从CPU被“打断”说起

各位同学,咱们今天聊聊中断。说白了,中断就是CPU正在干一件事,突然被一个外部或内部的事件“打断”,然后CPU停下当前工作,去处理这个紧急事件,处理完再回来接着干。

我在Jacinto平台上调BSP时,经常跟新人说:中断是嵌入式系统的“神经反射”。没有中断,CPU就得轮询——那效率低得没法看。你想想看,一个高速CAN报文来了,CPU要是不靠中断,得一直盯着寄存器,啥也别干了。

1.1 什么是中断?

中断的本质,是一个硬件信号。它告诉CPU:“嘿,有活儿来了,先停一下手头的事。”

中断源可以是:

  • 外部设备:比如GPIO按键按下、UART收到数据、DMA传输完成
  • 内部事件:比如定时器溢出、看门狗超时、软件触发的中断
  • 异常:比如除零错误、未定义指令、缺页异常(ARM里叫Abort)

我个人习惯把中断分成两类:IRQ(普通中断)和FIQ(快速中断)。在ARM Cortex-A系列里,FIQ有独立的寄存器组,响应更快。Jacinto平台的GIC(通用中断控制器)对这两种都支持,但实际项目中FIQ用得少,除非你对实时性有变态要求。

关键点:中断不是轮询。中断是事件驱动的,CPU只在需要时才响应。这就像你坐在工位上写代码,同事喊你“帮忙看个bug”——你放下键盘,看完再回来。轮询则是你每写一行代码就扭头问一遍“有bug吗?”——显然不现实。

1.2 中断向量表:CPU的“紧急联络簿”

中断向量表,说白了就是一张表。表里存的是每个中断对应的处理函数地址。CPU收到中断后,硬件自动查这张表,跳转到对应的函数去执行。

在ARM架构里,中断向量表通常放在内存的起始地址(0x00000000或0xFFFF0000)。Jacinto平台用的是Cortex-A系列,向量表基址可以通过VBAR寄存器设置。我记得第一次调Jacinto的BSP时,发现默认向量表在ROM里,想重映射得先配好VBAR,否则中断一进来就飞了。

一个典型的中断向量表结构如下:

; ARM Cortex-A 中断向量表(示例)
0x00000000:  Reset_Handler      ; 复位
0x00000004:  Undef_Handler      ; 未定义指令
0x00000008:  SVC_Handler        ; 系统调用
0x0000000C:  Prefetch_Handler   ; 预取指中止
0x00000010:  Data_Handler       ; 数据中止
0x00000014:  NMI_Handler        ; 不可屏蔽中断
0x00000018:  IRQ_Handler        ; 普通中断
0x0000001C:  FIQ_Handler        ; 快速中断

嗯,这里要注意:向量表里每个条目占4字节,存的是跳转指令(比如LDR PC, [PC, #offset])或者直接是函数地址。Jacinto的GIC会把外设中断映射到IRQ或FIQ入口,所以你在BSP里主要写IRQ_Handler和FIQ_Handler。

避坑指南:我曾经在移植U-Boot时,忘了把向量表拷贝到RAM里,结果一开中断就死机。后来查了半天,发现VBAR指向了ROM里的表,而ROM里的IRQ_Handler只是个死循环。所以,重定向向量表是BSP移植的第一步。

1.3 中断优先级:谁更紧急?

多个中断同时来了怎么办?CPU得有个“先来后到”的规则——这就是优先级。

在Jacinto平台,GIC支持最多256个优先级(0-255,0最高)。每个中断源可以独立配置优先级。当多个中断同时触发时,GIC会选优先级最高的那个送给CPU。

优先级配置的典型场景:

  • 高优先级:比如安全相关的看门狗、电源管理中断
  • 中优先级:比如实时性要求高的CAN、以太网
  • 低优先级:比如按键扫描、调试串口

我建议你在BSP里把优先级分成几个档次,别搞得太细。256级看着多,实际用4-8级就够了。分太细反而容易出优先级反转的问题。

优先级等级 典型中断源 说明
0-3(最高) 安全中断、FIQ 不可屏蔽或必须立即响应
4-7(高) 实时外设(CAN、以太网) 有严格延迟要求
8-15(中) DMA、定时器 允许少量延迟
16-31(低) GPIO、UART 可以排队处理

注意:优先级不是越高越好。你把所有中断都设成最高优先级,那跟没设一样。而且高优先级中断会抢占低优先级的,如果高优先级中断处理时间太长,低优先级的中断可能永远得不到响应——这叫“中断饿死”。

1.4 中断嵌套与抢占:中断里还能有中断?

中断嵌套,就是CPU正在处理一个中断时,又来了一个更高优先级的中断。CPU会暂停当前中断处理,去响应新的中断。处理完新的,再回来继续处理旧的。

抢占,就是高优先级中断“抢”低优先级中断的执行权。这跟RTOS里的任务抢占是一个道理。

在Jacinto的GIC里,中断嵌套是硬件支持的。但我在项目中遇到过一个问题:嵌套太深容易爆栈。每个中断都有自己的栈帧,嵌套一层就多压一次栈。如果嵌套个七八层,栈空间可能就不够了。

我一般建议:

  • 中断处理函数尽量短:只做必要的操作,比如读数据、清标志、发信号量。耗时操作放到任务里做。
  • 控制嵌套深度:在BSP里可以设置优先级掩码,让某些中断不能嵌套。比如把优先级低于某个值的中断都屏蔽掉,等当前中断处理完再打开。
  • 使用FIQ做紧急处理:FIQ有独立的栈和寄存器,不会跟IRQ抢资源。

实战经验:我在调Jacinto的以太网驱动时,发现网络中断优先级设得太高,结果它把定时器中断给抢了。定时器是系统心跳,一丢就导致调度异常。后来我把网络中断优先级降了一级,问题解决。所以,优先级不是越高越好,得看系统整体需求。

1.5 知识结构总览

下面这张图,把中断的核心概念串起来了。你可以把它当作本章的“思维导图”。

中断基础概念知识结构 中断系统 中断定义 中断向量表 中断优先级 中断嵌套与抢占 硬件信号 事件驱动 函数地址表 VBAR寄存器 0-255级 GIC配置 硬件支持 栈深度控制 优先级掩码 核心:中断是CPU的“紧急响应机制”, 向量表是“联络簿”,优先级是“排序规则”,嵌套是“多级响应”

这张图把中断的四个核心概念串在了一起。你可以看到:中断定义是基础,向量表是入口,优先级是规则,嵌套与抢占是运行时行为。这四个概念,是后面所有中断管理代码的基石。

学习建议:别急着看代码。先把这四个概念搞透。我见过太多人一上来就写中断处理函数,结果连向量表在哪都不知道。基础不牢,地动山摇。