4. QNX中断子系统
在 QNX 实时操作系统中,中断处理是 PCI 驱动与 DMA 协同设计的核心环节。QNX 提供了灵活的中断模型,允许开发者根据实时性要求选择 中断服务例程(ISR) 或 中断线程(iThread) 来处理硬件事件。本章深入剖析 InterruptAttach / InterruptAttachEvent 的差异,并对比 ISR 与 iThread 的适用场景。
4.1 中断连接 API:InterruptAttach vs InterruptAttachEvent
QNX 提供两个主要的中断连接函数,用于将中断源与处理逻辑绑定。它们的核心区别在于处理上下文和阻塞行为。
| 特性 | InterruptAttach | InterruptAttachEvent |
|---|---|---|
| 处理上下文 | 中断服务例程(ISR),运行在中断上下文中 | 中断线程(iThread),运行在线程上下文中 |
| 阻塞行为 | ISR 内不可阻塞,不可调用阻塞式 API | iThread 内可阻塞,可调用 MsgSend、ThreadCtl 等 |
| 中断屏蔽 | 执行期间屏蔽同级或更低优先级中断 | 不屏蔽中断,仅通过事件机制通知 |
| 适用场景 | 极短处理、清除中断源、设置标志 | 复杂处理、数据搬移、与进程通信 |
| 返回值 | 返回 int,0 表示成功 |
返回 int,0 表示成功 |
关键选择原则: 如果中断处理只需要几十微秒且不涉及阻塞操作,使用
InterruptAttach;如果需要与用户态进程通信、执行 DMA 描述符链处理或等待硬件状态,使用 InterruptAttachEvent。
4.2 中断服务例程(ISR)详解
ISR 是运行在中断上下文中的轻量级函数。它必须快速执行,通常只完成以下工作:
- 读取中断状态寄存器,确认中断源。
- 清除中断标志,防止重复触发。
- 设置事件标志或信号量,通知中断线程处理剩余工作。
- 返回处理结果(
NULL或事件指针)。
以下是一个典型的 PCI 设备 ISR 代码示例:
#include <sys/neutrino.h>
#include <hw/pci.h>
const struct sigevent *pci_isr(void *arg, int id) {
pci_dev_t *dev = (pci_dev_t *)arg;
uint32_t status;
// 1. 读取中断状态寄存器
status = in32(dev->base_addr + PCI_INT_STATUS);
// 2. 检查是否为我们的中断
if (!(status & dev->int_mask)) {
return NULL; // 不是我们的中断
}
// 3. 清除中断标志
out32(dev->base_addr + PCI_INT_CLEAR, status);
// 4. 通知中断线程
if (dev->event.sigev_notify == SIGEV_PULSE) {
MsgSendPulse(dev->coid, -1, dev->pulse_code, status);
}
// 5. 返回事件指针(或 NULL)
return &dev->event;
}
ISR 陷阱: 不要在 ISR 中调用
printf、malloc、ThreadCtl 等阻塞函数。这些调用会导致系统死锁或优先级反转。
4.3 中断线程(iThread)详解
中断线程通过 InterruptAttachEvent 注册,运行在普通线程上下文中。它通过 InterruptWait 或 SignalWaitinfo 等待中断事件,然后执行更复杂的处理逻辑。
iThread 的优势在于:
- 可阻塞:可以调用
MsgSend、ThreadCtl、delay等。 - 可执行 DMA 操作:可以启动 DMA 传输、检查描述符链状态。
- 可进行数据搬移:将硬件 FIFO 数据拷贝到用户缓冲区。
- 可与其他线程同步:使用互斥锁、条件变量等。
iThread 的典型结构:
void *pci_ithread(void *arg) {
pci_dev_t *dev = (pci_dev_t *)arg;
int id;
struct sigevent event;
// 1. 注册中断事件
id = InterruptAttachEvent(dev->irq, &event, _NTO_INTR_FLAGS_TRK_MSK);
if (id == -1) {
perror("InterruptAttachEvent");
return NULL;
}
while (1) {
// 2. 等待中断事件
InterruptWait(0, NULL);
// 3. 处理中断(可阻塞)
process_dma_descriptor_chain(dev);
// 4. 与用户态通信
MsgSend(dev->chid, &dev->msg, sizeof(dev->msg));
// 5. 重新使能中断(如果硬件需要)
out32(dev->base_addr + PCI_INT_ENABLE, dev->int_mask);
}
InterruptDetach(id);
return NULL;
}
4.4 ISR 与 iThread 的协同工作流
在 PCI 驱动中,ISR 和 iThread 通常协同工作:ISR 负责快速响应和清除中断,iThread 负责后续的复杂处理。下图展示了这种协同关系:
4.5 中断处理模式对比总结
| 维度 | ISR(InterruptAttach) | iThread(InterruptAttachEvent) |
|---|---|---|
| 实时性 | 极高(微秒级响应) | 高(毫秒级响应,受调度影响) |
| 可阻塞性 | 不可阻塞 | 可阻塞 |
| 中断屏蔽 | 屏蔽同级中断 | 不屏蔽中断 |
| API 调用限制 | 仅限 in32/out32、MsgSendPulse、SignalKill |
无限制(可调用任何 API) |
| DMA 操作 | 不推荐(时间太长) | 推荐(可管理描述符链) |
| 与用户态通信 | 仅通过脉冲或信号 | 可通过 MsgSend、共享内存 |
| 典型执行时间 | < 50 μs | 50 μs ~ 数毫秒 |
4.6 实践建议:PCI 驱动中的中断设计
推荐架构: 对于大多数 PCI 设备(如网卡、存储控制器),采用 ISR + iThread 分层模型:ISR 只做最小必要操作(清除中断、记录状态),iThread 完成 DMA 描述符处理、数据搬移和协议栈交互。这种设计兼顾了实时性和功能完整性。
具体步骤:
- 初始化阶段:调用
InterruptAttachEvent注册 iThread,同时保存中断 ID。 - 中断触发:硬件中断到达,QNX 内核调用 ISR(如果注册了
InterruptAttach)或直接唤醒 iThread。 - ISR 处理(可选):快速清除中断源,发送脉冲唤醒 iThread。
- iThread 处理:调用
InterruptWait等待事件,执行 DMA 操作,与用户态通信。 - 中断重新使能:iThread 处理完成后,写硬件寄存器重新使能中断。
性能陷阱: 如果 iThread 处理时间过长(超过 1ms),考虑使用 中断节流(Interrupt Coalescing) 或 多 iThread 池,避免中断丢失。对于高速设备(如 25GbE 网卡),建议每个 CPU 核心绑定一个 iThread。
4.7 中断与 DMA 的协同触发
在 PCI 驱动中,DMA 传输完成通常触发中断。下图展示了 DMA 完成中断在 ISR/iThread 模型中的处理路径: