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里磨蹭太久,其他设备的中断就会被延迟,整个系统的实时性就崩了。
我个人习惯的做法:
- ISR只做最必要的事——读取硬件状态、清除中断标志、把数据放到一个缓冲区里。仅此而已。
- 繁重工作交给线程——通过信号量或事件标志通知一个高优先级线程,让线程去处理数据解析、协议栈这些耗时操作。
- 避免循环和分支——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里千万别干多余的事。
最后总结一下:中断处理函数,核心就三句话——知道什么不能做(上下文限制)、知道怎么做最快(快速处理)、知道怎么测延迟(优化方向)。把这三点吃透了,中断这块基本就稳了。