1. 中断基础概念

1.1 中断的定义

中断(Interrupt)是一种硬件或软件触发的异步事件,用于通知处理器发生了需要立即处理的紧急情况。当中断发生时,处理器会暂停当前正在执行的程序,保存现场(寄存器、程序计数器等),跳转到预先注册的中断服务程序(ISR,Interrupt Service Routine)执行处理,处理完成后恢复现场并返回被中断的程序继续执行。

核心要点:中断是嵌入式系统中实现实时响应、多任务抢占和硬件事件通知的基础机制。在 Linux 内核中,中断处理是驱动开发的核心环节,直接影响系统的实时性和吞吐量。
调试提示:在设备树中,中断控制器节点通过 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 通常用于对延迟极度敏感的场景,如音频数据流、高速存储接口。

注意事项:FIQ 在 Linux 主线内核中使用较少,多数 SoC 的 FIQ 被映射为普通 IRQ 处理。在设备树中,FIQ 通常通过 interrupts-extended 属性配合特殊中断号描述,驱动开发者需查阅具体 SoC 手册确认 FIQ 支持情况。

软中断(Software Interrupt)

软中断由软件通过执行 SWI(ARM)或 SVC(ARMv7+)指令主动触发。在 Linux 内核中,软中断机制被用于实现下半部处理(Bottom Half),例如 NET_RX_SOFTIRQ(网络接收)、TASKLET_SOFTIRQ(Tasklet 调度)等。软中断不在设备树中描述,而是内核内部的中断分发机制。

ARM 中断类型与处理流程 硬件外设中断源 高速紧急中断源 软件指令触发 中断控制器(GIC / INTC) ARM 处理器(CPU Core) IRQ 处理(向量 0x18) FIQ 处理(向量 0x1C) SWI 处理(向量 0x08) 说明:FIQ 可抢占 IRQ;软中断由软件主动触发,不经过中断控制器

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,便于跟踪和调试。
驱动开发黄金法则:中断处理程序(上半部)只做最紧急的硬件操作,将数据拷贝、协议解析、唤醒等待队列等耗时操作推迟到下半部(Tasklet、Workqueue、Threaded IRQ)在进程上下文中执行。设备树中的 interrupts 属性只描述中断的硬件连接,不决定中断上下文的划分方式。
中断上下文 vs 进程上下文 执行流对比 中断上下文(上半部) 硬件操作:读 FIFO、清中断标志 唤醒下半部:Tasklet / Workqueue 返回中断前上下文(IRET) 进程上下文(下半部) 数据拷贝:从 FIFO 到内核缓冲区 协议解析 / 唤醒等待队列 通知用户态(poll / read 可读) 关键区别 中断上下文:不可睡眠、不可访问用户空间、使用自旋锁 → 快速执行 进程上下文:可睡眠、可访问用户空间、使用互斥锁 → 灵活处理

1.4 中断处理流程示例(Linux 内核视角)

以下是一个典型的外设中断处理流程,展示中断上下文与进程上下文的协作关系:

  1. 硬件触发:外设(如 UART 控制器)检测到接收 FIFO 中有数据,通过中断线向 GIC(通用中断控制器)发送 IRQ 信号。
  2. 中断控制器仲裁:GIC 根据优先级和中断屏蔽状态,决定是否向 CPU 核发送 IRQ 信号。
  3. CPU 响应:CPU 完成当前指令后,保存 CPSR 和 PC 到 IRQ 模式下的 SPSR 和 LR,跳转到 IRQ 向量表入口(0xFFFF0018)。
  4. 内核中断入口:执行 arch/arm/kernel/entry-armv.S 中的 __irq_svc 或 __irq_usr 汇编代码,保存现场并调用 handle_arch_irq()。
  5. 中断分发:GIC 驱动调用 generic_handle_irq(),根据中断号找到注册的 irq_desc 描述符。
  6. 执行 ISR:调用驱动注册的 request_irq() 处理函数(中断上下文),读取 UART 接收 FIFO 数据到临时缓冲区。
  7. 触发下半部:ISR 返回 IRQ_WAKE_THREAD 或调度 Tasklet/Workqueue,将数据拷贝和协议解析推迟到进程上下文。
  8. 进程上下文处理:下半部函数在进程上下文中执行,完成数据拷贝、唤醒等待队列、通知用户态应用程序。
调试技巧:使用 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 进行低开销的调试信息采集。