3. Linux 中断子系统架构:IRQ Domain、中断控制器、中断号映射流程
在深入 PCIe MSI 中断调试之前,必须理解 Linux 中断子系统的核心架构。PCIe 设备产生的中断请求(MSI/MSI-X)需要经过中断控制器、IRQ Domain 和中断号映射等多个层次,最终才能被 CPU 处理。任何一个环节的配置错误或延迟,都可能导致中断丢失或性能下降。
3.1 中断子系统三层模型
Linux 中断子系统采用分层设计,从上到下依次为:通用中断层(Generic IRQ Layer)、中断控制器驱动层(IRQ Chip Driver) 和 硬件中断控制器层(Hardware Interrupt Controller)。PCIe MSI 中断的传递路径贯穿这三层。
关键概念: 中断号在 Linux 中分为 硬件中断号(hwirq) 和 Linux IRQ 号(virq)。硬件中断号由中断控制器或设备本身定义(如 MSI 的 Data 字段),而 Linux IRQ 号是内核使用的虚拟中断号,两者通过 IRQ Domain 建立映射。
3.2 IRQ Domain:中断号映射的核心
IRQ Domain 是 Linux 中断子系统中连接硬件中断与软件中断号的桥梁。每个中断控制器(如 GIC、IO-APIC、MSI 控制器)都对应一个或多个 IRQ Domain。对于 PCIe MSI 中断,MSI 控制器(如 x86 的 MSI chip 或 ARM 的 ITS)会创建一个专门的 IRQ Domain 来管理 MSI 中断号的分配与映射。
3.2.1 IRQ Domain 类型
| 类型 | 描述 | 适用场景 |
|---|---|---|
| irq_domain_ops | 映射操作集合:map() / unmap() / xlate() / alloc() / free() | 所有中断控制器 |
| 线性映射 (Linear) | hwirq 直接作为 virq 的偏移量,查找 O(1) | 中断源较少且连续(如 GPIO 控制器) |
| 树形映射 (Tree) | 使用基数树 (radix tree) 存储映射关系 | 中断源数量大且稀疏(如 MSI 中断) |
| 无映射 (No Map) | 由驱动自行管理 virq 分配 | 动态分配的中断(如 PCIe MSI-X) |
调试提示: 在排查 MSI 中断丢失时,可以通过
/proc/irq/<virq>/ 目录查看中断的亲和性、触发方式和中断控制器信息。使用 cat /proc/interrupts 可以观察每个 CPU 上 MSI 中断的分布情况。
3.3 中断号映射流程:从 PCIe 设备到 CPU
当 PCIe 设备使能 MSI 中断时,内核会执行以下映射流程:
- 设备枚举阶段: PCI 子系统读取设备的 MSI/MSI-X Capability 结构,获取设备支持的 MSI 向量数量。
- 中断号分配: 调用
pci_alloc_irq_vectors(),该函数会调用 MSI 控制器的 IRQ Domain 的alloc()回调,分配一组连续的 hwirq(硬件中断号)。 - virq 映射: IRQ Domain 为每个 hwirq 分配一个 Linux IRQ 号(virq),并建立映射关系。对于 MSI,通常使用树形映射。
- 中断控制器配置: 配置中断控制器的路由表,将 hwirq 映射到目标 CPU 的 LAPIC(x86)或 Redistributor(ARM GIC)。
- 中断处理程序注册: 驱动调用
request_irq(virq, handler, flags, name, dev)注册中断处理函数。 - MSI 消息写入: 内核将目标 CPU 的 LAPIC ID 和中断向量写入 PCIe 设备的 MSI 地址寄存器(Address)和数据寄存器(Data)。
3.4 中断控制器与 IRQ Chip 结构
每个中断控制器在 Linux 中对应一个 irq_chip 结构体,该结构体定义了操作中断控制器的底层函数。对于 PCIe MSI 中断,常见的 irq_chip 包括:
| 平台 | 中断控制器 | irq_chip 名称 | 关键回调 |
|---|---|---|---|
| x86 | IO-APIC + MSI | msi_chip / ioapic_chip |
irq_mask / irq_unmask / irq_ack / irq_set_affinity |
| ARM64 | GICv3 ITS | gic_chip / its_msi_chip |
irq_eoi / irq_set_affinity / irq_set_type |
| RISC-V | PLIC / APLIC | plic_chip |
irq_mask / irq_unmask / irq_ack |
常见陷阱: 在调试 MSI 中断丢失时,检查
irq_chip 的 irq_set_affinity 回调是否正确实现。如果亲和性设置失败,中断可能被路由到错误的 CPU,导致处理延迟或丢失。使用 cat /proc/irq/<virq>/smp_affinity 可以查看当前亲和性掩码。
3.5 中断号映射的调试方法
当怀疑中断号映射出现问题时,可以使用以下方法进行排查:
- 查看 /proc/interrupts: 确认 MSI 中断是否被正确分配,以及每个 CPU 上的中断计数。
- 查看 /sys/kernel/debug/irq_domain_mapping: 如果内核开启了 CONFIG_IRQ_DOMAIN_DEBUG,可以查看所有 IRQ Domain 的映射关系。
- 使用 tracepoint: 启用
irq_vectors和irq_handler_entrytracepoint,跟踪中断从硬件触发到处理函数执行的完整路径。 - 检查 dmesg: 内核在 IRQ Domain 分配失败时会打印错误信息,如
irq: no irq domain found for hwirq。
# 查看 MSI 中断分布
cat /proc/interrupts | grep -E "PCI-MSI|msi"
# 查看特定 IRQ 的亲和性
cat /proc/irq/128/smp_affinity
# 启用 IRQ Domain 调试(需要重新编译内核)
# 内核配置:CONFIG_IRQ_DOMAIN_DEBUG=y
# 挂载 debugfs 后查看
mount -t debugfs none /sys/kernel/debug
cat /sys/kernel/debug/irq_domain_mapping
3.6 总结:中断号映射的关键点
| 组件 | 作用 | 调试关注点 |
|---|---|---|
| IRQ Domain | hwirq ↔ virq 映射 | 映射类型(线性/树形)、alloc() 回调 |
| irq_chip | 硬件中断控制器操作 | mask/unmask/ack/set_affinity 实现 |
| MSI 控制器 | 管理 MSI 中断的分配与路由 | MSI Address/Data 写入是否正确 |
| PCI 子系统 | 调用 pci_alloc_irq_vectors() | 向量数量、MSI-X 表配置 |