中断基础概念:从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 知识结构总览
下面这张图,把中断的核心概念串起来了。你可以把它当作本章的“思维导图”。
这张图把中断的四个核心概念串在了一起。你可以看到:中断定义是基础,向量表是入口,优先级是规则,嵌套与抢占是运行时行为。这四个概念,是后面所有中断管理代码的基石。
学习建议:别急着看代码。先把这四个概念搞透。我见过太多人一上来就写中断处理函数,结果连向量表在哪都不知道。基础不牢,地动山摇。