1. 中断基础概念:中断定义、中断向量表、中断控制器
中断是硬件与软件交互的核心机制,允许外设或CPU内部事件异步通知处理器,从而避免轮询带来的CPU浪费。理解中断的定义、向量表结构以及中断控制器(APIC/GIC)的作用,是掌握内核驱动中断处理的基石。
1.1 中断定义与分类
中断(Interrupt)是一种硬件或软件触发的信号,使CPU暂停当前执行流,保存上下文,跳转到预定义的中断服务例程(ISR)执行,完成后恢复原执行流。中断分为以下几类:
| 分类 | 触发源 | 典型示例 | 特点 |
|---|---|---|---|
| 外部中断 | 外设硬件 | 网卡、磁盘、键盘 | 异步,由中断控制器路由 |
| 内部中断 | CPU内部异常 | 除零、缺页、非法指令 | 同步,与指令执行直接相关 |
| 软中断 | 软件指令 | int 0x80、系统调用 | 同步,用于陷入内核 |
| IPI(核间中断) | 其他CPU核 | TLB刷新、任务调度 | 多核通信专用 |
1.2 中断向量表(IVT/IDT)
中断向量表是CPU用于查找中断处理程序入口地址的数据结构。x86架构使用中断描述符表(IDT),ARM架构使用异常向量表(Vector Table)。
IDT结构(x86): 每个中断号对应一个8字节的描述符,包含段选择子、偏移量、类型属性等。CPU收到中断后,根据中断号从IDT中取出描述符,跳转到对应的ISR。
struct idt_entry {
uint16_t offset_low; // 偏移量低16位
uint16_t selector; // 代码段选择子
uint8_t ist; // 中断栈表索引
uint8_t type_attr; // 类型与属性(DPL、Present等)
uint16_t offset_mid; // 偏移量中16位
uint32_t offset_high; // 偏移量高32位
uint32_t reserved; // 保留
} __attribute__((packed));
ARM异常向量表: ARMv8-A架构的异常向量表分为4个表(EL0/EL1/EL2/EL3),每个表包含16个条目,每个条目对应一种异常类型(同步、IRQ、FIQ、SError)。
1.3 中断控制器的作用
中断控制器是连接外设与CPU的硬件单元,负责中断的接收、优先级仲裁、路由和屏蔽。不同架构使用不同的中断控制器:x86使用APIC(高级可编程中断控制器),ARM使用GIC(通用中断控制器)。
1.3.1 x86 APIC架构
现代x86系统使用两级APIC架构:I/O APIC(负责收集外设中断)和Local APIC(集成在CPU核内,负责向核提交中断)。
| 组件 | 位置 | 功能 |
|---|---|---|
| I/O APIC | 芯片组/主板 | 接收外设中断线,根据重定向表将中断路由到指定CPU的Local APIC |
| Local APIC | CPU核内 | 接收I/O APIC或IPI,管理本地中断优先级,向CPU核提交中断 |
| MSI/MSI-X | PCIe设备 | 允许设备直接向Local APIC写入中断消息,绕过I/O APIC |
1.3.2 ARM GIC架构
ARM GIC(Generic Interrupt Controller)是ARM体系的标准中断控制器,支持SPI(共享外设中断)、PPI(私有外设中断)和SGI(软件生成中断)。
| 中断类型 | ID范围 | 目标 | 说明 |
|---|---|---|---|
| SGI | 0–15 | 指定CPU核 | 用于核间通信(IPI) |
| PPI | 16–31 | 单个CPU核 | 每个核私有的外设中断(如本地定时器) |
| SPI | 32–1019 | 任意CPU核 | 共享外设中断,可路由到多个核 |
interrupt-controller 属性标识,外设通过 interrupts 属性指定中断号、触发类型(边沿/电平)和优先级。内核使用 irq_domain 将硬件中断号映射到Linux IRQ号。
1.4 中断处理流程总览
下图展示了从外设触发中断到CPU执行ISR的完整流程,涵盖中断控制器路由和向量表查找两个关键阶段。
1.5 中断优先级与嵌套
中断控制器支持优先级管理,高优先级中断可以抢占低优先级中断的处理。驱动开发者需要关注以下几点:
- 优先级分组: APIC使用TPR(任务优先级寄存器)和PPR(处理器优先级寄存器)控制;GIC使用二进制点(Binary Point)寄存器配置抢占级别。
- 中断屏蔽: 在ISR中,可以通过本地中断控制器屏蔽同源或低优先级中断,防止递归或嵌套过深。
- 嵌套限制: Linux内核默认在ISR中关闭本地中断(
local_irq_disable),避免嵌套复杂性;但实时内核(RT-Preempt)允许中断线程化后开启中断。
// 设置SPI中断优先级为0x80(范围0x00-0xFF,数值越小优先级越高)
// 在设备树中配置:
interrupts = <0 42 4>; // 类型(0=SPI) 中断号 触发类型(4=高电平)
// 或在驱动中动态配置:
irq_set_priority(irq_num, 0x80);
1.6 中断上下文与限制
中断处理程序运行在中断上下文中,与进程上下文有显著区别:
| 特性 | 中断上下文 | 进程上下文 |
|---|---|---|
| 可睡眠 | 否(不能调用 schedule()) |
是 |
| 可被抢占 | 否(除非开启内核抢占) | 是 |
| 持有锁 | 只能使用自旋锁 | 可以使用互斥锁 |
| 访问用户空间 | 不允许 | 允许 |
| 执行时间 | 必须极短(微秒级) | 可较长 |
kmalloc(GFP_KERNEL) 可能导致睡眠,应使用 GFP_ATOMIC。同样,mutex_lock() 在中断上下文中会导致死锁,必须使用 spin_lock()。