2. 中断基础概念:中断向量、中断优先级、中断嵌套、中断上下文与进程上下文
在嵌入式 Linux 系统中,中断是 CPU 响应外部或内部异步事件的硬件机制。理解中断的核心概念是编写可靠 GPIO 中断驱动的基石。本节将逐一剖析中断向量、中断优先级、中断嵌套以及中断上下文与进程上下文的区别。
2.1 中断向量与中断向量表
中断向量是每个中断源对应的唯一标识符,通常是一个整数。CPU 在接收到中断信号后,通过中断向量在中断向量表(IVT)中查找对应的中断服务程序(ISR)入口地址。
- 中断向量号:由硬件或系统软件分配,例如 ARM GIC 使用中断 ID(0~1019)。
- 中断向量表:存储在内存固定地址的跳转表,每个表项指向一个 ISR。
- Linux 中的映射:内核通过
irq_desc结构体将硬件中断号映射到 Linux IRQ 号。
关键点: 在 ARM 平台上,中断向量表通常位于 0x00000000 或 0xFFFF0000(高向量模式)。Linux 启动时会初始化并重定位向量表。
2.2 中断优先级
中断优先级决定了当多个中断同时发生时,CPU 优先响应哪一个。高优先级中断可以抢占低优先级中断的执行。
| 优先级模型 | 描述 | 典型应用 |
|---|---|---|
| 固定优先级 | 每个中断源分配固定优先级,不可动态调整 | 简单 MCU,如 8051 |
| 可编程优先级 | 软件可配置每个中断的优先级等级 | ARM GIC、x86 APIC |
| 轮转优先级 | 优先级在中断处理后循环移位,实现公平 | 某些 DSP 或实时系统 |
实践提示: 在 Linux 中,可以通过
/proc/irq/<irq_num>/smp_affinity 和 irq_set_affinity_hint() 调整中断亲和性,间接影响优先级行为。但真正的优先级控制由中断控制器硬件完成。
2.3 中断嵌套
中断嵌套是指一个中断服务程序正在执行时,另一个更高优先级的中断到达,CPU 暂停当前 ISR 转而执行新的 ISR 的现象。
- 硬件支持:需要中断控制器支持优先级屏蔽和自动保存/恢复上下文。
- Linux 中的限制:Linux 内核默认在 ISR 中关闭本地 CPU 中断(
local_irq_disable()),因此不推荐在常规 ISR 中发生嵌套。但使用request_threaded_irq()的线程化中断可以在内核线程中打开中断,允许嵌套。 - 嵌套深度:受限于堆栈空间和中断控制器硬件寄存器数量。
注意: 过度嵌套可能导致堆栈溢出或优先级反转。在 GPIO 驱动中,建议将耗时操作推迟到
tasklet 或 workqueue 中执行,避免在 ISR 中长时间关中断。
2.4 中断上下文与进程上下文
理解中断上下文和进程上下文的区别,是编写安全、高效中断驱动的关键。两者在可调用函数、可访问内存、调度行为等方面有本质差异。
| 特性 | 中断上下文 | 进程上下文 |
|---|---|---|
| 执行环境 | 在 ISR 或 tasklet 中执行 | 在系统调用、内核线程或用户进程中执行 |
| 可睡眠 | 不允许(会导致死锁) | 允许(通过 schedule()) |
| 可访问用户空间 | 不允许(无进程地址空间) | 允许(通过 copy_from_user()) |
| 持有锁 | 只能使用自旋锁(spinlock) | 可以使用互斥锁(mutex) |
| 调度行为 | 不可抢占(除非使用 threaded IRQ) | 可被抢占 |
| 典型函数 | writel(), ioread32(), tasklet_schedule() |
kmalloc(GFP_KERNEL), msleep() |
黄金法则: 在中断上下文中,永远不要调用可能导致睡眠的函数(如
kmalloc(GFP_KERNEL)、mutex_lock()、copy_from_user())。如果需要执行这些操作,请使用 request_threaded_irq() 将中断处理线程化。
2.5 在 GPIO 驱动中的应用
在 GPIO 中断驱动中,上述概念直接影响代码设计:
- 中断向量:GPIO 中断通常对应多个中断号(如 GPIO IRQ 0~N),需要在设备树或平台数据中正确映射。
- 优先级:对于关键 GPIO(如电源按键、紧急停止),应配置为高优先级中断。
- 嵌套:避免在 GPIO ISR 中执行复杂逻辑;使用
request_threaded_irq()将处理线程化。 - 上下文选择:ISR 中只做最小硬件操作(如读取 GPIO 电平),耗时任务交给
workqueue在进程上下文中执行。
代码示例: 使用线程化中断处理 GPIO 按键去抖:
static irqreturn_t gpio_irq_handler(int irq, void *dev_id) {
/* 中断上下文:仅记录事件 */
struct gpio_dev *dev = dev_id;
dev->event_count++;
/* 唤醒等待队列,由内核线程处理 */
return IRQ_WAKE_THREAD;
}
static irqreturn_t gpio_irq_thread(int irq, void *dev_id) {
/* 进程上下文:可以睡眠,执行去抖逻辑 */
struct gpio_dev *dev = dev_id;
msleep(20); /* 去抖延时 */
int val = gpio_get_value(dev->gpio_pin);
if (val == dev->expected_state) {
/* 处理有效按键事件 */
pr_info("GPIO key pressed, count=%d\n", dev->event_count);
}
return IRQ_HANDLED;
}
/* 注册线程化中断 */
ret = request_threaded_irq(gpio_irq, gpio_irq_handler,
gpio_irq_thread,
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
"gpio_key", dev);