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 负责后续的复杂处理。下图展示了这种协同关系:

ISR 与 iThread 协同工作流 ISR 与 iThread 协同工作流 PCI 设备中断 ISR 处理 读取状态 → 清除中断 → 发送脉冲 iThread 处理 等待事件 → DMA 描述符处理 → 数据搬移 → 与用户态通信 → 重新使能中断 用户态驱动进程 ISR 运行时间 < 50μs,iThread 可运行数毫秒;两者通过脉冲事件同步 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 描述符处理、数据搬移和协议栈交互。这种设计兼顾了实时性和功能完整性。

具体步骤:

  1. 初始化阶段:调用 InterruptAttachEvent 注册 iThread,同时保存中断 ID。
  2. 中断触发:硬件中断到达,QNX 内核调用 ISR(如果注册了 InterruptAttach)或直接唤醒 iThread。
  3. ISR 处理(可选):快速清除中断源,发送脉冲唤醒 iThread。
  4. iThread 处理:调用 InterruptWait 等待事件,执行 DMA 操作,与用户态通信。
  5. 中断重新使能:iThread 处理完成后,写硬件寄存器重新使能中断。
性能陷阱: 如果 iThread 处理时间过长(超过 1ms),考虑使用 中断节流(Interrupt Coalescing) 或 多 iThread 池,避免中断丢失。对于高速设备(如 25GbE 网卡),建议每个 CPU 核心绑定一个 iThread。

4.7 中断与 DMA 的协同触发

在 PCI 驱动中,DMA 传输完成通常触发中断。下图展示了 DMA 完成中断在 ISR/iThread 模型中的处理路径:

DMA 完成中断处理路径 DMA 完成中断处理路径 DMA 引擎 传输完成 → 触发中断 中断控制器 路由到 CPU ISR:清除中断 iThread:处理 DMA DMA 完成中断 → 中断控制器 → ISR(可选)→ iThread 处理描述符链 ISR 负责快速确认,iThread 负责 DMA 状态检查和下一轮传输启动