1. 中断基础概念

中断(Interrupt)是嵌入式实时系统中处理器与外部世界异步交互的核心机制。它允许硬件设备在需要服务时主动通知处理器,从而避免处理器轮询(Polling)带来的CPU资源浪费。理解中断的基础概念是掌握中断处理驱动开发的基石。

1.1 中断定义与分类

中断是一种硬件或软件触发的电信号,它使处理器暂停当前正在执行的程序,保存现场(Context),转而执行一个预先定义好的中断服务程序(Interrupt Service Routine, ISR),执行完毕后再恢复现场,继续执行被中断的程序。

核心要点:中断的本质是“异步事件驱动”——处理器不需要主动查询设备状态,设备通过中断线“通知”处理器。

中断按触发来源可分为以下几类:

分类 触发源 典型示例 特点
硬件中断 外部设备(外设) UART接收数据、定时器溢出、GPIO边沿触发 异步、随机、与处理器时钟无关
软件中断 CPU执行指令 系统调用(SWI/SVC)、断点指令(BKPT) 同步、由程序主动触发
异常 CPU内部错误或特殊状态 除零错误、未定义指令、页故障 同步、通常不可屏蔽
实时系统视角:在RTOS中,硬件中断的响应延迟(Interrupt Latency)是衡量系统实时性的关键指标。中断定义中的“保存现场”和“恢复现场”操作耗时,直接影响中断延迟。

1.2 中断向量表(Interrupt Vector Table, IVT)

中断向量表是一张存储在内存固定地址的表格,表中每一项存放着对应中断服务程序(ISR)的入口地址。当处理器接收到中断信号并完成仲裁后,会通过中断号(Vector Number)索引IVT,跳转到对应的ISR执行。

IVT的结构因处理器架构而异:

  • ARM Cortex-M系列:向量表起始地址默认为0x00000000,前16个为系统异常向量,从第16个开始为外部中断向量。向量表项存放的是函数地址(而非跳转指令)。
  • x86架构:实模式下IVT位于0x00000~0x003FF(256个中断向量,每个4字节);保护模式下使用中断描述符表(IDT),每个描述符8字节,包含段选择子和偏移量。
  • RISC-V:使用机器模式中断向量表(mtvec寄存器指向),支持直接向量模式和向量化模式。
中断向量表(IVT)结构示意图 中断向量表 向量0:复位 向量1:NMI 向量2:硬故障 ... 向量16:IRQ0 向量17:IRQ1 CPU收到中断号16 CPU收到中断号17 内存中的ISR代码 ISR_Reset_Handler() 地址:0x08000004 ISR_NMI_Handler() 地址:0x08000008 ISR_UART_RX_Handler() 地址:0x08000234 ISR_TIMER_Handler() 地址:0x08000456
常见陷阱:在ARM Cortex-M上,向量表第0项存放的是栈顶指针(MSP),而非ISR地址。如果开发者在链接脚本中错误放置向量表偏移,会导致系统上电后无法正确初始化堆栈,直接进入HardFault。

1.3 中断号(Interrupt Number)

中断号是系统为每个中断源分配的唯一的数字标识符。处理器和中断控制器使用中断号来区分不同的中断请求,并索引中断向量表找到对应的ISR。

中断号的分配规则:

  • 系统异常:通常占用最低的几个编号(如ARM Cortex-M的1~15号),用于复位、NMI、硬故障等。
  • 外部中断:从某个基数开始连续编号(如ARM Cortex-M从16号开始),每个外设中断源占用一个唯一编号。
  • 软件中断:由操作系统或应用程序通过写中断控制器寄存器触发,编号通常位于外部中断之后或由软件指定。
架构 中断号范围 外部中断起始号 最大中断数
ARM Cortex-M3/M4 1 ~ 240 16 240(含系统异常)
ARM Cortex-A72 0 ~ 987 32(PPI) 988(GICv3)
x86 APIC 0 ~ 255 32(IRQ0~IRQ15) 256
RISC-V(CLINT) 0 ~ 15(机器模式) 3(MEI) 16
驱动开发提示:在Linux设备树(Device Tree)中,中断号通过 interrupts = <GIC_SPI 42 IRQ_TYPE_LEVEL_HIGH> 这样的属性指定。其中42是相对于GIC SPI(共享外设中断)基址的偏移量,驱动开发者需要查阅芯片手册确认每个外设的中断号映射。

1.4 中断控制器(GIC)简介

通用中断控制器(Generic Interrupt Controller, GIC)是ARM架构中用于管理中断的核心硬件单元。它负责接收来自各个外设的中断请求,进行优先级仲裁、分发和屏蔽,最终将中断信号发送给指定的CPU核心。GIC是构建多核、实时性要求高的嵌入式系统的关键组件。

