1. 中断基础概念
1.1 中断的定义
中断(Interrupt)是一种硬件或软件触发的异步事件,用于通知处理器发生了需要立即处理的紧急情况。当中断发生时,处理器会暂停当前正在执行的程序,保存现场(寄存器、程序计数器等),跳转到预先注册的中断服务程序(ISR,Interrupt Service Routine)执行处理,处理完成后恢复现场并返回被中断的程序继续执行。
interrupt-controller 属性和 #interrupt-cells 属性描述硬件中断拓扑,驱动开发者必须理解中断在硬件、设备树和内核三层的映射关系。
1.2 中断类型
ARM 架构下,中断主要分为以下三类,它们在优先级、使用场景和内核处理方式上存在显著差异:
| 中断类型 | 全称 | 优先级 | 触发方式 | 典型用途 |
|---|---|---|---|---|
| IRQ | Interrupt Request | 普通优先级 | 硬件信号 | 外设中断(UART、I2C、GPIO、定时器等) |
| FIQ | Fast Interrupt Request | 最高优先级 | 硬件信号 | 高速数据传输、紧急事件处理(DMA、看门狗) |
| 软中断 | Software Interrupt | 由软件设定 | 软件指令 | 系统调用(SWI/SVC)、内核下半部机制(SoftIRQ、Tasklet) |
IRQ(普通中断请求)
IRQ 是嵌入式系统中最常见的中断类型。ARM 处理器在接收到 IRQ 信号后,会跳转到 0x00000018(或高向量表的 0xFFFF0018)处的 IRQ 向量入口。Linux 内核中,所有外设驱动注册的中断处理函数默认运行在 IRQ 上下文中。
FIQ(快速中断请求)
FIQ 拥有比 IRQ 更高的优先级,可以抢占 IRQ 处理。FIQ 向量位于 0x0000001C,且 ARM 架构为 FIQ 保留了独立的寄存器组(R8~R14_fiq),减少了现场保存的开销。在 Linux 内核中,FIQ 通常用于对延迟极度敏感的场景,如音频数据流、高速存储接口。
interrupts-extended 属性配合特殊中断号描述,驱动开发者需查阅具体 SoC 手册确认 FIQ 支持情况。
软中断(Software Interrupt)
软中断由软件通过执行 SWI(ARM)或 SVC(ARMv7+)指令主动触发。在 Linux 内核中,软中断机制被用于实现下半部处理(Bottom Half),例如 NET_RX_SOFTIRQ(网络接收)、TASKLET_SOFTIRQ(Tasklet 调度)等。软中断不在设备树中描述,而是内核内部的中断分发机制。
1.3 中断上下文与进程上下文
理解中断上下文(Interrupt Context)与进程上下文(Process Context)的区别,是编写安全、高效内核驱动的关键。中断处理程序运行在中断上下文中,不能睡眠、不能访问用户空间、不能持有互斥锁;而进程上下文可以执行上述操作。
| 对比维度 | 中断上下文 | 进程上下文 |
|---|---|---|
| 运行环境 | 中断处理函数(ISR) | 系统调用、内核线程、工作队列 |
| 可睡眠 | ❌ 不允许 | ✅ 允许(通过 schedule()) |
| 访问用户空间 | ❌ 不允许 | ✅ 允许(通过 copy_from_user()) |
| 持有锁类型 | 自旋锁(spinlock) | 互斥锁(mutex)、信号量 |
| 当前进程指针 | current 不可靠 |
current 指向当前任务 |
| 典型函数 | request_irq() 注册的 handler |
read()、write()、ioctl() |
| 调试方法 | /proc/interrupts、ftrace |
printk、kgdb、perf |
中断上下文的特点
- 原子性要求:中断处理程序必须快速完成,通常只做必要的硬件操作(清除中断标志、读取数据),耗时工作推迟到下半部(Bottom Half)处理。
- 不可阻塞:中断上下文中不能调用可能导致睡眠的函数,如
kmalloc(GFP_KERNEL)、mutex_lock()、copy_from_user()。 - 栈空间有限:中断上下文使用内核栈(通常 4KB~16KB),递归或大量局部变量可能导致栈溢出。
- 优先级影响:中断上下文可以抢占进程上下文,高优先级中断可以抢占低优先级中断(FIQ 可抢占 IRQ)。
进程上下文的特点
- 可调度:进程上下文可以主动调用
schedule()让出 CPU,或被其他进程/中断抢占。 - 资源丰富:可以访问用户空间内存、使用文件系统接口、申请大块内存。
- 锁机制灵活:可以使用互斥锁、读写信号量等可能导致睡眠的同步原语。
- 调试便利:可以通过
current宏获取当前进程的 task_struct,便于跟踪和调试。
interrupts 属性只描述中断的硬件连接,不决定中断上下文的划分方式。
1.4 中断处理流程示例(Linux 内核视角)
以下是一个典型的外设中断处理流程,展示中断上下文与进程上下文的协作关系:
- 硬件触发:外设(如 UART 控制器)检测到接收 FIFO 中有数据,通过中断线向 GIC(通用中断控制器)发送 IRQ 信号。
- 中断控制器仲裁:GIC 根据优先级和中断屏蔽状态,决定是否向 CPU 核发送 IRQ 信号。
- CPU 响应:CPU 完成当前指令后,保存 CPSR 和 PC 到 IRQ 模式下的 SPSR 和 LR,跳转到 IRQ 向量表入口(
0xFFFF0018)。 - 内核中断入口:执行
arch/arm/kernel/entry-armv.S中的__irq_svc或__irq_usr汇编代码,保存现场并调用handle_arch_irq()。 - 中断分发:GIC 驱动调用
generic_handle_irq(),根据中断号找到注册的irq_desc描述符。 - 执行 ISR:调用驱动注册的
request_irq()处理函数(中断上下文),读取 UART 接收 FIFO 数据到临时缓冲区。 - 触发下半部:ISR 返回
IRQ_WAKE_THREAD或调度 Tasklet/Workqueue,将数据拷贝和协议解析推迟到进程上下文。 - 进程上下文处理:下半部函数在进程上下文中执行,完成数据拷贝、唤醒等待队列、通知用户态应用程序。
cat /proc/interrupts 查看系统中每个中断号的触发次数和 CPU 亲和性;使用 trace-cmd record -e irq:* 记录中断事件的完整生命周期,包括进入/退出中断、下半部调度和执行时间。
1.5 中断上下文编程注意事项
| 禁止操作 | 原因 | 替代方案 |
|---|---|---|
kmalloc(GFP_KERNEL) |
可能触发内存回收导致睡眠 | 使用 GFP_ATOMIC 标志 |
mutex_lock() |
互斥锁可能导致调度 | 使用 spin_lock() 自旋锁 |
copy_from_user() |
访问用户空间可能触发缺页异常 | 推迟到进程上下文执行 |
printk() 大量调用 |
控制台输出可能阻塞 | 使用 trace_printk() 或限制打印次数 |
msleep() / udelay(>10us) |
睡眠函数在中断上下文非法 | 使用 ndelay() 或硬件定时器 |
printk() 进行调试,但大量 printk() 会显著增加中断延迟,甚至导致系统不稳定。建议在开发阶段使用 trace_printk() 或 ftrace 进行低开销的调试信息采集。