4. 中断系统基础:中断向量、中断优先级、中断嵌套、中断服务程序(ISR)设计原则
在嵌入式系统中,中断是实现实时响应与异步事件处理的核心机制。对于热插拔检测场景,GPIO 电平变化必须通过中断系统快速传递至 CPU,进而触发驱动层的安全恢复流程。理解中断向量、优先级、嵌套以及 ISR 设计原则,是编写可靠驱动的基础。
核心概念速览: 中断向量表是中断源的“入口索引”;中断优先级决定多个中断同时触发时的响应顺序;中断嵌套允许高优先级中断抢占低优先级 ISR;ISR 设计原则确保系统稳定与数据一致。
4.1 中断向量与中断向量表
中断向量(Interrupt Vector)是每个中断源对应的固定入口地址。CPU 在响应中断时,通过硬件自动查找中断向量表(IVT, Interrupt Vector Table),跳转到对应的 ISR 执行。
- 向量表基址: 通常位于存储器最低地址区域(如 ARM Cortex-M 的 0x00000000),或通过 VTOR 寄存器重定位。
- 向量号: 每个中断源分配唯一的向量号(如 IRQ0 ~ IRQn),CPU 根据向量号计算偏移。
- 热插拔场景: GPIO 中断通常映射到 EXTI(外部中断/事件控制器)的固定向量号,如 STM32 的 EXTI0 ~ EXTI15。
驱动提示: 在 Linux 设备树中,通过
interrupts = <GIC_SPI 42 IRQ_TYPE_EDGE_RISING> 指定中断控制器、中断号与触发类型,内核据此注册中断处理函数。
4.2 中断优先级与中断嵌套
中断优先级(Priority)决定多个中断同时请求时 CPU 的响应顺序。中断嵌套(Nesting)允许高优先级中断打断正在执行的低优先级 ISR,实现实时抢占。
| 特性 | 固定优先级(传统) | 可编程优先级(NVIC) |
|---|---|---|
| 优先级数量 | 通常 2 级(高/低) | 8~256 级(取决于实现) |
| 抢占能力 | 不支持嵌套 | 支持嵌套(高优先级抢占低优先级) |
| 子优先级 | 无 | 支持(组优先级 + 子优先级) |
| 热插拔适用性 | 低(可能丢失事件) | 高(可配置紧急程度) |
嵌套风险: 中断嵌套可能导致栈溢出或优先级反转。在热插拔驱动中,建议将 GPIO 中断设为中等优先级,避免与定时器、DMA 等高优先级中断产生深度嵌套。
4.3 中断服务程序(ISR)设计原则
ISR 是中断响应的核心执行单元。设计不当的 ISR 会导致系统延迟、数据损坏甚至死锁。以下是热插拔检测场景下的关键设计原则:
- 短小精悍: ISR 应尽快完成关键操作(如读取 GPIO 状态、清除中断标志),耗时任务(如协议解析、驱动恢复)移至任务上下文。
- 避免阻塞: 禁止在 ISR 中使用自旋锁、信号量、延时函数或阻塞 I/O。
- 数据一致性: 使用 volatile 关键字声明共享变量,必要时使用原子操作或关中断保护。
- 中断标志清除: 在 ISR 入口或出口及时清除中断挂起位,防止重复触发。
- 优先级分组: 合理配置抢占优先级与子优先级,确保热插拔事件不被低优先级任务淹没。
热插拔 ISR 典型流程:
1. 读取 GPIO 电平状态(确认插拔方向)
2. 清除 EXTI 中断挂起位
3. 将事件信息写入共享缓冲区(volatile)
4. 触发任务通知(如 Linux 的
5. 退出 ISR
1. 读取 GPIO 电平状态(确认插拔方向)
2. 清除 EXTI 中断挂起位
3. 将事件信息写入共享缓冲区(volatile)
4. 触发任务通知(如 Linux 的
tasklet 或 workqueue)5. 退出 ISR
4.4 中断下半部机制与热插拔恢复
为了满足 ISR 短小原则,Linux 内核提供了多种下半部机制,将耗时操作推迟到更安全的上下文执行:
| 机制 | 执行上下文 | 可睡眠 | 适用场景 |
|---|---|---|---|
| Tasklet | 软中断上下文 | 否 | 快速处理,不可阻塞 |
| Workqueue | 进程上下文 | 是 | 热插拔驱动恢复、I2C/SPI 通信 |
| Threaded IRQ | 内核线程 | 是 | 需要互斥锁或长时间操作 |
推荐实践: 在热插拔 GPIO 中断驱动中,使用
request_threaded_irq() 注册中断,将硬件操作放在顶半部(清除标志),将设备枚举、电源管理等恢复逻辑放在底半部线程中。
/* 热插拔 GPIO 中断驱动示例(Linux 内核) */
static irqreturn_t hotplug_irq_handler(int irq, void *dev_id)
{
struct hotplug_dev *hpdev = (struct hotplug_dev *)dev_id;
/* 顶半部:读取状态,清除中断 */
hpdev->status = gpio_get_value(hpdev->gpio_pin);
disable_irq_nosync(irq); /* 防止重复触发 */
/* 触发底半部 workqueue */
schedule_work(&hpdev->work);
return IRQ_HANDLED;
}
static void hotplug_work_handler(struct work_struct *work)
{
struct hotplug_dev *hpdev = container_of(work, struct hotplug_dev, work);
/* 底半部:执行安全恢复(可睡眠) */
mutex_lock(&hpdev->lock);
if (hpdev->status == 1) {
/* 设备插入:上电、枚举 */
regulator_enable(hpdev->vcc);
usb_hub_enumerate(hpdev->hub);
} else {
/* 设备拔出:断电、清理 */
usb_hub_disconnect(hpdev->hub);
regulator_disable(hpdev->vcc);
}
mutex_unlock(&hpdev->lock);
enable_irq(hpdev->irq); /* 重新使能中断 */
}
/* 中断注册 */
ret = request_threaded_irq(gpio_to_irq(gpio_pin),
hotplug_irq_handler, /* 顶半部 */
NULL, /* 无单独线程化底半部 */
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
"hotplug_gpio", hpdev);
安全恢复关键点: 在底半部中必须重新使能中断(
enable_irq()),并确保互斥锁保护共享资源。若使用 request_threaded_irq() 的 irq_handler 返回 IRQ_WAKE_THREAD,内核会自动调度线程化底半部。
4.5 中断延迟与热插拔实时性
中断延迟(Interrupt Latency)指从硬件触发中断到 CPU 执行第一条 ISR 指令的时间。在热插拔检测中,过大的延迟可能导致设备状态误判或电气损坏。
- 影响因素: 中断屏蔽时间、更高优先级中断执行时间、总线事务延迟。
- 测量方法: 使用 GPIO 翻转 + 示波器,或内核的
ftrace工具。 - 优化手段: 减少临界区长度、使用快速中断(FIQ)、将非关键中断优先级降低。
热插拔延迟预算参考:
• GPIO 中断响应:< 10 µs(典型)
• ISR 顶半部执行:< 50 µs
• 底半部调度延迟:< 1 ms
• 完整恢复流程:< 10 ms(含设备枚举)
• GPIO 中断响应:< 10 µs(典型)
• ISR 顶半部执行:< 50 µs
• 底半部调度延迟:< 1 ms
• 完整恢复流程:< 10 ms(含设备枚举)