3、中断处理函数编写:中断上下文限制、ISR快速处理原则、中断延迟

好,咱们今天聊聊中断处理函数怎么写。这话题看着基础,但坑特别多。我见过不少项目,系统跑着跑着就卡死了,查到最后都是中断处理写得有问题。

说白了,中断处理函数不是普通函数。它运行在特殊的环境里——中断上下文。这个环境有很多限制,你一不小心就踩雷。

3.1 中断上下文限制

先说说中断上下文是什么。当CPU收到中断信号,它会暂停当前任务,跳转到ISR去执行。这时候,系统处于一种「特权模式」,但不是普通线程环境。

中断上下文里不能做的事:

  • 不能阻塞——比如调用 sleep()wait()pthread_mutex_lock() 这些会挂起当前执行流的函数。为什么?因为中断上下文没有「进程」的概念,你阻塞了没人能唤醒你。
  • 不能访问用户空间——像 copy_from_user() 这类操作,在中断里调用会直接崩溃。我早期做驱动时犯过这个错,查了两天才发现是这里的问题。
  • 不能做复杂的内存操作——比如 malloc()new。内存分配可能触发页面错误,而中断上下文处理不了页面错误。
  • 不能持有普通互斥锁——只能用自旋锁(spinlock)。因为互斥锁会导致睡眠,而中断里不能睡。
⚠️ 特别注意: 在QNX中,中断处理函数里调用 printf()log() 也要非常小心。这些函数内部可能涉及锁操作,搞不好就死锁了。我建议用 TraceEvent() 或者简单的标志位来调试。

3.2 ISR快速处理原则

ISR的设计核心就四个字:快进快出

你想想看,中断处理期间,CPU不能响应其他中断(除非你特意开了嵌套中断)。如果你在ISR里磨蹭太久,其他设备的中断就会被延迟,整个系统的实时性就崩了。

我个人习惯的做法:

  1. ISR只做最必要的事——读取硬件状态、清除中断标志、把数据放到一个缓冲区里。仅此而已。
  2. 繁重工作交给线程——通过信号量或事件标志通知一个高优先级线程,让线程去处理数据解析、协议栈这些耗时操作。
  3. 避免循环和分支——ISR里尽量不用 for 循环,特别是循环次数不确定的那种。实在要用,加个最大次数限制。

举个例子,一个典型的UART接收中断处理:

// 好的做法:ISR只做轻量工作
const struct sigevent *uart_isr(void *arg, int id) {
    uint32_t status = in32(UART_STATUS_REG);
    
    if (status & RX_READY) {
        // 只读一个字节,放到环形缓冲区
        buffer[buffer_head] = in32(UART_RX_REG);
        buffer_head = (buffer_head + 1) % BUFFER_SIZE;
        
        // 通知处理线程
        pthread_mutex_lock(&data_mutex);  // 注意:这里用自旋锁
        data_ready = 1;
        pthread_mutex_unlock(&data_mutex);
        
        // 触发事件
        pulse(handler_pid, SIGEV_PULSE, ...);
    }
    
    // 清除中断标志
    out32(UART_STATUS_REG, status);
    
    return NULL;
}
💡 小技巧: 我曾经在ISR里做过一次数据校验,结果发现校验算法太复杂,导致中断延迟飙升。后来我把校验挪到了线程里,中断延迟从50微秒降到了5微秒。记住:ISR里只做「搬砖」的活,不做「砌墙」的活。

3.3 中断延迟

中断延迟,指的是从硬件发出中断信号到ISR开始执行的时间差。这个指标直接决定了系统的实时性。

中断延迟的组成:

阶段 说明 典型时间
硬件延迟 中断信号从设备传到CPU的时间 纳秒级
CPU响应延迟 CPU完成当前指令、保存上下文的时间 几十到几百纳秒
中断屏蔽时间 其他ISR或关中断代码占用的时间 微秒到毫秒级(关键)
调度延迟 QNX内核调度ISR的时间 几微秒

你看,最不可控的就是「中断屏蔽时间」。如果某个ISR写得太长,或者某段代码关了中断太久,其他中断就得排队等着。

如何降低中断延迟?

  • 关中断的代码要短——比如操作临界区时,用自旋锁而不是关中断。自旋锁只影响当前CPU,不影响其他CPU的中断。
  • ISR里不要嵌套太深——QNX默认支持中断嵌套,但嵌套多了,每个中断的延迟都会增加。我一般建议最多嵌套2层。
  • 合理设置中断优先级——高优先级的中断可以抢占低优先级的ISR。把时间敏感的设备(比如网卡、定时器)设高优先级。
  • 使用中断线程化——QNX支持把ISR的一部分工作放到线程里做。这样ISR本身可以更快返回,减少对其他中断的影响。
🔑 核心要点: 中断延迟的优化,本质上是在「响应速度」和「系统吞吐」之间找平衡。ISR写得越短,延迟越低,但可能把压力转嫁给了线程。反过来,ISR做得太多,延迟就上去了。这个度,得根据你的硬件和业务需求来调。

嗯,我记得有一次做工业控制项目,要求中断响应时间在100微秒以内。刚开始怎么都达不到,后来发现是某个驱动在ISR里做了大量日志输出。去掉日志后,延迟直接降到了30微秒。所以说,ISR里千万别干多余的事。

最后总结一下:中断处理函数,核心就三句话——知道什么不能做(上下文限制)、知道怎么做最快(快速处理)、知道怎么测延迟(优化方向)。把这三点吃透了,中断这块基本就稳了。