2. 中断类型详解:硬件中断与软件中断、可屏蔽中断与不可屏蔽中断、异常与陷阱

好,咱们接着聊中断。上一章我讲了中断的基本概念,说白了就是CPU被“打断”了。但打断的方式可不一样,有的来自外部设备,有的来自程序自己,有的你还能拦着它,有的你拦都拦不住。这一章,我就把这些类型掰开揉碎了讲清楚。

2.1 硬件中断 vs 软件中断

这是按中断的来源分的。一个是外部世界发起的,一个是程序自己“作”出来的。

2.1.1 硬件中断

硬件中断,就是由CPU以外的硬件设备产生的中断信号。比如你按一下键盘,网卡收到一个数据包,定时器滴答了一下——这些都会给CPU发一个电信号。CPU收到信号后,就会暂停当前任务,去处理这个设备。

我个人习惯把硬件中断叫做“异步中断”。为什么?因为它随时可能发生,CPU根本不知道下一秒键盘会不会被按下。你想想看,程序正在算1+1,突然键盘来了个信号,这完全不可预测。

关键点:硬件中断是异步的,与CPU当前执行的指令无关。

我在项目中遇到过一个问题:一个高速数据采集卡,每次采集完数据就发中断。结果中断来得太频繁,CPU光顾着响应中断,正经的计算任务反而卡住了。嗯,这就是硬件中断的“双刃剑”——响应快,但频率高了也头疼。

2.1.2 软件中断

软件中断,是程序主动执行一条指令来触发的。比如x86架构里的INT n指令,ARM里的SVC指令。程序执行到这条指令时,CPU就会像处理硬件中断一样,保存现场、跳转到中断向量表。

说白了,软件中断是“自找的”。程序自己喊了一声:“嘿,CPU,我要进内核态办点事!”

小技巧:操作系统里的系统调用,底层就是靠软件中断实现的。比如Linux里的int 0x80(老版本)或syscall指令。

我曾经调试过一个bug,用户态程序调用系统调用后,返回值总是不对。查了半天,发现是软件中断的入口地址被某个驱动给覆盖了。从那以后,我每次写驱动都会检查中断向量表有没有被篡改。

2.2 可屏蔽中断 vs 不可屏蔽中断

这是按CPU能不能“假装没听见”来分的。你想想看,有些中断你可以暂时不理,但有些中断你必须立刻响应。

2.2.1 可屏蔽中断

可屏蔽中断,就是CPU可以通过设置中断屏蔽寄存器(比如x86的IF标志位)来暂时忽略的中断。大多数硬件中断都是可屏蔽的。比如键盘中断、鼠标中断、硬盘中断——这些都可以等一等。

为什么要有可屏蔽中断?因为有时候CPU正在处理一个更紧急的任务,比如正在保存关键数据,这时候你按一下键盘,CPU说:“键盘的事先放一放,等我存完数据再说。”

注意:可屏蔽中断不是“丢失”,而是“延迟”。当中断被重新允许时,CPU会继续处理之前挂起的中断请求。

我记得有一次做嵌入式系统,一个定时器中断每1ms触发一次。但我在中断服务程序里做了太多计算,导致下一次中断来的时候,上一次还没处理完。结果就是中断嵌套,栈空间差点爆了。后来我学乖了:中断服务程序里只做最必要的事,复杂计算放到任务队列里。

2.2.2 不可屏蔽中断

不可屏蔽中断,简称NMI。CPU无论如何都必须响应,你关不掉它。比如硬件故障、电源掉电、内存校验错误——这些事你没法等,必须立刻处理。

你想想看,如果电源掉电了,CPU还在那慢悠悠地处理键盘中断,那数据就全丢了。NMI就是用来处理这种“天塌下来”的事的。

特性 可屏蔽中断 不可屏蔽中断
能否被CPU忽略 能(通过IF标志位) 不能
典型来源 键盘、鼠标、硬盘 电源故障、硬件错误
紧急程度 低到中 高
使用场景 日常设备交互 系统保护、错误恢复
避坑指南:我曾经在NMI处理程序里调用了printf,结果系统直接死机。因为NMI处理期间,很多系统服务不可用。记住:NMI处理程序里,只做最核心的保存工作,然后尽快退出。

2.3 异常与陷阱

这两个词经常被混用,但在计算机体系结构里,它们有明确的区别。异常和陷阱都属于“内部中断”,也就是由CPU内部事件触发的。但触发的原因和处理方式不一样。

2.3.1 异常

异常,是CPU在执行指令时遇到了“错误”情况。比如除零错误、缺页错误、非法指令。这些错误导致当前指令无法正常完成。CPU会触发异常,然后跳转到异常处理程序。

异常又分两种:

  • 故障(Fault):错误发生在指令执行过程中,处理完后可以重新执行这条指令。比如缺页异常——页面加载回来后,CPU重新执行那条导致缺页的指令。
  • 终止(Abort):严重错误,无法恢复。比如硬件故障,CPU直接放弃治疗。
举个例子:你在C语言里写了个int a = 1 / 0;,CPU执行除法指令时发现除数为0,就会触发“除零异常”。操作系统收到这个异常后,通常会给进程发一个SIGFPE信号,然后进程就挂了。

我记得有一次调试一个嵌入式设备,程序跑着跑着就重启了。查了三天,发现是某个数组越界访问,写到了异常向量表里。结果下一次中断来的时候,CPU跳到了一个非法地址,直接触发异常。嗯,从那以后我写代码都会加边界检查。

2.3.2 陷阱

陷阱,是CPU执行指令时“主动”触发的中断。它不是错误,而是程序故意为之。比如调试器里的断点指令(INT 3),或者系统调用指令。

陷阱和异常最大的区别在于:陷阱是预期的,是程序主动要求的;异常是非预期的,是程序出错了。

一句话总结:异常是“出事了,快来救我”;陷阱是“嘿,帮我办件事”。

我刚开始学操作系统时,总搞不清异常和陷阱的区别。后来写了一个简单的内核,在系统调用入口处打了个断点,用调试器单步跟踪。看到CPU执行INT 0x80后,自动保存了CS、EIP、EFLAGS,然后跳转到系统调用处理函数——那一刻我才真正理解了陷阱的机制。

2.4 知识体系总览

说了这么多,我画了一张图帮你理清思路。这张图展示了中断的整体分类和它们之间的关系。

中断 按来源分 按可屏蔽性分 硬件中断 软件中断 可屏蔽中断 不可屏蔽中断 按内部事件分 异常(非预期) 陷阱(预期) 故障(Fault) 终止(Abort) 常见示例 • 硬件中断:键盘按下、网卡收到数据、定时器超时 • 软件中断:系统调用(int 0x80)、断点指令(int 3) • 异常:除零错误、缺页错误、非法指令 • 陷阱:调试器单步执行、系统调用

这张图把中断分成了三个维度:按来源、按可屏蔽性、按内部事件。你可能会问:“为什么异常和陷阱要单独列出来?”因为它们虽然都是内部中断,但一个是“被动出错”,一个是“主动请求”。搞混了这两个,写操作系统时很容易出问题。

好了,这一章的内容就到这里。中断的类型其实不难理解,关键是要记住每种中断的特点和适用场景。下一章我会讲中断控制器——那个负责管理所有中断请求的“交通警察”。


可可精品资料库