2. 中断系统概述:中断向量表、中断优先级、中断嵌套与响应流程
在嵌入式系统中,中断是CPU处理异步事件的核心机制。对于GPIO中断驱动开发而言,理解中断系统的底层运作——从硬件信号触发到CPU执行中断服务函数(ISR)的完整路径——是编写可靠、低延迟驱动的先决条件。本节将围绕中断向量表、中断优先级、中断嵌套与响应流程四个核心概念展开,并结合ARM Cortex-M系列与Linux通用中断子系统进行对比说明。
2.1 中断向量表(Interrupt Vector Table, IVT)
中断向量表是一张存储在固定内存地址的表,表中每一项存放着对应中断源的中断服务函数入口地址。当CPU检测到中断请求时,硬件自动根据中断号从向量表中取出地址并跳转执行。
关键点: 向量表基地址在Cortex-M中由VTOR寄存器控制,支持运行时重定位;在Linux设备树中,中断控制器通过
interrupt-cells属性描述中断号与触发类型。
GPIO驱动视角: 每个GPIO引脚可映射到一个或多个中断号。在STM32中,EXTI线0~15对应中断向量EXTI0_IRQn ~ EXTI15_10_IRQn;在Linux中,GPIO中断通过
gpio_to_irq()获取虚拟IRQ号。
2.2 中断优先级(Interrupt Priority)
中断优先级决定了当多个中断同时发生时CPU优先响应哪一个,以及一个中断是否可以被另一个更高优先级的中断抢占。不同架构的优先级管理方式存在差异:
| 架构/子系统 | 优先级位数 | 数值与优先级关系 | 分组方式 | GPIO中断示例 |
|---|---|---|---|---|
| ARM Cortex-M3/M4 | 8位(实际使用高4位) | 数值越小,优先级越高 | 抢占优先级 + 子优先级(NVIC) | EXTI0_IRQn 可设为 0x10 |
| ARM Cortex-M7 | 8位(实际使用高4~8位) | 数值越小,优先级越高 | 抢占优先级 + 子优先级(NVIC) | EXTI9_5_IRQn 可设为 0x30 |
| RISC-V (PLIC) | 可配置(常见3位) | 数值越大,优先级越高 | 单一优先级域 | GPIO中断号0~31 |
| Linux IRQ子系统 | 由中断控制器驱动定义 | 依赖底层硬件 | 通过irq_set_priority()设置 |
gpio_to_irq() 返回虚拟IRQ |
常见陷阱: 在Cortex-M中,如果两个中断具有相同的抢占优先级,则不会发生嵌套——CPU会等待当前ISR执行完毕后再响应另一个中断。GPIO中断驱动中,务必为不同实时性要求的GPIO线分配不同的抢占优先级。
2.3 中断嵌套(Interrupt Nesting)
中断嵌套是指一个中断服务函数正在执行时,另一个更高优先级的中断到达,CPU暂停当前ISR,转而执行更高优先级的ISR,待其完成后返回被暂停的ISR继续执行。中断嵌套是提高系统实时性的关键机制。
GPIO驱动中的嵌套场景: 一个高优先级定时器中断可以抢占正在执行的GPIO按键中断ISR,确保时间关键任务(如PWM波形更新)不被GPIO抖动处理阻塞。
2.4 中断响应流程(Interrupt Response Flow)
从GPIO引脚电平变化到CPU执行对应的ISR,完整的硬件与软件协同流程可分为以下阶段:
- 中断源触发: GPIO引脚检测到设定的边沿(上升沿/下降沿/双边沿)或电平。
- 中断挂起: 外部中断控制器(如STM32的EXTI)将对应中断挂起寄存器位置1。
- 中断屏蔽检查: 检查中断屏蔽寄存器(IMR)对应位是否使能。
- 中断信号传递: 中断控制器向CPU核(如Cortex-M的NVIC)发送中断请求信号。
- 优先级裁决: NVIC比较当前中断优先级与正在执行的中断优先级,决定是否抢占。
- 硬件自动压栈: CPU自动将xPSR、PC、LR、R0~R3、R12压入当前栈。
- 向量表查找: CPU根据中断号从向量表中取出ISR入口地址。
- ISR执行: CPU跳转到ISR,执行用户编写的处理代码。
- 中断返回: ISR执行完毕,执行异常返回指令(如BX LR),CPU自动出栈恢复现场。
驱动开发优化点: 在ISR中应尽快完成必要操作(如读取GPIO数据寄存器、清除中断标志位),将耗时任务(如消息处理、协议解析)推迟到中断底半部(Bottom Half)或工作队列(Workqueue)中执行。
2.5 中断响应时间关键指标
| 指标 | 定义 | 典型值(Cortex-M4 @168MHz) | GPIO驱动影响 |
|---|---|---|---|
| 中断延迟 | 从中断请求到ISR第一条指令执行 | 12~16个CPU周期(约70~95ns) | 决定GPIO事件响应速度 |
| 中断响应时间 | 中断延迟 + ISR执行时间 | 取决于ISR长度 | 影响系统实时性上限 |
| 中断恢复时间 | 从ISR返回指令到主程序恢复 | 12个CPU周期 | 影响中断嵌套深度 |
| 最差情况中断延迟 | 考虑最高优先级中断屏蔽 + 最长指令执行 | 通常 < 1μs | 必须满足GPIO应用时序要求 |
驱动开发避坑: 在ISR中调用可能导致睡眠的函数(如
mutex_lock()、kmalloc(GFP_KERNEL))会触发内核调度,导致中断延迟急剧增大。GPIO中断ISR中应仅使用原子操作或spin_lock()。
2.6 中断标志位清除机制
中断服务函数执行完毕后,必须清除中断挂起标志位,否则中断控制器会持续向CPU发送中断请求,导致系统陷入死循环。不同硬件平台的清除方式不同:
- STM32 EXTI: 向EXTI_PR寄存器对应位写1清除。
- Linux GPIO中断: 由中断控制器驱动自动处理,用户ISR中通常不需要手动清除。
- Zynq GIC: 读取IAR寄存器自动清除中断标志。
/* STM32 GPIO中断ISR示例 - 手动清除中断标志 */
void EXTI0_IRQHandler(void)
{
/* 检查中断源 */
if (EXTI->PR & EXTI_PR_PR0) {
/* 执行GPIO中断处理逻辑 */
uint8_t pin_state = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0);
/* 清除中断挂起标志位(写1清除) */
EXTI->PR = EXTI_PR_PR0;
}
}
/* Linux GPIO中断ISR示例 - 无需手动清除 */
static irqreturn_t gpio_irq_handler(int irq, void *dev_id)
{
struct gpio_device *dev = (struct gpio_device *)dev_id;
/* 读取GPIO电平 */
int val = gpiod_get_value(dev->desc);
/* 调度工作队列处理耗时任务 */
schedule_work(&dev->work);
/* 返回IRQ_HANDLED,中断控制器驱动负责清除标志 */
return IRQ_HANDLED;
}
2.7 中断系统对比总结
| 概念 | 裸机/RTOS环境 | Linux内核环境 | GPIO驱动关注点 |
|---|---|---|---|
| 中断向量表 | 由链接脚本定义,固定或VTOR重定位 | 由中断控制器驱动管理,动态注册 | 确保GPIO中断号映射正确 |
| 中断优先级 | NVIC寄存器直接配置 | 通过irq_set_priority()或设备树 |
GPIO中断优先级需低于定时器中断 |
| 中断嵌套 | 硬件自动支持,需配置抢占优先级 | 支持,但需考虑中断上下文限制 | 避免在GPIO_ISR中调用睡眠函数 |
| 中断响应流程 | 硬件自动压栈 + 向量跳转 | 硬件流程 + 内核irq_enter/irq_exit | ISR应短小精悍,使用底半部机制 |
| 中断标志清除 | 手动写寄存器清除 | 由中断控制器驱动自动处理 | 裸机开发中极易遗漏,需格外注意 |
本节要点: 中断向量表是中断响应的入口映射表;中断优先级决定中断的抢占行为;中断嵌套提高了系统的实时响应能力;完整的中断响应流程涉及硬件自动压栈、向量查找、ISR执行和现场恢复。GPIO驱动开发者必须深刻理解这些机制,才能编写出高效、稳定的中断处理代码。