3. 中断子系统架构:中断控制器、中断向量表、中断服务例程(ISR)与中断线程(ITH)
在 QNX 实时操作系统中,中断子系统是连接硬件事件与软件响应的核心枢纽。理解中断控制器、中断向量表、中断服务例程(ISR)以及中断线程(ITH)之间的协作关系,是编写高效、稳定设备驱动的基础。本章将深入剖析这一架构的每一层,并揭示其在 QNX 微内核中的独特设计哲学。
3.1 中断子系统总体架构
QNX 的中断处理采用“快速响应 + 延迟处理”的两阶段模型。第一阶段由 中断服务例程(ISR) 在超级微内核模式下执行,要求极短且不可阻塞;第二阶段由 中断线程(ITH) 在用户态或内核态以线程形式运行,负责大部分实际工作。中断控制器负责将硬件信号路由至 CPU,而中断向量表则作为硬件中断号与软件处理函数之间的映射表。
3.2 中断控制器(Interrupt Controller)
中断控制器是硬件与 CPU 之间的桥梁。在 ARM 平台上通常为 GIC(Generic Interrupt Controller),在 x86 平台上为 APIC(Advanced Programmable Interrupt Controller)。其主要职责包括:
- 中断路由: 将外设产生的中断信号分发到指定的 CPU 核心。
- 优先级管理: 支持多级中断优先级,高优先级中断可抢占低优先级中断的处理。
- 中断屏蔽: 允许软件临时启用或禁用特定中断源。
- 中断确认与结束: 提供 EOI(End of Interrupt)机制,通知控制器中断处理已完成。
| 特性 | GIC (ARM) | APIC (x86) | PIC (传统 x86) |
|---|---|---|---|
| 中断类型 | SPI, PPI, SGI, LPI | LINT0, LINT1, IOAPIC | IRQ0–IRQ15 |
| 优先级支持 | 16 级 | 256 级 | 8 级 |
| 多核支持 | 原生支持 | 原生支持 | 不支持 |
| QNX 驱动接口 | InterruptAttach() | InterruptAttach() | InterruptAttach() |
pci -vvv 或 devb-* 的调试输出,可以查看中断控制器的路由配置。若发现中断未触发,首先检查中断控制器是否已正确使能该中断源。
3.3 中断向量表(Interrupt Vector Table, IVT)
中断向量表是一个由操作系统维护的映射表,它将硬件中断号(IRQ)映射到对应的中断处理函数入口地址。在 QNX 微内核中,中断向量表的管理由 procnto 进程负责。驱动开发者通过 InterruptAttach() 或 InterruptAttachEvent() 系统调用注册 ISR,内核自动更新向量表。
3.4 中断服务例程(ISR)
ISR 是中断响应中最先被执行的代码,运行在 超级微内核模式(Supervisor Mode)。QNX 对 ISR 有严格的约束:
- 不可阻塞: 禁止调用任何可能引起阻塞的 API(如
malloc()、printf()、pthread_mutex_lock())。 - 执行时间极短: 通常应在微秒级完成,避免影响系统实时性。
- 不可访问用户态内存: ISR 运行在微内核空间,不能直接访问驱动进程的用户态地址空间。
- 返回值决定行为: 返回
NULL表示不需要唤醒 ITH;返回ITH指针则触发对应 ITH 运行。
const struct sigevent *my_isr(void *arg, int intr_id) {
// 1. 读取硬件状态寄存器,确认中断源
uint32_t status = in32(DEVICE_STATUS_REG);
// 2. 清除中断标志位(写1清除)
out32(DEVICE_CLEAR_REG, status);
// 3. 记录事件,唤醒 ITH
struct my_device *dev = (struct my_device *)arg;
if (status & DEVICE_RX_READY) {
return &dev->rx_event; // 返回 ITH 事件
}
// 4. 无需处理时返回 NULL
return NULL;
}
3.5 中断线程(ITH)
ITH 是 QNX 中断处理模型的核心创新。它是一个由 ISR 触发的、运行在 线程上下文 中的处理实体。与 ISR 不同,ITH 可以:
- 调用阻塞式 API(如
malloc()、printf()、pthread_mutex_lock())。 - 访问用户态内存和驱动数据结构。
- 执行复杂的数据处理、协议解析、DMA 管理。
- 被调度器按优先级管理,支持抢占和同步。
| 特性 | ISR | ITH |
|---|---|---|
| 执行模式 | 超级微内核模式 | 用户态 / 内核态线程 |
| 可阻塞 | 否 | 是 |
| 执行时间 | 微秒级 | 毫秒级或更长 |
| 内存访问 | 仅内核空间 | 用户态 + 内核空间 |
| 调度优先级 | 硬件优先级(固定) | 软件优先级(可配置) |
| 典型操作 | 清中断、读状态、发事件 | 数据拷贝、协议处理、信号量释放 |
3.6 中断注册与生命周期管理
驱动开发者通过以下 API 管理中断:
InterruptAttach():注册 ISR,指定中断号、ISR 函数指针、参数和标志位。InterruptDetach():注销 ISR,释放中断资源。InterruptMask()/InterruptUnmask():临时屏蔽/使能指定中断。InterruptWait():ITH 中等待中断事件。
InterruptMask() 可能导致死锁,因为该函数本身可能依赖中断机制。正确的做法是在 ISR 中仅操作硬件寄存器屏蔽中断,或在 ITH 中调用 InterruptMask()。
3.7 中断调试实践
在 QNX 下调试中断子系统时,以下工具和方法至关重要:
| 工具/方法 | 用途 | 示例命令 |
|---|---|---|
pidin -p irq |
查看当前系统中断分配 | pidin -p irq |
sloginfo |
查看内核日志中的中断信息 | sloginfo -w | grep -i interrupt |
io-pkt-v6-hc -d |
网络驱动调试输出 | io-pkt-v6-hc -d verbose |
| 硬件断点 | 在 ISR 入口设置断点 | gdb> break my_isr |
| 计数变量 | 在 ISR 中递增计数器 | volatile uint64_t isr_count; |
volatile 计数器,并在 ITH 中定期打印该计数器值。如果计数器不增长,说明中断未触发或 ISR 未正确注册;如果计数器增长但 ITH 未执行,说明 ISR 返回了 NULL 或 ITH 事件配置错误。