2. 中断基础概念:中断向量、中断优先级、中断嵌套、中断上下文与进程上下文

在嵌入式 Linux 系统中,中断是 CPU 响应外部或内部异步事件的硬件机制。理解中断的核心概念是编写可靠 GPIO 中断驱动的基石。本节将逐一剖析中断向量、中断优先级、中断嵌套以及中断上下文与进程上下文的区别。

2.1 中断向量与中断向量表

中断向量是每个中断源对应的唯一标识符,通常是一个整数。CPU 在接收到中断信号后,通过中断向量在中断向量表(IVT)中查找对应的中断服务程序(ISR)入口地址。

  • 中断向量号:由硬件或系统软件分配,例如 ARM GIC 使用中断 ID(0~1019)。
  • 中断向量表:存储在内存固定地址的跳转表,每个表项指向一个 ISR。
  • Linux 中的映射:内核通过 irq_desc 结构体将硬件中断号映射到 Linux IRQ 号。
关键点: 在 ARM 平台上,中断向量表通常位于 0x00000000 或 0xFFFF0000(高向量模式)。Linux 启动时会初始化并重定位向量表。
中断向量表结构 中断向量表基址 (0x00000000 / 0xFFFF0000) 向量 0: Reset 向量 1: Undefined 向量 2: SWI 向量 3~N 中断服务程序 (ISR) 代码区 ISR_Reset() | ISR_Undef() | ISR_SWI() | ISR_IRQ() | ... 每个表项存储对应 ISR 的入口地址

2.2 中断优先级

中断优先级决定了当多个中断同时发生时,CPU 优先响应哪一个。高优先级中断可以抢占低优先级中断的执行。

优先级模型 描述 典型应用
固定优先级 每个中断源分配固定优先级,不可动态调整 简单 MCU,如 8051
可编程优先级 软件可配置每个中断的优先级等级 ARM GIC、x86 APIC
轮转优先级 优先级在中断处理后循环移位,实现公平 某些 DSP 或实时系统
实践提示: 在 Linux 中,可以通过 /proc/irq/<irq_num>/smp_affinity 和 irq_set_affinity_hint() 调整中断亲和性,间接影响优先级行为。但真正的优先级控制由中断控制器硬件完成。
中断优先级抢占示例 时间 低优先级 ISR (IRQ1) 高优先级 ISR (IRQ2) 抢占 恢复低优先级 ISR 抢占 高优先级中断 (IRQ2) 在低优先级 ISR 执行期间触发,CPU 暂停低优先级 ISR,先执行高优先级 ISR

2.3 中断嵌套

中断嵌套是指一个中断服务程序正在执行时,另一个更高优先级的中断到达,CPU 暂停当前 ISR 转而执行新的 ISR 的现象。

  • 硬件支持:需要中断控制器支持优先级屏蔽和自动保存/恢复上下文。
  • Linux 中的限制:Linux 内核默认在 ISR 中关闭本地 CPU 中断(local_irq_disable()),因此不推荐在常规 ISR 中发生嵌套。但使用 request_threaded_irq() 的线程化中断可以在内核线程中打开中断,允许嵌套。
  • 嵌套深度:受限于堆栈空间和中断控制器硬件寄存器数量。
注意: 过度嵌套可能导致堆栈溢出或优先级反转。在 GPIO 驱动中,建议将耗时操作推迟到 tasklet 或 workqueue 中执行,避免在 ISR 中长时间关中断。
中断嵌套流程 主程序 (进程上下文) 低优先级 ISR (IRQ1) 高优先级 ISR (IRQ2) 嵌套 返回继续执行 恢复主程序 嵌套要求:中断控制器支持优先级屏蔽 Linux 默认关中断,需使用 threaded IRQ

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() 将中断处理线程化。
中断上下文 vs 进程上下文 中断上下文 • 不可睡眠 • 不可访问用户空间 • 仅使用自旋锁 • 不可被抢占 • 典型:ISR、tasklet 使用函数:writel(), ioread32() tasklet_schedule() 进程上下文 • 可以睡眠 • 可以访问用户空间 • 可使用互斥锁 • 可被抢占 • 典型:系统调用、内核线程 使用函数:kmalloc(GFP_KERNEL) msleep(), copy_from_user() 上下文切换边界

2.5 在 GPIO 驱动中的应用

在 GPIO 中断驱动中,上述概念直接影响代码设计:

  1. 中断向量:GPIO 中断通常对应多个中断号(如 GPIO IRQ 0~N),需要在设备树或平台数据中正确映射。
  2. 优先级:对于关键 GPIO(如电源按键、紧急停止),应配置为高优先级中断。
  3. 嵌套:避免在 GPIO ISR 中执行复杂逻辑;使用 request_threaded_irq() 将处理线程化。
  4. 上下文选择: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);