除服务进程和用户请求外,Linux内核核心工作之一是实现与硬件的双向对话,涵盖CPU到设备、设备到CPU的交互,这一过程依托中断机制完成。
1 中断的状态
中断是外部硬件设备向处理器发送的紧急请求信号,在被CPU识别前,需由中断控制器启用并完成路由。中断存在五种核心状态:
1. 活动(active):已被处理单元确认并正在处理,同一中断的后续触发不会再次被处理单元感知,直至初始中断失效。
2. 挂起(pending):硬件已识别中断请求或由软件生成,等待目标处理单元处理。多数硬件在挂起位清除前,不会再次生成中断;被禁用的中断无法进入挂起状态,会被中断控制器直接丢弃。
3. 活动且挂起(active and pending):中断被激活后,又接收到新的触发请求,同时处于活动和挂起状态。
4. 非活动(inactive):既未处于活动状态,也未挂起,中断被停用后可重新进入待处理流程。
5. 禁用/停用(disabled/deactivated):该中断对CPU和中断控制器均不可见,永远不会被触发,触发后直接丢失。
实际场景中,“禁用”与“屏蔽”常被视作等价概念,处理器复位后所有中断默认禁用,需由内核初始化代码统一启用。
2 中断处理流程
ARM Linux系统的完整IRQ处理流程分为硬件执行和内核软件操作两个阶段:
1. 硬件自动操作:中断发生且CPU状态寄存器允许中断时,ARM核心自动完成三步操作:禁止本地CPU新增中断,保存当前程序状态寄存器和程序计数器并切换至IRQ模式,跳转至异常向量表中的IRQ处理程序。
2. 内核软件处理:内核接手后,通过向量表宏判断处理器模式,调用对应处理函数;随后保存通用寄存器,调用中断处理入口函数;接着识别硬件IRQ编号,完成硬件编号到Linux逻辑编号的映射;再通过中断描述符找到对应处理程序,执行芯片级中断处理,最终调用设备驱动注册的中断服务程序(ISR);ISR执行完毕后,内核恢复上下文,结束中断处理流程。
早期内核存在快速与慢速中断之分,慢速处理程序允许中断重入,现代内核已摒弃该机制,执行中断处理程序期间,本地CPU中断始终保持禁用,确保中断不可重入,避免堆栈溢出风险。
3 中断处理程序的设计与实现
中断处理程序需遵循严格约束,其核心设计围绕API使用、返回值规范和执行限制展开:
- 核心API:驱动程序通过`request_irq`注册中断,通过`devm_request_irq`实现设备托管的中断注册,无需手动释放资源;中断释放使用`free_irq`和`devm_free_irq`,二者适配对应注册方式。注册参数需注意,`dev_id`在共享中断时必须唯一,用于区分不同设备。
- 返回值规则:`IRQ_NONE`表示中断并非本设备产生,仅适用于共享中断,大量返回该值会触发内核的伪中断判定;`IRQ_HANDLED`标识中断处理完成;`IRQ_WAKE_THREAD`仅用于线程化中断,用于唤醒下半部处理线程。
- 执行限制:中断处理程序运行在原子上下文,禁止休眠操作,不能调用互斥锁、用户空间拷贝、非原子内存分配等可能引发休眠的函数,内存分配需使用`GFP_ATOMIC`标志。
4 核心中断标志及其应用
中断标志决定中断的行为特性,核心标志及其作用如下:
1. 触发类标志:`IRQF_TRIGGER_HIGH/LOW`用于电平触发中断,需在ISR中清除中断源,否则会引发中断风暴;`IRQF_TRIGGER_RISING/FALLING`用于边沿触发中断,硬件电平跳变产生一次中断,共享场景下存在中断丢失风险。
2. 共享与配置类标志:`IRQF_SHARED`允许多设备共享中断线,所有共享设备均需配置该标志且`dev_id`唯一;`IRQF_ONESHOT`是线程化中断的必备标志,确保硬中断完成后,中断线保持屏蔽直至线程处理完毕。
3. 电源与调度类标志:`IRQF_NO_THREAD`禁止中断线程化,适用于定时器等特殊场景;`IRQF_NO_SUSPEND`保证系统休眠时中断不关闭,支持唤醒功能,与共享标志混用需谨慎;`IRQF_NOBALANCING`关闭SMP中断负载均衡,固定中断的CPU亲和性。
设备树若已指定触发方式,驱动注册时无需重复配置触发标志。
5 上半部与下半部的核心思想
为平衡中断响应速度和处理效率,Linux将中断处理拆分为上半部和下半部:
- 上半部(硬中断上下文):本地CPU中断禁用,运行于原子上下文,不可休眠,仅处理硬件寄存器读写、中断确认、状态判断等短耗时、紧急操作。
- 下半部:承担耗时任务,提供多种实现方案:`softirq`优先级高但不可休眠,驱动极少直接使用;`tasklet`基于`softirq`,同一任务不会多CPU并发,不可休眠;`workqueue`运行于内核线程,可休眠,是通用下半部方案;
`threaded-irq`为单中断创建专用内核线程,运行于进程上下文,支持休眠操作。
拆分遵循明确原则:硬件交互、时间敏感操作放在上半部;耗时计算、IO操作、内存分配等可能休眠的操作移至下半部;若硬中断处理仅需几微秒,可无需拆分。
6 线程化中断处理
线程化中断通过将处理任务拆分为硬中断上半部和线程下半部,解决复杂中断处理的休眠限制,核心API为`request_threaded_irq`:
- 参数说明:`handler`为硬中断处理程序,负责快速读取硬件状态,返回`IRQ_WAKE_THREAD`唤醒线程,或`IRQ_HANDLED`直接结束处理;
`handler=NULL`时,内核安装默认处理程序,直接返回唤醒信号,需搭配`IRQF_ONESHOT`;`thread_fn`为线程处理程序,运行于进程上下文,可执行休眠操作,处理完成后返回`IRQ_HANDLED`。
- 适用场景:I2C扩展GPIO中断等场景,硬中断上下文无法访问I2C总线(会引发休眠),将`handler`置为空,交由线程处理。共享中断场景下,`handler`不能为空,需先判断中断来源,非本设备中断返回`IRQ_NONE`。
7 自适应上下文的中断注册
`request_any_context_irq`用于中断控制器特性不确定的场景,例如GPIO中断可能来自片上MMIO控制器,也可能来自I2C/SPI扩展芯片。该API会根据底层硬件特性,自动选择硬中断或线程化中断处理方式,驱动无需手动判断中断特性,实现代码通用化,降低适配成本。
8 中断上下文的锁定规则
中断与进程、不同中断处理机制间的资源共享需遵循差异化锁定规则:
1. 进程与硬中断:使用`spin_lock_irqsave/spin_unlock_irqrestore`,禁用本地中断,防止进程持锁时被硬中断抢占引发死锁。
2. 进程与`softirq/tasklet`:采用`spin_lock_bh/spin_unlock_bh`,禁用下半部,避免进程上下文被软中断打断。
3. 硬中断与`softirq`:硬中断处理程序用普通自旋锁即可,因硬中断执行时软中断不会运行;软中断处理程序需使用禁用中断的自旋锁,防止被硬中断抢占。
4. 多CPU软中断间:使用普通自旋锁,无需禁用中断,仅需防范多CPU并发访问。
9 中断处理的实战要点
中断处理的实战需关注核心要点:
- 释放规则:普通注册的中断用`free_irq`释放,托管注册的中断用`devm_free_irq`释放,二者不可混用,且释放前需确保设备中断已禁用,避免产生伪中断。
- 执行约束:中断处理程序不可休眠、不可重入,硬中断执行期间本地中断始终禁用,需严格控制执行时间,避免长时间占用CPU导致其他中断丢失。
- 优先级管理:线程化中断支持单独配置优先级和CPU亲和性,通过`/proc/irq/IRQ_NUMBER/smp_affinity`可查看和设置中断亲和性,满足实时系统的精细化调度需求。
总结
围绕Linux内核中断管理展开,覆盖中断状态、处理流程、程序设计、标志配置、上下半部机制、线程化中断、锁定规则等核心内容,为嵌入式开发人员掌握中断驱动开发奠定基础,助力实现高效、稳定的硬件驱动设计。