GIC的架构经历了多个版本迭代,目前主流的是GICv2和GICv3:

特性 GICv2 GICv3 GICv4
CPU接口 内存映射寄存器(CPU Interface) 系统寄存器(SysReg)访问 系统寄存器 + 虚拟化扩展
中断类型 SGI/PPI/SPI/LPI SGI/PPI/SPI/LPI + 扩展SPI 增加虚拟中断(vLPI)
最大中断数 1020 988(SPI)+ 扩展 与GICv3相同
多核支持 支持,但亲和性配置有限 增强亲和性路由(GICR) 支持虚拟化直通
实时性 中等,优先级分组固定 高,支持抢占和优先级掩码 高,支持虚拟中断直接注入

GIC内部的核心组件包括:

  • 分发器(Distributor, GICD):全局管理所有中断源的使能、优先级、触发方式(边沿/电平)、目标CPU亲和性。
  • CPU接口(CPU Interface, GICC):每个CPU核心独享,负责向核心发送中断信号、应答中断、完成中断。
  • 重分发器(Redistributor, GICR):GICv3引入,每个CPU核心对应一个GICR,管理PPI和SGI的配置,减少全局分发器的竞争。
  • 中断翻译服务(ITS, GIC ITS):GICv3可选组件,用于管理LPI(本地特定中断)的映射和路由,支持MSI(消息信号中断)。
GICv3 中断控制器架构图 外设A SPI #42 外设B SPI #55 定时器 PPI #29 GIC 分发器 (GICD) 中断使能/屏蔽 优先级配置 触发方式选择 目标CPU路由 中断分组 重分发器0 (GICR0) PPI/SGI管理 重分发器1 (GICR1) PPI/SGI管理 CPU接口0 (GICC0) → CPU0 CPU接口1 (GICC1) → CPU1 GIC中断类型说明 • SGI(Software Generated Interrupt):软件生成中断,用于核间通信(IPI),目标由GICR寄存器指定。 • PPI(Private Peripheral Interrupt):私有外设中断,每个CPU核心独享,如本地定时器、PMU。 • SPI(Shared Peripheral Interrupt):共享外设中断,可由分发器路由到任意CPU核心,如UART、DMA。 • LPI(Locality-specific Peripheral Interrupt):本地特定中断,通过ITS管理,支持大量中断源(MSI)。
实时性关键点:GIC的优先级分组(Priority Group)和抢占(Preemption)机制直接影响中断嵌套行为。在GICv2中,通过二进制点(Binary Point)寄存器控制抢占阈值;GICv3则支持更灵活的优先级掩码(Priority Mask)和运行优先级(Running Priority),允许高优先级中断抢占低优先级中断服务程序,这对实时操作系统(RTOS)的中断管理至关重要。

1.5 中断处理流程概览

一个完整的中断处理流程从硬件信号触发到软件ISR执行完毕,通常包含以下步骤:

  1. 中断请求(IRQ):外设将中断信号发送到中断控制器(如GIC)的引脚或消息通道。
  2. 中断仲裁:GIC分发器根据优先级、使能状态和目标CPU配置,选择最高优先级的中断,将其路由到目标CPU核心。
  3. 中断通知:GIC CPU接口向CPU核心发送IRQ信号(或FIQ信号,在ARM中用于快速中断)。
  4. 现场保存:CPU硬件自动压栈(Push)当前程序的上下文(PC、PSR、LR、R0-R3、R12等),并切换到中断模式(或Handler模式)。
  5. 向量读取:CPU从中断向量表中读取对应中断号的ISR入口地址。
  6. ISR执行:CPU跳转到ISR执行。ISR通常分为顶半部(Top Half,快速响应)和底半部(Bottom Half,延迟处理),这部分将在后续章节详细展开。
  7. 中断完成:ISR执行完毕后,CPU执行中断返回指令(如ARM的BX LR或MOVS PC, LR),恢复现场,继续执行被中断的程序。
中断处理流程(硬件+软件协作) ① 外设触发中断 硬件信号 ② GIC仲裁 优先级/路由 ③ CPU响应 保存现场 ④ 读取向量表 获取ISR地址 ⑤ ISR 执行 ⑥ 中断返回:恢复现场,继续执行 关键时序指标 • 中断延迟(Interrupt Latency):从步骤①到步骤⑤开始执行的时间,通常要求 < 10μs(硬实时系统)。 • 中断响应时间(Interrupt Response Time):从步骤①到步骤⑥完成的时间,受ISR执行长度和中断嵌套深度影响。
驱动开发实践:在Linux内核中,中断处理流程的步骤④~⑤由handle_IRQ()或__handle_domain_irq()函数完成。驱动开发者注册的中断处理函数(通过request_irq())在步骤⑤中被调用。理解这个流程有助于定位中断丢失、延迟过高或死锁等问题。