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 实际设计中的几个要点

最后,分享几个我在项目中总结的要点:

  1. 中断延迟要量化:从外设发出中断请求,到 CPU 开始执行第一行中断服务程序,这个时间叫中断延迟。我一般要求这个值在 10~100 个 CPU 周期以内。超过这个范围,就要检查是不是优先级配置有问题,或者中断服务程序写得太长。
  2. 中断嵌套要谨慎:高优先级中断可以打断低优先级中断,这叫嵌套。但嵌套层数太多,栈空间会暴涨。我见过一个项目嵌套了 7 层,结果栈溢出,系统复位——查了三天才找到原因。
  3. 共享中断要加锁:多个外设共享一个中断号时,中断服务程序里要轮询每个外设的状态寄存器。这时候一定要加锁,防止中断重入导致状态错乱。

好了,中断控制器的基础就聊到这儿。说白了,它就是 SoC 里那个「最忙的管家」——既要接住所有外设的电话,又要按轻重缓急安排 CPU 去处理。设计得好,系统响应如丝般顺滑;设计得不好,那就是一场灾难。

下一章咱们聊聊实际的中断控制器编程——怎么配置寄存器、怎么写中断服务程序、怎么调试中断问题。到时候我会拿一个真实的 GIC-400 例子来讲,保证干货满满。