1. 中断基础概念:中断定义、中断向量、中断号、中断上下文

1.1 中断定义:CPU 的异步事件响应机制

中断(Interrupt)是一种硬件或软件发出的异步信号,用于通知 CPU 发生了需要立即处理的事件。CPU 暂停当前正在执行的程序,保存现场,跳转到预先注册的中断服务程序(ISR)执行,处理完成后恢复现场并继续执行原程序。

核心特征:中断是异步的、优先级可配置的、由事件驱动的。与轮询(Polling)相比,中断能显著降低 CPU 占用率,提高系统实时响应能力。
类比理解:中断就像你在写代码时,电话铃响了(中断信号),你保存当前编辑的内容(保存现场),接电话(执行 ISR),挂断后继续写代码(恢复现场)。

1.2 中断向量与中断号:硬件到软件的映射桥梁

中断向量(Interrupt Vector)是中断服务程序入口地址的索引表。每个中断源对应一个唯一的中断向量号,CPU 通过中断向量号在向量表中查找对应的 ISR 地址。

中断号(IRQ Number)是 Linux 内核为每个中断源分配的软件标识符,用于驱动注册和中断管理。中断号与中断向量之间存在映射关系,但中断号是软件抽象层,中断向量是硬件层概念。

概念 层级 作用 示例
中断向量 硬件层 CPU 通过向量号查找 ISR 入口地址 x86 的 IDT(中断描述符表)
中断号 软件层 内核用于标识中断源,驱动注册时使用 request_irq(irq, handler, ...)
中断线 物理层 硬件引脚或 PCI 中断线 GPIO 中断线、PCI INTx
中断向量与中断号映射关系 硬件中断源 中断控制器 CPU 核心 中断向量表 中断号映射表 中断服务程序 ISR 硬件中断线 → 中断向量 → 中断号 → ISR

1.3 中断上下文:驱动开发的核心约束

中断上下文(Interrupt Context)是指 CPU 在执行中断服务程序时所处的特殊运行环境。与进程上下文不同,中断上下文具有以下关键特征:

  • 不可睡眠:中断上下文不在任何进程的调度范围内,不能调用可能导致睡眠的函数(如 kmalloc(GFP_KERNEL)、mutex_lock())。
  • 不可抢占:在 Linux 内核中,中断上下文不能被其他进程抢占,但可以被更高优先级的中断嵌套。
  • 栈空间有限:中断使用独立的栈(通常为 4KB~16KB),必须避免深度递归或大局部变量。
  • 原子性要求:中断上下文中对共享资源的访问必须使用自旋锁等原子操作,不能使用信号量。
对比维度 进程上下文 中断上下文
可睡眠 是(可调用 schedule()) 否(禁止睡眠)
可抢占 是(内核态可被抢占) 否(不可被进程抢占)
锁机制 互斥锁、信号量、自旋锁 仅自旋锁(不能使用信号量)
栈大小 进程栈(通常 8KB+) 中断栈(通常 4KB~16KB)
典型函数 copy_from_user()、msleep() writel()、spin_lock()
常见错误:在中断上下文中调用 kmalloc(GFP_KERNEL) 会导致内核崩溃,因为 GFP_KERNEL 可能触发内存回收而睡眠。应使用 GFP_ATOMIC 标志。

1.4 中断处理流程:从硬件触发到软件响应

一个完整的中断处理流程包含以下步骤:

  1. 硬件触发:外设产生中断信号,通过中断线发送给中断控制器。
  2. 中断仲裁:中断控制器根据优先级选择最高优先级的中断,向 CPU 发送中断请求。
  3. CPU 响应:CPU 完成当前指令后,保存程序计数器(PC)和状态寄存器,进入中断模式。
  4. 向量查找:CPU 根据中断向量号在向量表中查找 ISR 入口地址。
  5. 执行 ISR:CPU 跳转到 ISR 执行,处理中断事件。
  6. 中断返回:ISR 执行完毕,CPU 恢复现场,继续执行被中断的程序。
中断处理全流程 外设触发中断 中断控制器仲裁 CPU 保存现场 查找中断向量 执行 ISR 恢复现场 返回原程序 关键约束:整个流程在中断上下文中执行,不可睡眠、不可调度 ISR 应尽量短小,耗时操作应推迟到下半部(Bottom Half)处理

1.5 中断分类:同步与异步、硬中断与软中断

中断按触发方式分为同步中断和异步中断;按处理机制分为硬中断和软中断。

分类 触发方式 典型场景 Linux 对应机制
同步中断 CPU 指令执行直接导致 除零异常、缺页异常、系统调用 异常处理(trap)
异步中断 外部硬件事件触发 网卡收包、按键按下、定时器到期 IRQ 中断
硬中断 硬件信号直接触发 外设中断、时钟中断 request_irq() 注册
软中断 软件通过指令触发 网络协议栈处理、任务调度 softirq、tasklet
驱动开发要点:硬中断 ISR 必须快速完成(通常微秒级),将耗时处理下放到软中断或工作队列中执行。Linux 内核提供 top half(硬中断)和 bottom half(软中断/tasklet/工作队列)的分层机制。

1.6 中断号分配与动态申请

在 Linux 内核中,中断号可以通过以下方式获取:

  • 静态分配:传统 ISA 设备使用固定的中断号(如 IRQ 0 为时钟中断)。
  • 动态分配:PCI/平台设备通过 platform_get_irq() 或 pci_irq_vector() 动态获取中断号。
  • 设备树绑定:嵌入式设备通过设备树(Device Tree)的 interrupts 属性指定中断号。
/* 动态获取中断号示例 */
int irq = platform_get_irq(pdev, 0);
if (irq < 0) {
    dev_err(&pdev->dev, "failed to get IRQ\n");
    return irq;
}

/* 注册中断处理函数 */
ret = request_irq(irq, my_isr, IRQF_TRIGGER_RISING,
                  "my_device", dev_id);
if (ret) {
    dev_err(&pdev->dev, "failed to request IRQ %d\n", irq);
    return ret;
}
最佳实践:始终使用动态中断号分配,避免硬编码。在设备树中明确指定中断触发类型(上升沿、下降沿、电平触发),确保与硬件设计一致。