4. 中断服务程序:ISR编写规范、保存与恢复现场、中断返回指令IRET
好,咱们进入中断机制里最核心、也最考验功底的部分——中断服务程序(ISR)的编写。说白了,ISR 就是 CPU 响应中断后,跑去执行的那段“临时任务”。写得好,系统稳如泰山;写得不好,嗯,我见过系统直接死在那儿的。
4.1 ISR 编写规范:别让中断变成灾难
我个人习惯,写 ISR 之前先问自己三个问题:这个中断能有多快?它要访问哪些共享资源?会不会被更高优先级的中断打断?想清楚这些,再动手。
核心规范就几条:
- 短小精悍:ISR 不是主程序,别在里面做复杂运算。我见过有人把排序算法写在 ISR 里,结果系统响应慢得像蜗牛。ISR 的任务就是“快速响应,快速退出”。
- 不可重入要小心:如果 ISR 和主程序共享同一个全局变量,记得加保护。我曾经踩过一个坑——两个中断同时修改一个标志位,结果逻辑全乱套了。
- 关中断时间尽量短:在 ISR 里关中断,是为了保护临界区。但关太久,其他中断就响应不了。我建议:关中断只包住那几行关键代码,别一关就是一大片。
- 避免调用不可重入函数:像
printf()、malloc()这类函数,很多实现是不可重入的。在 ISR 里调用它们,等于给自己埋雷。
重要原则:ISR 里只做“必须立即做的事”,比如读取硬件寄存器、清除中断标志、把数据放到队列里。剩下的处理,交给主循环或任务去干。
4.2 保存与恢复现场:CPU 的“记忆暂停术”
中断来了,CPU 正干着活呢,突然被叫走。等它回来,得知道自己刚才干到哪了。这就是“保存现场”和“恢复现场”的意义。
保存现场:把 CPU 当前的所有寄存器(通用寄存器、段寄存器、标志寄存器等)压入堆栈。这样,中断返回时就能原样恢复。
恢复现场:中断处理完,从堆栈里弹出之前保存的寄存器值,让 CPU 回到中断前的状态。
你想想看,如果忘了保存某个寄存器,中断返回后那个寄存器的值就变了,主程序跑着跑着就出错了。我早期调试时,就遇到过因为没保存 EAX 寄存器,导致循环变量被改,程序直接飞掉的情况。
典型的保存/恢复流程(x86 汇编示例):
; 中断服务程序入口
ISR_Handler:
; 保存现场
push ax
push bx
push cx
push dx
push si
push di
push ds
push es
; 这里写你的中断处理逻辑
; 比如读取键盘输入、处理定时器等等
; 恢复现场
pop es
pop ds
pop di
pop si
pop dx
pop cx
pop bx
pop ax
; 中断返回
iret
我的小技巧:保存现场时,压栈顺序和出栈顺序要严格相反。我习惯用“先入后出”来记——先压进去的,最后才能弹出来。另外,有些 CPU 会自动保存 CS、IP 和标志寄存器,这部分不用你操心。
4.3 中断返回指令 IRET:优雅地回到“现场”
ISR 的最后一条指令,通常是 IRET(Interrupt Return)。它和普通的 RET 不一样,IRET 会做三件事:
- 从堆栈弹出
IP(指令指针) - 从堆栈弹出
CS(代码段寄存器) - 从堆栈弹出
FLAGS(标志寄存器)
说白了,IRET 就是告诉 CPU:“我干完了,你回到之前被打断的地方,继续执行。” 如果你误用了 RET 而不是 IRET,那标志寄存器就不会被恢复,程序状态就乱了。我曾经见过一个同事,调试了半天发现中断返回后系统总是不对劲,最后发现是用了 RET 而不是 IRET——嗯,这种错误,犯一次就记住了。
警告:在 64 位模式下,IRET 的行为略有不同,它会弹出更多的信息。如果你在写 64 位系统的 ISR,务必查阅对应 CPU 手册。别拿 32 位的经验直接套用。
4.4 本章知识体系图
下面这张图,帮你理清 ISR 编写的核心逻辑:
4.5 避坑指南:我踩过的那些雷
写 ISR 这么多年,我总结了几条“血泪教训”:
- 别在 ISR 里开循环等待:比如用
while(flag);等某个硬件状态。万一那个状态永远不来,系统就卡死了。我早期做嵌入式时,就因为这个原因,导致整个系统“假死”,查了两天才找到问题。 - 中断嵌套要小心:高优先级中断可以打断低优先级中断。但嵌套层数太多,堆栈可能溢出。我建议:除非必要,否则尽量用“中断禁止”代替“嵌套”。
- 清除中断标志要及时:很多硬件在中断响应后,不会自动清除中断标志。如果你忘了在 ISR 里手动清除,那中断会反复触发,系统就一直在处理同一个中断。嗯,这种“死循环”我见过不止一次。
我的习惯:写 ISR 时,我会在开头先关中断,然后保存现场,接着清除中断标志,再处理逻辑,最后恢复现场、开中断、执行 IRET。这个顺序,我用了十几年,基本没出过问题。
好了,ISR 的编写规范、现场保存与恢复、以及 IRET 指令,就聊到这儿。记住:中断处理是系统稳定性的基石,写的时候多一分小心,调试的时候就少一分痛苦。