中断子系统

本页目录

设备不断报中断,屏蔽后 DMA 却仍在写内存。这里要分别追踪通知、设备状态和后台工作。

查阅目标阅读位置
编号与分发第 1~3 节
执行上下文与延后工作第 4~5 节
停止与有效亲和性第 6~7 节

中断是设备向 CPU 通知事件的一种机制。通知可能对应多个已完成请求,一个请求也可能涉及多个通知,因此驱动最终应检查设备的状态、队列或完成记录,而不是把中断计数直接当成业务完成数。

1. 三种编号与四个对象

概念职责容易混淆的对象
hwirq某个中断域内的硬件编号不是整个系统唯一编号
Linux IRQ内核管理 IRQ 的逻辑标识不等于 x86 CPU vector
CPU vector / 架构中断 ID架构入口识别和路由具体含义依 APIC、GIC 等机制
irq_domain映射并管理硬件域与 Linux IRQ 关系可分层,不是简单加一个固定偏移
irq_chip控制器操作,如 mask、ack、EOI、affinity不等于驱动处理设备业务的 handler
irq_descIRQ 状态、控制流、统计及 action 等一个 IRQ 可关联多个共享 action
irqaction驱动注册的处理函数、参数与线程信息不一定一项就对应一条独占物理线

一个 GPIO 控制器可作为另一个中断控制器的下级,中断域因而可能构成层次。PCI MSI 也可经过 MSI domain、重映射与 CPU 路由等多层管理。驱动通常取得子系统提供的 Linux IRQ 后注册处理函数,不应猜测硬件编号。见 irq_domain 文档。

2. 事件分发与确认

从硬件通知到驱动处理:各层有不同职责 设备产生事件 电平、边沿或 MSI 消息;不等于每个事件都一次通知 1 中断控制器与架构入口 路由到 CPU,进入相应异常/中断处理路径 2 Linux IRQ 描述与 flow handler 协调 mask、ack、EOI 等控制器操作 3 驱动 primary handler 识别来源、处理必要状态,或返回 IRQ_WAKE_THREAD 4 后续处理与资源回收 IRQ 线程、NAPI、普通 workqueue 等按需求选择 5 EOI、设备清状态、屏蔽 IRQ 与停止 DMA 不是同一个动作。

架构入口保存必要现场并建立内核处理环境,具体栈切换与保存寄存器规则依架构和入口类型不同。不能把 syscall 入口、x86 外部中断和 ARM 异常处理写成同一个固定函数链。

generic IRQ 层调用为该 IRQ 安装的 flow handler,再调用 irq_chip 与驱动 action。generic_handle_irq_desc() 是通用分发相关接口,不应与 handle_edge_irq()、handle_fasteoi_irq() 并排画成三种触发类型。

mask、ack、EOI 与设备状态

操作处理哪一层状态
mask是否继续递交通知
ack控制器的识别/应答
EOI告知控制器相关服务阶段结束
清设备状态、消费完成队列驱动按设备协议处理真正的事件来源

控制器确认与设备业务完成分别发生,不能用其中一个替代另一个。

电平源若仍保持有效,解除屏蔽后可以再次通知;边沿源则要考虑通知是否锁存与处理期间新的边沿。不同 flow handler 为这些约束组织操作,fasteoi 也不能一概写成“先 mask,处理,最后 unmask”——它的核心是相应的 EOI 协议,并依状态、oneshot 等条件处理屏蔽。

向控制器发送 EOI 不自动清除设备的中断原因,屏蔽 IRQ 也不自动停止 DMA。两层弄反可能导致中断风暴或释放后的 DMA 写入。参见 generic IRQ。

分别操作设备源、控制器服务状态和屏蔽位。只发送 EOI 而不清除电平源,再次递送仍可能发生;不需要人为制造一个新设备事件。

正在呈现知识画面
EOI 结束服务,却不清除设备原因

设备原因、控制器 in-service 和屏蔽状态是不同对象。EOI 不清设备原因,mask 不停止 DMA 或清除业务队列。

3. 共享 IRQ 与返回值

共享线上的 handler 要确认事件是否属于自己。dev_id 标识对应注册项及私有上下文,注销时也要匹配;不能在无法识别来源的情况下无条件宣称处理中断成功。

返回值含义
IRQ_NONE此 handler 未确认处理属于自己的事件
IRQ_HANDLED已处理自己的中断事件
IRQ_WAKE_THREAD请求执行注册的 thread_fn

IRQ_WAKE_THREAD 是返回值,不是名为 IRQF_WAKE_THREAD 的注册标志。持续无人认领的 IRQ 可能触发内核异常统计与处置,但阈值和路径依实现,不能写死“恰好十万次就一定关闭”。

