3、中断控制器基础:中断控制器的功能、中断向量表、中断优先级
好,咱们今天聊聊中断控制器。这东西,说白了就是 SoC 里负责「接电话」和「排顺序」的管家。
我刚开始做 SoC 集成那会儿,总觉得中断控制器就是个简单的「或门」加几个寄存器。后来被坑过几次才明白——它才是整个系统响应能力的核心。你想想看,CPU 就那么几个外部中断脚,但 SoC 里外设几十上百个,谁有资格打断 CPU?谁先谁后?这些都得靠中断控制器来管。
3.1 中断控制器的核心功能
中断控制器到底干些什么?我归纳了三个核心职责:
- 中断收集与合并:把多个外设的中断请求合并成一条或几条线送给 CPU。比如 GIC(通用中断控制器)可以管理上千个中断源。
- 中断屏蔽与使能:每个中断源都可以独立打开或关闭。我在调试一个 USB 控制器时,就遇到过中断一直狂触发的情况——查了半天,原来是 bootloader 没关某个外设的中断使能。
- 中断分发与路由:在多核系统里,中断可以指定发给哪个核。甚至可以做到「负载均衡」,让多个核分担中断处理。
关键点:中断控制器不是简单的「线与」。它要处理电平触发、边沿触发、脉冲触发等多种信号类型。我见过有人把边沿触发的中断源接到了电平触发的控制器上,结果丢中断丢得莫名其妙。
3.2 中断向量表——中断的「黄页」
中断向量表,你可以把它理解成一本电话簿。CPU 收到中断后,得知道该跳到哪里去执行代码。这个地址就存在向量表里。
在 ARM Cortex-M 系列里,向量表默认放在起始地址 0x00000000。但实际项目中,我习惯把它重定位到 RAM 里——这样可以在运行时动态修改,方便做 bootloader 和固件升级。
// 以 ARM Cortex-M 为例,向量表结构
typedef struct {
uint32_t stack_top; // 栈顶地址
uint32_t reset_handler; // 复位向量
uint32_t nmi_handler; // NMI 异常
uint32_t hard_fault_handler; // 硬错误
// ... 其他异常向量
uint32_t irq0_handler; // 外部中断 0
uint32_t irq1_handler; // 外部中断 1
// ... 以此类推
} vector_table_t;
嗯,这里要注意:向量表里存的不是函数体,而是函数的入口地址。CPU 拿到这个地址后,直接跳转过去执行。我曾经在项目里犯过一个低级错误——把函数指针赋值成了函数返回值,结果一进中断就跑飞了。
个人经验:我建议在向量表里给每个中断都预留一个「默认处理函数」。哪怕这个中断当前没用,也填一个指向死循环或打印错误信息的函数。这样一旦有意外触发,你能立刻发现,而不是让系统静默崩溃。
3.3 中断优先级——谁说了算?
中断优先级,说白了就是「谁更紧急」。但这里有个坑:不同架构的优先级定义完全不一样。
| 架构 | 优先级数值含义 | 可配置位数 | 特点 |
|---|---|---|---|
| ARM Cortex-M (NVIC) | 数值越小,优先级越高 | 4~8 位 | 支持抢占优先级和子优先级 |
| RISC-V (PLIC) | 数值越大,优先级越高 | 可配置 | 支持中断优先级阈值 |
| x86 (APIC) | 数值越大,优先级越高 | 4 位 | 固定优先级与可编程结合 |
你看,ARM 和 RISC-V 的优先级方向是反的。我刚从 ARM 转到 RISC-V 项目时,就因为这个习惯性写反了优先级配置——结果高优先级的中断一直被低优先级的打断,系统响应一塌糊涂。
优先级设计上,我个人的习惯是:
- 时间关键型中断(比如定时器、实时时钟)给最高优先级
- 数据流中断(比如 DMA、以太网)给中等优先级
- 事件型中断(比如按键、GPIO)给最低优先级
但这不是绝对的。有一次我在做工业控制项目,一个外部急停信号必须用最高优先级——哪怕它在设计文档里只是个 GPIO 中断。你想想看,安全相关的信号,优先级怎么高都不为过。
避坑指南:我曾经在一个多核 SoC 项目里,把两个核的中断优先级配成了一样,结果发生了「中断风暴」——两个核同时响应同一个中断,互相抢锁,系统直接 hang 住。记住:多核系统里,中断的 affinity(亲和性)和优先级要一起考虑。
3.4 实际设计中的几个要点
最后,分享几个我在项目中总结的要点:
- 中断延迟要量化:从外设发出中断请求,到 CPU 开始执行第一行中断服务程序,这个时间叫中断延迟。我一般要求这个值在 10~100 个 CPU 周期以内。超过这个范围,就要检查是不是优先级配置有问题,或者中断服务程序写得太长。
- 中断嵌套要谨慎:高优先级中断可以打断低优先级中断,这叫嵌套。但嵌套层数太多,栈空间会暴涨。我见过一个项目嵌套了 7 层,结果栈溢出,系统复位——查了三天才找到原因。
- 共享中断要加锁:多个外设共享一个中断号时,中断服务程序里要轮询每个外设的状态寄存器。这时候一定要加锁,防止中断重入导致状态错乱。
好了,中断控制器的基础就聊到这儿。说白了,它就是 SoC 里那个「最忙的管家」——既要接住所有外设的电话,又要按轻重缓急安排 CPU 去处理。设计得好,系统响应如丝般顺滑;设计得不好,那就是一场灾难。
下一章咱们聊聊实际的中断控制器编程——怎么配置寄存器、怎么写中断服务程序、怎么调试中断问题。到时候我会拿一个真实的 GIC-400 例子来讲,保证干货满满。