前言:上一次我们说了说,异常和中断是什么,现在我们说说IA-32 架构下异常和中断的处理
还记得这个图吗?
异常和中断的处理方式很像,都是进入内核态。所以两者的处理方式很相同。在 IA-32 架构下有 256 种不同的异常和中断。
每个异常和中断都有唯一编号,称之为中断类型号,如类型号 0,是除法除以 0。而且,每一个异常和中断都有其对应的异常处理程序和中断服务程序,其入口地址放在一个专门的「中断向量表」或者「中断描述符」中
前 32 个类型(0~31)留给 CPU,剩下的(32~255)由操作系统定义。
通过 int n(31 < n < 256),使 CPU 自动转到 OS 给出的中断服务程序执行
一共有两种不同的表:「中断向量表」和「中断描述符」,原因是,我们从开机到由操作系统接管机器经历了两种模式:
实地址模式
-------->保护模式
在实模式中存储中断程序入口表的叫做:「中断向量表」,而在保护模式存储中断程序入口表的叫做:「中断描述符表」
现在分别来介绍两种表:
实地址模式:中断向量表
我们知道:在实地址模式
下通过 cs(16 位) ip(16 位) 来确定地址(cs << 4 + ip)。寻址空间只有 1M(20 位)。
中断向量表位于 0000H~03FFH。共 256 组,每组占四个字节 CS:IP 。
(就像这样)
是谁!让我的机器有了「中断向量表」
都是 BIOS!!!
BIOS 干了啥?
- 开机后系统首先在实地址模式下工作(只有 1MB 寻址空间)
- 开机过程中 BIOS 在实地址模式下准备「中断向量表」和「中断服务程序」
- BIOS 程序检查显卡,键盘和内存等等,并在 00000H~003FFH 区建立中断向量表,并在主存中准备了中断服务程序
- BIOS 利用 INT 指令执行特定的中断服务程序把OS从磁盘加载到内存中。例如,BIOS 可通过执行 int 0x19 指令来调用中断向量 0x19 对应的中断服务程序,将启动盘上的 0 号磁头对应盘面的 0 磁道 1 扇区中的引导程序装入内存
- BIOS(Basic Input/Output System)是基本输入/输出系统的简称,是针对具体主板设计的,与安装的操作系统无关
- BIOS 包含各种基本设备驱动程序,通过执行 BIOS 程序,基本设备驱动程序以中断服务程序的形式被加载到内存,以提供基本 I/O 系统调用
- 一旦进入保护模式,就不再使用BIOS
保护模式:中断描述符表
保护模式下,通过「中断描述符表」获异常处理或中断服务程序入口地址。
中断描述符表 (Interrupt Descriptor Table,IDT)是 OS 内核中的一个表,共有 256 个表项,每个表项占 8 个字节,IDT 共占用 2KB,由 IDTR 存放 IDT 在内存的首地址,每一个表项是一个中断门描述符,陷阱门描述符,或者任务门描述符
下面介绍中断描述符的格式
有了现在的知识,我们就可以来学习IA-32中异常和中断的处理
IA-32 中异常和中断的处理
每条指令 CPU 都会根据执行情况判断内部是否发生了异常事件,在指令结束后判断是否发生了外部中断请求
由此可见,异常事件和中断请求的 检测 都是在某一条指令执行过程中进行的,显然由硬件完成
在 CPU 根据 CS 和 EIP 取下条指令之前,会根据检测的结果判断是否进入中断响应阶段
异常和中断的响应也都是在某一条指令执行过程中或执行结束时进行的,显然也由硬件完成
现在开始叙述所有IA -32 中异常和中断响应过程
- 确定中断类型号 i(int i),从 IDTR 指向的 IDT 中取出第 i 个表项 IDTi
- 从 IDTi 中选择段选择符,从 GDTR 中得到 GDT,再从 GDT 中取出相应段描述符,得到对应异常或中断处理程序所在段的 DPL、基地址等信息。Linux 下中断门和陷阱门对应的即为内核代码段,所以 DPL 为 0,基地址为 0
- 若 CPL < DPL 或编程异常 IDTi 的 DPL<CPL,则发生 13 号异常。Linux 下,前者不会发生。后者用于防止恶意程序模拟 INT n 陷入内核进行破坏性操作
- 若 CPL≠DPL,则从用户态换至内核态,以使用内核栈。切换栈的步骤:
- 读 TR 寄存器,以访问正在运行的用户进程的 TSS 段
- TSS 段中保存的内核栈的段选择符和栈指针分别装入寄存器 SS 和 ESP,然后在内核栈中保存原来用户栈的 SS 和 ESP
- 若是故障,则将发生故障的指令的逻辑地址写入 CS 和 EIP,以使处理后回到故障指令执行。其他情况下,CS 和 EIP 不变,使处理后回到下条指令执行
- 在当前栈中保存 EFLAGS、CS 和 EIP 寄存器的内容(断点和程序状态)
- 若异常产生了一个硬件出错码,则将其保存在内核栈中
- 将 IDTi 中的段选择符装入 CS,IDTi 中的偏移地址装入 EIP,它们是异常处理程序或中断服务程序第一条指令的逻辑地址(Linux中段基址 = 0)
下个时钟周期开始,从 CS:EIP 所指处开始执行异常或中断处理程序!
什么是 TSS
内核中的 TSS 段记录了每个进程的状态信息,例如,每个进程对应的页表、task 和 mm 等结构信息。
红色那块就是 TSS 段。
从中断程序中跳出来
中断或异常处理程序最后一条指令是 IRET
。CPU 在执行 IRET 指令过程中完成以下工作
- 从栈中弹出硬件出错码(保存过的话)、EIP、CS 和 EFLAGS
- 检查当前异常或中断处理程序的 CPL 是否等于 CS 中最低两位,若是则说明异常或中断响应前、后都处于同一个特权级,此时,IRET 指令完成操作。否则,再继续完成下一步工作。
- 从内核栈中弹出 SS 和 ESP,以恢复到异常或中断响应前的用户级进程所使用的栈。
- 检查DS、ES、FS 和 GS 段寄存器的内容,若其中有某个寄存器的段选择符指向一个段描述符且其 DPL 小于 CPL,则将该段寄存器清 0。这
是为了防止恶意应用程序(CPL = 3)利用内核以前使用过的段寄存器(DPL=0)来访问内核地址空间。
执行完 IRET 指令后,CPU 回到原来发生异常或中断的进程继续执行