2. 中断处理基础:中断向量表、中断控制器(GIC)、中断优先级与嵌套

2.1 中断向量表(IVT)

中断向量表是系统启动时由引导加载程序或内核在固定物理地址建立的一张跳转表。每个中断源对应一个表项,表项中存放的是中断服务例程(ISR)的入口地址。在ARMv7/v8架构中,QNX通常将向量表放置在0x00000000或0xFFFF0000(取决于异常向量基址寄存器设置)。

关键指标: 向量表项大小固定为4字节(32位)或8字节(64位),总表项数通常为256或1024。QNX微内核在启动后会将所有未使用的中断向量指向一个默认的处理函数,防止未注册中断导致系统崩溃。
中断向量表结构(ARMv7 示例) 向量表基址 (0x00000000 或 0xFFFF0000) 向量 0: 复位 向量 1: 未定义指令 向量 2: 软中断(SWI) 向量 3: 预取中止 向量 4: 数据中止 向量 5: 保留 向量 6: IRQ 向量 7: FIQ ISR_IRQ (由内核注册) ISR_FIQ (高优先级) QNX 在启动时用 InterruptAttach() 将用户 ISR 地址写入对应向量表项

2.2 通用中断控制器(GIC)

GIC是ARM架构中标准的中断控制器,负责中断的使能、屏蔽、优先级排序和分发。QNX对GIC的驱动位于io-sys层,通过内存映射寄存器进行配置。GIC分为两个主要单元:分发器(Distributor)和CPU接口(CPU Interface)。

GIC 组件 功能 QNX 配置要点
Distributor 管理所有中断源的使能、优先级、目标CPU 通过 GICD_ISENABLER 寄存器使能中断
CPU Interface 向CPU核发送中断信号,处理应答和结束 通过 GICC_EOIR 寄存器写中断ID结束中断
中断ID 每个中断源有唯一ID(0~1019) QNX 使用 InterruptAttach() 绑定ID与ISR
优先级寄存器 每个中断可配置0~255优先级(0最高) QNX 默认将优先级映射为 0~31 级
QNX 实践: 在启动脚本中通过 io-pkt -d gic 加载GIC驱动。使用 pci -v 可查看中断路由信息,确认中断ID与设备对应关系。
GIC-400 架构与中断流 外设中断源 软件触发中断 PPI(私有外设) Distributor 使能/屏蔽/优先级 目标CPU路由 中断分组 CPU Interface 中断应答 (IAR) 结束中断 (EOIR) 优先级掩码 CPU QNX 中断处理流程 1. 外设触发中断 → 2. Distributor 按优先级选择最高中断 → 3. CPU Interface 向CPU发送IRQ信号 4. CPU 读取 IAR 获取中断ID → 5. 跳转到向量表执行 ISR → 6. ISR 结束时写 EOI

2.3 中断优先级与嵌套

QNX支持中断优先级嵌套,即高优先级中断可以抢占正在执行的低优先级ISR。GIC为每个中断分配一个8位优先级值(0~255),QNX将其映射为32级(0~31),其中0为最高优先级。嵌套规则由GIC的优先级掩码寄存器(PMR)和当前优先级寄存器(RPR)共同控制。

嵌套陷阱: 在QNX中,ISR默认运行在中断上下文中,不能调用可能阻塞的系统调用(如 MsgSend())。嵌套中断时,内核会保存和恢复ISR上下文,但必须确保ISR代码是可重入的。如果ISR访问共享数据结构,需要使用 InterruptLock() 保护。
优先级级别 GIC 值范围 QNX 映射 典型用途
0(最高) 0x00 - 0x0F 0 FIQ、时钟中断、看门狗
1 - 15 0x10 - 0x7F 1 - 15 高速外设(网络、存储)
16 - 31 0x80 - 0xFF 16 - 31 低速外设(UART、GPIO)
中断优先级嵌套时序 时间 → 低优先级 ISR (优先级 16) 高优先级中断到达 (优先级 2) 高优先级 ISR (优先级 2) 高优先级ISR完成 恢复执行 嵌套规则:高优先级ISR(优先级2)抢占低优先级ISR(优先级16),完成后低优先级ISR继续执行

2.4 QNX 中断注册与优先级设置

在QNX中,中断注册通过 InterruptAttach() 或 InterruptAttachEvent() 系统调用完成。前者用于绑定ISR函数,后者用于绑定事件(如脉冲)以唤醒线程处理中断。优先级通过 InterruptSetPriority() 设置。

#include <sys/neutrino.h>

const int IRQ_TIMER = 5;  // 假设定时器中断ID为5

// ISR 函数原型
const struct sigevent *timer_isr(void *arg, int id) {
    // 清除硬件中断标志
    *(volatile uint32_t *)(TIMER_BASE + TIMER_CLR) = 0x1;
    // 返回事件通知线程(可选)
    return NULL;  // 不触发事件,仅在内核态处理
}

int main(void) {
    int irq_id;
    struct sched_param param;

    // 注册中断,优先级设为 10(QNX 0~31 级)
    irq_id = InterruptAttach(IRQ_TIMER, timer_isr, NULL,
                             sizeof(void*), _NTO_INTR_FLAGS_TRK_MSK);
    if (irq_id == -1) {
        perror("InterruptAttach failed");
        return EXIT_FAILURE;
    }

    // 设置中断优先级为 10(数值越小优先级越高)
    if (InterruptSetPriority(irq_id, 10) == -1) {
        perror("InterruptSetPriority failed");
    }

    // ... 应用程序主循环 ...
    return EXIT_SUCCESS;
}
性能提示: ISR应尽可能短小,只做必要的硬件操作(如清除中断标志、读取数据寄存器),然后将工作委托给中断服务线程(IST)。使用 InterruptAttachEvent() 配合脉冲,可以将中断处理完全转移到线程上下文中,避免在ISR中执行复杂逻辑。

2.5 中断延迟与抖动

中断延迟是指从中断信号到达CPU到ISR开始执行的时间。在QNX中,中断延迟由以下因素决定:

  • 硬件延迟: GIC分发和CPU接口的传播延迟(通常 < 100ns)
  • 内核延迟: 保存上下文、查找ISR入口(通常 0.5~2μs)
  • 中断屏蔽: 如果当前CPU关闭了中断(如执行临界区代码),延迟会增加
  • 嵌套抢占: 低优先级ISR被高优先级中断抢占导致的额外延迟
场景 典型延迟 抖动(最大值-最小值)
无中断屏蔽,无嵌套 0.8 - 1.5 μs < 0.5 μs
有中断屏蔽(临界区) 1.5 - 5.0 μs < 3.0 μs
高负载嵌套场景 2.0 - 10.0 μs < 8.0 μs
优化建议: 使用 ClockCycles() 在ISR入口和出口测量时间戳,通过 printf() 或内核日志输出延迟数据。在QNX中,io-pkt 和 devc-* 驱动通常已经过中断延迟优化,自定义驱动应遵循相同的模式。