MSI 通常避免传统共享线的来源识别问题,但队列合并、中断聚合与完成处理仍然存在。pci_alloc_irq_vectors() 返回实际分配数量,随后使用 pci_irq_vector() 取得每项 IRQ;它既不固定返回申请上限,也不直接返回第一个 IRQ。详见PCIe 与 USB 驱动模型。

4. Primary handler 与 threaded IRQ

request_threaded_irq() 可注册 primary handler 和 thread_fn。primary handler 在真正硬中断路径中应保持简短且不可睡眠;需要后续线程处理时返回 IRQ_WAKE_THREAD。

IRQF_ONESHOT 使核心按约定在线程处理期间保持 IRQ 屏蔽,避免相关处理尚未完成就再次递交。它不保证一个硬件事件恰好对应一次线程唤醒,线程通常仍要排空设备状态或队列。

在满足约束时,可以使用默认 primary handler 配合 thread_fn;共享线则尤其需要正确识别与协调来源。PREEMPT_RT 的强制线程化和例外会改变实际上下文,不能据进程名或 API 名就推断所有 handler 都可睡眠。

IRQ 线程可以执行适当睡眠操作,但不能在持有 raw spinlock 或依赖被自己阻断的路径时等待。线程化降低硬中断段长度,不自动修复锁顺序和资源寿命。

5. softirq、tasklet 与 workqueue

机制执行特征设计边界
softirq固定类别、按 CPU 挂起和执行,可能由 ksoftirqd 处理普通 softirq 回调不可随意睡眠,可跨 CPU 并发
tasklet基于 softirq 的旧式延后机制同一个 tasklet 实例不会同时执行,不是所有同类 tasklet 互斥
普通 workqueueworker pool 提供进程上下文可按规则睡眠,专用 queue 不等于固定独占线程
WQ_BH workqueue支持该接口的内核中使用 BH/softirq 上下文不可照搬普通 workqueue 的睡眠假设
IRQ thread与 IRQ action 关联的内核线程处理设备协议及 oneshot/共享等约束

ksoftirqd 的存在不使 softirq callback 变成可以任意拿 mutex 的普通工作函数。tasklet 仍存在于一些内核路径,新实现通常应选择更合适的线程化、NAPI 或 workqueue 机制,不能简单声称它已从内核删除。

十次通知,为什么不一定运行十次 work

同一 work 已经 pending 时,queue_work() 可能返回 false。它保存的是“这个工作需要执行”的调度状态,不是十条独立业务事件。

若十次 IRQ 都安排同一个 work,事件数量必须由其他队列/计数保存,或由 work 排空设备可查询的完成状态。这样即使调度通知合并,业务事实仍不会只剩一份。

多个 work 的并行、顺序和内存回收上下文由 queue flags、pool 和配置决定;需要严格顺序时使用相应 ordered 接口。见 workqueue 文档。

6. 退出路径:先阻止新活动,再等旧活动

退出时按依赖逐步关闭活动来源:

阶段要建立的条件
停止新提交不再产生新业务
控制设备源与 DMA硬件不再产生危险访问
注销/同步 IRQ相应 handler 不再使用资源
取消或等待后台工作先前排入的 callback 不再访问上下文
释放资源前述使用者已退出

实际顺序受驱动依赖影响,尤其要防止某个 callback 重新启动已停止资源。

disable_irq_nosync() 不等待正在执行的 handler;disable_irq() 与 synchronize_irq() 等等待接口又不能在持有 handler 所需锁时调用。synchronize_irq() 本身不是永远禁止后续新 IRQ 的措施。

free_irq() 等待并撤销相应处理注册,但不会自动停止设备硬件,也不自动取消此前排入的 work。共享 IRQ 还不能随意通过禁用整条 IRQ 影响其他设备;应优先正确控制自己的设备源。

7. 亲和性与只读观察

/proc/irq/N/smp_affinity_list 表达请求的 CPU 集合,effective_affinity_list 在支持时显示有效路由。managed IRQ、控制器能力、CPU hotplug、irqbalance 和驱动策略都可能影响实际结果。

from pathlib import Path

for irq in sorted(Path('/proc/irq').glob('[0-9]*'), key=lambda p: int(p.name))[:12]:
    values = {}
    for field in ('smp_affinity_list', 'effective_affinity_list'):
        try:
            values[field] = (irq / field).read_text().strip()
        except OSError:
            pass
    print(irq.name, values)

/proc/interrupts 还包含某些架构特殊事件行,并非每一行都是普通可重新分配 affinity 的设备 IRQ。计数增长集中在一个 CPU 不一定是错误:需结合队列映射、NUMA、NAPI 与业务负载判断。