1. 中断基础概念
中断(Interrupt)是嵌入式系统与 Linux 内核驱动开发中最核心的异步事件处理机制。它允许硬件或软件在特定事件发生时,暂停当前正在执行的程序,转而执行一个优先级更高的中断服务程序(ISR),处理完成后恢复原程序的执行。理解中断的定义、分类、向量表与中断号,是后续进行中断申请与驱动移植的基石。
1.1 中断的定义与核心机制
中断的本质是一种硬件或软件触发的信号,用于通知处理器发生了需要立即关注的事件。与轮询(Polling)方式相比,中断机制能显著提高 CPU 利用率,避免 CPU 在等待事件时空转。
中断 vs 轮询 对比
| 特性 | 中断驱动 | 轮询方式 |
|---|---|---|
| CPU 利用率 | 高,无事时休眠或执行其他任务 | 低,持续检查状态寄存器 |
| 响应延迟 | 低,事件触发立即响应 | 高,取决于轮询周期 |
| 实现复杂度 | 较高,需配置中断控制器与向量表 | 简单,仅需循环读取状态 |
| 适用场景 | 低频或突发性事件(按键、网络包) | 高频可预测事件(ADC 连续采样) |
中断处理流程通常包含以下步骤:
- 中断请求:外设或软件通过中断线向中断控制器(如 GIC、APIC)发送信号。
- 中断仲裁:中断控制器根据优先级决定是否向 CPU 发送 IRQ 信号。
- CPU 响应:CPU 保存当前上下文(PC、PSW 等),跳转到中断向量表对应入口。
- ISR 执行:执行中断服务程序,处理硬件事件。
- 中断返回:恢复上下文,继续执行被中断的程序。
1.2 中断类型
根据触发源与实现方式,中断可分为以下三大类:
| 中断类型 | 触发源 | 特点 | 典型示例 |
|---|---|---|---|
| 外部中断 | 外部硬件设备(GPIO、定时器、DMA) | 异步、由硬件引脚电平或边沿触发 | 按键按下、UART 接收数据 |
| 内部中断 | CPU 内部异常(除零、缺页、系统调用) | 同步、与指令执行直接相关 | 除零异常、页错误、SWI 指令 |
| 软中断 | 软件主动触发(系统调用、任务切换) | 同步、由程序指令发起 | Linux 系统调用(svc)、tasklet |
驱动开发关注点: 在 Linux 设备驱动中,最常处理的是外部中断(通过 request_irq() 注册)。内部中断通常由内核异常处理框架接管,软中断则用于下半部(Bottom Half)机制,如 tasklet 和 workqueue。
1.3 中断向量表与中断号
中断向量表(Interrupt Vector Table, IVT) 是存储在固定内存地址的一张表,表中每一项对应一个中断号,保存着该中断对应的中断服务程序(ISR)入口地址。当 CPU 响应中断时,硬件自动根据中断号从向量表中取出地址并跳转执行。
中断号(IRQ Number) 是系统为每个中断源分配的整数标识符。在 Linux 内核中,中断号是驱动申请中断时使用的关键参数。
| 概念 | 说明 | Linux 内核对应 |
|---|---|---|
| 硬件中断号 | 由 SoC 或中断控制器定义的物理编号 | irq_desc[] 数组索引 |
| 软件中断号 | Linux 内核虚拟化后的逻辑中断号 | request_irq() 的第一个参数 |
| 中断向量 | CPU 架构相关的异常向量编号(如 ARM 的 IRQ 向量 0x18) | arch/arm/kernel/entry-armv.S |
常见误区: 中断号与 GPIO 编号不是同一概念。GPIO 中断需要先通过 gpio_to_irq() 将 GPIO 号转换为对应的 Linux 中断号,才能用于 request_irq()。直接使用 GPIO 编号注册中断会导致驱动加载失败。
1.4 中断号在 Linux 驱动中的使用
在 Linux 内核中,中断号是驱动与中断子系统交互的核心标识。以下是一个典型的中断申请代码片段,展示了中断号的使用方式:
#include <linux/interrupt.h>
#include <linux/gpio.h>
/* 中断服务程序 */
static irqreturn_t my_irq_handler(int irq, void *dev_id)
{
/* 处理中断事件 */
pr_info("Interrupt received on IRQ %d\n", irq);
return IRQ_HANDLED;
}
static int __init my_driver_init(void)
{
int irq_num;
int gpio_pin = 42; /* 假设 GPIO 42 */
/* 将 GPIO 号转换为 Linux 中断号 */
irq_num = gpio_to_irq(gpio_pin);
if (irq_num < 0) {
pr_err("Failed to map GPIO %d to IRQ\n", gpio_pin);
return irq_num;
}
/* 申请中断 */
if (request_irq(irq_num, my_irq_handler,
IRQF_TRIGGER_RISING,
"my_device", NULL)) {
pr_err("Failed to request IRQ %d\n", irq_num);
return -EBUSY;
}
pr_info("Driver loaded, IRQ %d registered\n", irq_num);
return 0;
}
static void __exit my_driver_exit(void)
{
int irq_num = gpio_to_irq(42);
free_irq(irq_num, NULL);
pr_info("Driver unloaded\n");
}
module_init(my_driver_init);
module_exit(my_driver_exit);
关键点总结:
- 中断号是驱动申请中断的唯一凭证,必须通过平台设备、设备树或 gpio_to_irq() 获取。
- 中断向量表由 CPU 架构和启动代码初始化,驱动开发者通常不需要直接操作向量表。
- 外部中断、内部中断、软中断在 Linux 内核中有不同的处理路径,驱动主要关注外部中断的申请与释放。