第4章:中断处理程序(ITH) — 真正的中断执行者

好,咱们接着聊中断。上一章讲了ISR,那个轻量级的“哨兵”。这一章的主角是ITH——Interrupt Thread,中断处理程序。说白了,ISR负责“接电话”,ITH才是真正“干活”的人。

我个人习惯把ITH叫做“中断线程”。为什么要有线程?因为ISR里不能做太多事,否则系统就卡死了。你想想看,如果每个中断都在内核态里跑个几十毫秒,那实时性从何谈起?

4.1 ITH工作机制

ITH本质上是一个QNX线程。但它不是普通的线程,它和中断源绑定在一起。当ISR被触发后,它会做两件事:

  1. 快速处理硬件寄存器 — 比如清中断标志、读数据到缓冲区
  2. 通知ITH线程 — 通过信号量或消息,告诉ITH“该你上场了”

ITH线程随后被调度执行。它运行在用户空间,拥有完整的线程上下文。这意味着它可以调用任何POSIX API,可以阻塞,可以等待资源。ISR里不能做的事,ITH全都能做。

核心区别一句话总结:

ISR是原子操作,不可阻塞,运行在内核态。
ITH是普通线程,可以阻塞,运行在用户态。

我在项目中遇到过一种情况:某设备的中断频率很高,ISR里只做了“读FIFO数据到内存”这一步,然后立即唤醒ITH。ITH负责协议解析和数据处理。这样ISR的执行时间被压缩到微秒级,系统响应非常稳定。

4.2 ITH与ISR的协作

ISR和ITH怎么配合?我画个简单的流程:

硬件中断发生
    ↓
CPU进入中断上下文
    ↓
ISR被调用(内核态)
    ├─ 读取硬件状态
    ├─ 清除中断标志
    ├─ 拷贝关键数据
    └─ 发送信号/消息给ITH
    ↓
ISR返回
    ↓
调度器决定何时运行ITH
    ↓
ITH执行(用户态)
    ├─ 处理数据
    ├─ 调用驱动API
    ├─ 可能阻塞等待资源
    └─ 通知应用层

这里有个关键点:ISR和ITH之间如何传递数据?

我建议使用pulse(脉冲消息)或者信号量。脉冲消息是QNX特有的轻量级IPC机制,开销极小。ISR里发送一个脉冲,ITH里接收并处理。代码大概长这样:

// ISR部分
const struct sigevent *isr_handler(void *arg) {
    // 读取硬件寄存器
    uint32_t status = in32(REG_STATUS);
    
    // 清除中断
    out32(REG_CLEAR, 1);
    
    // 发送脉冲给ITH
    MsgSendPulse(ith_coid, -1, SIGEV_PULSE_NOCODE, status);
    
    return NULL;  // 不重新调度
}

// ITH部分
void *ith_thread(void *arg) {
    int rcvid;
    uint16_t code;
    uint8_t data[256];
    
    while (1) {
        // 等待脉冲
        rcvid = MsgReceivePulse(chid, &data, sizeof(data), NULL);
        
        if (rcvid == 0) {
            // 收到脉冲,开始处理
            process_data(data);
        }
    }
}

经验之谈:

ISR里尽量只做“必须在内核态做的事”。比如读一个寄存器、清一个标志。其他所有事情都丢给ITH。我曾经见过一个项目,ISR里做了内存分配——结果系统偶尔崩溃,查了三天才发现是ISR里调用了malloc。记住:ISR里不要动态分配内存!

4.3 ITH调度策略

ITH既然是线程,那它就有优先级。QNX的调度策略对ITH同样适用。但这里有个坑:ITH的优先级应该设多高?

我一般遵循这个原则:

  • 硬实时中断(比如电机控制、传感器采样):ITH优先级设为最高,通常63或64
  • 软实时中断(比如网络包处理、UI事件):ITH优先级中等,30-40左右
  • 非实时中断(比如日志、统计):ITH优先级低,10-20

为什么不能所有ITH都设最高优先级?你想想看,如果两个高优先级ITH同时就绪,调度器会先运行一个,另一个被阻塞。如果那个被阻塞的ITH持有某个资源,高优先级的ITH反而可能被低优先级的线程阻塞——这就是优先级反转。

注意:

QNX虽然支持优先级继承协议,能缓解优先级反转问题,但最好的办法是合理规划优先级。不要把所有ITH都堆在最高优先级上。

ITH的调度策略我推荐使用SCHED_FIFO(先进先出)或SCHED_RR(时间片轮转)。对于中断处理线程,SCHED_FIFO更常见,因为中断处理通常需要尽快完成,不需要被时间片打断。

设置优先级和策略的代码:

struct sched_param param;
param.sched_priority = 50;  // 中等优先级

pthread_setschedparam(ith_thread_id, SCHED_FIFO, ¶m);

我曾经在一个多核系统上调试中断响应。有个ITH优先级设得太低,结果被其他线程抢占了CPU,导致中断响应延迟从5微秒飙升到200微秒。后来我把这个ITH绑定到某个专用核上,并设置高优先级,问题就解决了。

4.4 避坑指南

最后分享几个我踩过的坑:

  • ITH里不要做死循环等待 — 除非你明确知道自己在做什么。否则一旦ITH卡住,整个中断处理链路就断了。
  • 注意ITH的栈大小 — 默认栈可能不够用。如果ITH里调用了复杂的库函数,记得增大栈空间。
  • ISR和ITH之间的数据同步 — 用原子操作或锁。但锁不能在ISR里用!只能用原子变量。
  • 测试中断频率上限 — 每个系统都有极限。我习惯用cyclictest或自己写一个中断注入工具,压测一下ITH的吞吐能力。

总结一下:

ITH是QNX中断处理的核心。它把中断处理从内核态搬到用户态,既保证了实时性,又提供了灵活性。ISR负责“快”,ITH负责“全”。两者配合好了,你的系统才能既稳定又高效。

嗯,这一章就到这里。下一章咱们聊聊中断的嵌套和优先级管理,那又是一个容易出问题的地方。