1. 中断与异常概述
各位同学,咱们今天聊聊操作系统里一个绕不开的话题——中断与异常。说实话,我刚入行那会儿,总觉得这俩概念差不多,都是“程序跑着跑着突然被叫停”。后来被坑过一次才明白,它们完全是两码事。
先问大家一个问题:你写了个死循环,CPU会一直卡在那吗?答案是不会。为什么?因为有中断机制在背后“搅局”。没有中断,你的电脑连键盘都响应不了,更别提多任务了。
什么是中断
中断,说白了就是硬件主动找CPU的麻烦。
想象一下:你正在专心写代码,突然有人拍你肩膀。你停下手中的活,转头看看是谁,处理完再回来继续写。这就是中断的完整流程。
在计算机里,中断是由硬件设备发起的。比如:
- 键盘按下一个键,触发键盘中断
- 网卡收到数据包,触发网络中断
- 硬盘读写完成,触发磁盘中断
我做过一个嵌入式项目,当时调试串口通信,数据老是丢。查了半天,发现是中断处理函数里干了太多活,导致下一个中断来了还没处理完。嗯,这里要注意:中断处理要快,越快越好。
核心要点:中断是异步的、由硬件触发的。CPU不知道它什么时候会来,只能被动响应。
什么是异常
异常就不一样了。它是CPU自己执行指令时发现不对劲,主动喊停。
你想想看,CPU执行一条指令,发现除数是0,怎么办?它不能假装没看见,必须停下来处理。这就是异常。
常见的异常场景:
- 除零异常(Divide by Zero)
- 缺页异常(Page Fault)
- 非法指令异常(Invalid Opcode)
- 段错误(Segmentation Fault)
我个人习惯把异常分成三类:
| 类型 | 特点 | 例子 |
|---|---|---|
| 故障(Fault) | 可恢复,处理完重新执行当前指令 | 缺页异常 |
| 陷阱(Trap) | 主动触发,用于系统调用 | int 0x80 |
| 终止(Abort) | 严重错误,无法恢复 | 硬件校验错误 |
避坑指南:我曾经在写驱动时,把缺页异常当成普通中断处理,结果系统直接崩溃。记住:异常是同步的,它和当前执行的指令有直接关系。中断是异步的,和当前指令无关。
中断与异常的区别
很多教材喜欢列一堆对比表格,我觉得没必要那么复杂。记住三个关键区别就够了:
- 触发源不同:中断来自外部硬件,异常来自CPU内部
- 同步性不同:中断是异步的,异常是同步的
- 可预测性不同:中断不可预测,异常可以预测(比如除零前就知道可能出问题)
举个例子你就明白了:
// 异常场景:除零
int a = 10 / 0; // CPU执行到这里,立刻触发异常
// 中断场景:键盘输入
while(1) {
// CPU在这死循环,键盘按下时触发中断
// 中断处理完,继续死循环
}
你看,除零是代码写死的,CPU执行到那一步必然触发。键盘中断呢?你永远不知道用户什么时候按键盘。
中断向量表的概念
好,现在问题来了:中断来了,CPU怎么知道该执行哪段代码?
答案就是中断向量表(Interrupt Vector Table,IVT)。
你可以把它想象成一个电话本。每个中断都有一个编号(中断号),CPU根据这个编号去查表,找到对应的处理函数地址。
关键理解:中断向量表就是一张映射表,把中断号和中断处理函数一一对应起来。
在x86架构下,中断向量表长这样:
中断向量表(IVT):
0号中断 -> 除零异常处理函数
1号中断 -> 单步调试异常处理函数
...
32号中断 -> 时钟中断处理函数
33号中断 -> 键盘中断处理函数
...
我记得第一次看Linux内核源码时,发现中断向量表初始化那部分代码特别有意思。它把0-31号留给异常,32号以后留给硬件中断。这个设计很巧妙,避免了中断号和异常号冲突。
下面这张图展示了中断和异常的处理流程:
注意事项:中断向量表的大小是固定的。在x86实模式下,IVT从内存地址0x0000开始,占用1024字节(256个中断向量,每个4字节)。如果你不小心写坏了这个区域,系统必崩无疑。我曾经在调试bootloader时干过这事,屏幕一黑,整个世界都安静了。
最后说一句:理解中断和异常,是理解操作系统内核的敲门砖。后面讲中断处理、上下文切换、系统调用,都离不开今天这些基础。把这些概念吃透了,后面的路就好走了。