2. 中断类型详解:硬件中断与软件中断、可屏蔽中断与不可屏蔽中断、异常与陷阱
好,咱们接着聊中断。上一章我讲了中断的基本概念,说白了就是CPU被“打断”了。但打断的方式可不一样,有的来自外部设备,有的来自程序自己,有的你还能拦着它,有的你拦都拦不住。这一章,我就把这些类型掰开揉碎了讲清楚。
2.1 硬件中断 vs 软件中断
这是按中断的来源分的。一个是外部世界发起的,一个是程序自己“作”出来的。
2.1.1 硬件中断
硬件中断,就是由CPU以外的硬件设备产生的中断信号。比如你按一下键盘,网卡收到一个数据包,定时器滴答了一下——这些都会给CPU发一个电信号。CPU收到信号后,就会暂停当前任务,去处理这个设备。
我个人习惯把硬件中断叫做“异步中断”。为什么?因为它随时可能发生,CPU根本不知道下一秒键盘会不会被按下。你想想看,程序正在算1+1,突然键盘来了个信号,这完全不可预测。
我在项目中遇到过一个问题:一个高速数据采集卡,每次采集完数据就发中断。结果中断来得太频繁,CPU光顾着响应中断,正经的计算任务反而卡住了。嗯,这就是硬件中断的“双刃剑”——响应快,但频率高了也头疼。
2.1.2 软件中断
软件中断,是程序主动执行一条指令来触发的。比如x86架构里的INT n指令,ARM里的SVC指令。程序执行到这条指令时,CPU就会像处理硬件中断一样,保存现场、跳转到中断向量表。
说白了,软件中断是“自找的”。程序自己喊了一声:“嘿,CPU,我要进内核态办点事!”
int 0x80(老版本)或syscall指令。
我曾经调试过一个bug,用户态程序调用系统调用后,返回值总是不对。查了半天,发现是软件中断的入口地址被某个驱动给覆盖了。从那以后,我每次写驱动都会检查中断向量表有没有被篡改。
2.2 可屏蔽中断 vs 不可屏蔽中断
这是按CPU能不能“假装没听见”来分的。你想想看,有些中断你可以暂时不理,但有些中断你必须立刻响应。
2.2.1 可屏蔽中断
可屏蔽中断,就是CPU可以通过设置中断屏蔽寄存器(比如x86的IF标志位)来暂时忽略的中断。大多数硬件中断都是可屏蔽的。比如键盘中断、鼠标中断、硬盘中断——这些都可以等一等。
为什么要有可屏蔽中断?因为有时候CPU正在处理一个更紧急的任务,比如正在保存关键数据,这时候你按一下键盘,CPU说:“键盘的事先放一放,等我存完数据再说。”
我记得有一次做嵌入式系统,一个定时器中断每1ms触发一次。但我在中断服务程序里做了太多计算,导致下一次中断来的时候,上一次还没处理完。结果就是中断嵌套,栈空间差点爆了。后来我学乖了:中断服务程序里只做最必要的事,复杂计算放到任务队列里。
2.2.2 不可屏蔽中断
不可屏蔽中断,简称NMI。CPU无论如何都必须响应,你关不掉它。比如硬件故障、电源掉电、内存校验错误——这些事你没法等,必须立刻处理。
你想想看,如果电源掉电了,CPU还在那慢悠悠地处理键盘中断,那数据就全丢了。NMI就是用来处理这种“天塌下来”的事的。
| 特性 | 可屏蔽中断 | 不可屏蔽中断 |
|---|---|---|
| 能否被CPU忽略 | 能(通过IF标志位) | 不能 |
| 典型来源 | 键盘、鼠标、硬盘 | 电源故障、硬件错误 |
| 紧急程度 | 低到中 | 高 |
| 使用场景 | 日常设备交互 | 系统保护、错误恢复 |
printf,结果系统直接死机。因为NMI处理期间,很多系统服务不可用。记住:NMI处理程序里,只做最核心的保存工作,然后尽快退出。
2.3 异常与陷阱
这两个词经常被混用,但在计算机体系结构里,它们有明确的区别。异常和陷阱都属于“内部中断”,也就是由CPU内部事件触发的。但触发的原因和处理方式不一样。
2.3.1 异常
异常,是CPU在执行指令时遇到了“错误”情况。比如除零错误、缺页错误、非法指令。这些错误导致当前指令无法正常完成。CPU会触发异常,然后跳转到异常处理程序。
异常又分两种:
- 故障(Fault):错误发生在指令执行过程中,处理完后可以重新执行这条指令。比如缺页异常——页面加载回来后,CPU重新执行那条导致缺页的指令。
- 终止(Abort):严重错误,无法恢复。比如硬件故障,CPU直接放弃治疗。
int a = 1 / 0;,CPU执行除法指令时发现除数为0,就会触发“除零异常”。操作系统收到这个异常后,通常会给进程发一个SIGFPE信号,然后进程就挂了。
我记得有一次调试一个嵌入式设备,程序跑着跑着就重启了。查了三天,发现是某个数组越界访问,写到了异常向量表里。结果下一次中断来的时候,CPU跳到了一个非法地址,直接触发异常。嗯,从那以后我写代码都会加边界检查。
2.3.2 陷阱
陷阱,是CPU执行指令时“主动”触发的中断。它不是错误,而是程序故意为之。比如调试器里的断点指令(INT 3),或者系统调用指令。
陷阱和异常最大的区别在于:陷阱是预期的,是程序主动要求的;异常是非预期的,是程序出错了。
我刚开始学操作系统时,总搞不清异常和陷阱的区别。后来写了一个简单的内核,在系统调用入口处打了个断点,用调试器单步跟踪。看到CPU执行INT 0x80后,自动保存了CS、EIP、EFLAGS,然后跳转到系统调用处理函数——那一刻我才真正理解了陷阱的机制。
2.4 知识体系总览
说了这么多,我画了一张图帮你理清思路。这张图展示了中断的整体分类和它们之间的关系。
这张图把中断分成了三个维度:按来源、按可屏蔽性、按内部事件。你可能会问:“为什么异常和陷阱要单独列出来?”因为它们虽然都是内部中断,但一个是“被动出错”,一个是“主动请求”。搞混了这两个,写操作系统时很容易出问题。
好了,这一章的内容就到这里。中断的类型其实不难理解,关键是要记住每种中断的特点和适用场景。下一章我会讲中断控制器——那个负责管理所有中断请求的“交通警察”。