4. 中断延迟的组成:硬件延迟与软件延迟
中断延迟,说白了就是从中断信号拉高到CPU真正执行第一行中断服务程序(ISR)之间的那段时间。很多刚入行的朋友觉得这玩意儿就是个固定值,查查芯片手册就完事了。我当年也这么想,直到在一个电机控制项目里,因为中断响应慢了那么几个微秒,电机直接过冲烧了驱动管……嗯,从那以后我再也不敢小看中断延迟的组成细节了。
中断延迟由两部分构成:硬件延迟和软件延迟。这两者叠加,才是你实际测到的总延迟。咱们一个一个拆开看。
4.1 硬件延迟:芯片层面的“硬等待”
硬件延迟是中断信号从外设走到CPU内核所花的时间。这部分你很难通过软件优化,但必须心里有数。
4.1.1 总线仲裁延迟
在多主设备系统中(比如CPU、DMA、以太网MAC都挂在总线上),中断信号要等总线空闲才能送达CPU。为什么?因为中断控制器本身也是总线上的一个设备。
我遇到过最夸张的情况:一个系统里DMA在做高速内存拷贝,占着总线不放,中断信号在总线仲裁器那里排队等了将近200个时钟周期。你想想看,200个周期,对于1GHz的CPU就是200纳秒,但对于低速外设(比如UART),这已经能丢一个字节了。
| 总线类型 | 典型仲裁延迟 | 影响因素 |
|---|---|---|
| AHB(AMBA 2) | 1-2个时钟周期 | 主设备优先级、当前传输长度 |
| AXI(AMBA 3/4) | 2-8个时钟周期 | 乱序传输、写缓冲深度 |
| OCP | 1-4个时钟周期 | 线程ID、管道深度 |
| 私有总线(如CoreLink) | 0-1个时钟周期 | 直连中断线、无仲裁 |
4.1.2 指令完成延迟
CPU不是任何时候都能响应中断的。它得把手头的指令执行完。这个“手头的指令”可能是单周期的ADD,也可能是几十个周期的DIV或者LDM(多寄存器加载)。
我记得在ARM Cortex-M3上做过测试:如果中断刚好撞上一条LDM指令(加载8个寄存器),硬件要等这条指令完全退休才能进中断。LDM本身要花十几个周期,再加上写回和流水线排空,光指令完成延迟就能吃掉20多个周期。
- 单周期指令(MOV、ADD):0-1个周期延迟
- 多周期指令(DIV、MUL):4-32个周期延迟
- 块传输指令(LDM、STM):N+2个周期(N为寄存器个数)
- 浮点指令(FDIV、FSQRT):15-60个周期延迟
4.2 软件延迟:你写得代码决定了延迟有多长
硬件延迟是“天灾”,软件延迟就是“人祸”了。这部分你完全可控,但也是最容易出问题的地方。
4.2.1 中断嵌套
中断嵌套的意思是:一个中断正在执行ISR,另一个更高优先级的中断来了,CPU暂停当前ISR去响应新的中断。听起来很合理对吧?但问题在于:每次嵌套都要压栈、弹栈,这些操作本身就在消耗时间。
我见过一个系统,中断嵌套深度达到了5层。每次嵌套压栈大约20个周期,5层就是100个周期。更可怕的是,最内层的中断如果是个高频中断(比如1ms一次),它可能永远出不来——因为外层中断一直在打断它。
// 中断嵌套的典型场景
void TIMER_IRQHandler(void) { // 优先级2
// 执行到一半,UART中断来了
process_timer_data();
// UART_IRQHandler 执行完才回来
}
void UART_IRQHandler(void) { // 优先级1(更高)
// 执行到一半,DMA中断来了
process_uart_byte();
// DMA_IRQHandler 执行完才回来
}
void DMA_IRQHandler(void) { // 优先级0(最高)
// 这里才是真正紧急的事
process_dma_done();
}
4.2.2 临界区
临界区是中断延迟的“隐形杀手”。你为了保护共享数据关掉中断,结果关中断的时间太长,其他中断就进不来了。
我曾经在一个项目中,工程师为了保护一个全局变量,在临界区里做了2000次循环的查表操作。关中断时间长达500微秒。结果一个1kHz的中断(周期1ms)直接丢了50%的触发。查了三天才找到原因。
| 临界区保护方式 | 关中断时间 | 适用场景 |
|---|---|---|
| 关全局中断(cli/sti) | 整个临界区长度 | 极短操作(< 10个周期) |
| 关特定中断(mask) | 只关目标中断 | 多中断源、优先级明确 |
| 自旋锁(spinlock) | 等待锁释放 | 多核系统、RTOS |
| 无锁编程(lock-free) | 0 | 原子操作、CAS指令 |
4.3 中断延迟的完整组成图
下面这张图把中断延迟的各个阶段串起来了。你可以看到,从外设发出中断到CPU执行ISR,中间经过了硬件和软件两道关卡。
4.4 实战:如何测量中断延迟的各个组成部分
光说不练假把式。我一般用逻辑分析仪或者示波器来拆解中断延迟。方法很简单:
- 测总延迟:把中断源信号和GPIO翻转信号同时接到示波器上。中断来时GPIO拉高,ISR第一行代码拉低。两个边沿之间的时间就是总延迟。
- 测硬件延迟:在中断控制器输出端(如果芯片有引出)测信号到CPU响应的时间。没有引出的话,用CPU的调试寄存器读中断响应时间戳。
- 测软件延迟:总延迟减去硬件延迟,就是软件延迟。如果软件延迟占比超过50%,说明你的临界区或者嵌套设计有问题。
- 一个优化良好的系统:硬件延迟占30%,软件延迟占70%
- 一个糟糕的系统:硬件延迟占10%,软件延迟占90%(软件是瓶颈)
- 目标:总中断延迟控制在100个时钟周期以内(对于1GHz CPU就是100ns)