x86架构学习之中断
前言
读本文前,你需要了解x86的操作模式、寻址方式、特权级相关知识,具体参考这篇文章
中断与异常
打断cpu当前任务的事件都叫中断,根据是否可屏蔽分为:
- 可屏蔽中断,即可通过软件配置相关寄存器(通常是PIC中的某个寄存器)来开启和关闭。
- 不可屏蔽中断,即中断不可关闭,一般用在一些非常紧急或灾难性的事件上。
根据中断的来源,我们将中断分为外部中断和内部异常
外部中断
简称中断,后文如果没特别说明,中断都指外部中断,由外部IO事件触发,由于时钟源来自CPU外部,与CPU时钟不同源,因此是异步事件。
外部中断,一般是由CPU的外部引脚电信号触发,这些引脚留给硬件厂商去定义中断,例如主板厂商可以将USB中断挂载70号中断引脚上,MCU厂商可以将片上外设(SPI、I2C等)的中断挂载在指定中断线上。
内部异常
由CPU内部事件触发,触发时钟源与CPU时钟同源,因此是同步事件。
- 处理器探测异常
- 故障,可修复,修复后可正常运行,如0除数、计算结果溢出、缺页等。故障发生时,入栈作为故障处理函数返回地址的,是故障指令所在的地址,而不是下一条指令的地址(这是一般中断和异常的行为),以便修复故障后,指令能够再次被执行,如缺页。
- 陷阱,通过软件插入断点指令触发。
- 异常终止,应用级灾难,只能终止当前任务。
- 编程异常,即通过软件插入中断或异常(INT n)指令来触发,由于需要显示的中断指令,因此,被称为软中断,利用软中断,可以模拟任意外部中断信号,在操作系统,软中断多用于系统调用。
- 机器检测异常,硬件上检测到错误时,会触发的异常,这个异常属于纯硬件故障,不同的版本的x86架构实现不同,因此不存在兼容性。(可以理解为硬件bug检测机制,硬件的设计不可能完美)
中断号
x86的中断号总共有256个,其中前32个(0~31)中断号已经被CPU硬件预定义或保留,软件无法修改,这32个中断号也是异常,后224个中断号可由硬件厂商自由配置使用。
中断号 | 异常类型 | 简称 | 描述 | 触发源 |
---|---|---|---|---|
0 | 故障 | #DE | Divide | 错误使用 DIV and IDIV 指令 |
1 | 陷阱 | #DB | Debug | 任意代码和数据访问都有可能 |
2 | 故障 | NMI Interrupt | 不可屏蔽中断 | |
3 | 陷阱 | #BP | Breakpoint | INT3 指令. |
4 | 陷阱 | #OF | Overflow | INTO 指令. |
5 | 故障 | #BR | BOUND Range Exceeded | BOUND 指令. |
6 | 故障 | #UD | Invalid Opcode (Undefined Opcode) | 执行无效指令 |
7 | 故障 | #NM | Device Not Available (No Math Coprocessor) | Floating-point or WAIT/FWAIT 指令 |
8 | 故障 | 异常终止 | #DF | Double Fault |
9 | 故障 | 异常终止 | #MF | CoProcessor Segment Overrun (reserved) |
10 | 故障 | #TS | Invalid TSS | 硬件任务切换时,TSS段无效 |
11 | 故障 | #NP | Segment Not Present | 访问不存在的段 |
12 | 故障 | #SS | Stack Segment Fault | 非法栈操作,ss寄存器加载无效值 |
13 | 故障 | #GP | General Protection | 各种权限校验不通过 |
14 | 故障 | #PF | Page Fault | 访问无效页 |
15 | 保留 | |||
16 | 故障 | #MF | 浮点运算错误 (数学错误,math fault) | 浮点运算相关指令 |
17 | 故障 | #AC | Alignment Check | 非对齐访问内存 |
18 | 异常终止 | #MC | Machine Check | Error codes (if any) and source are model dependent |
19 | 故障 | #XM | SIMD Floating-Point Exception | SIMD 浮点指令错误使用 |
20 | #VE | Virtualization Exception | EPT violations5 | |
21 | #CP | Control Protection Exception | The RET, IRET, RSTORSSP, and SETSSBSY instructions can generate this exception. When CET indirect branch tracking is enabled, this exception can be generated due to a missing ENDBRANCH instruction at the target of an indirect call or jump. | |
22-31 | 保留 | |||
32-255 | 可屏蔽中断 | 外部中断引脚触发 或 INT n 指令触发 |
中断描述符表(interrupt descriptor table,IDT)
中断描述符主要用于说明该中断的入口地址,中断描述符表则是将中断描述符一项一项管理在一个数组中,每一项中断描述符占用8个字节(64位模式下,占用16字节),其下标索引对应中断号。
描述符主要字段如下:
描述符特权级 | 描述符类型 | 16位段选择符 | 16位偏移 |
---|
中断描述符表的地址存放在cpu的idtr
寄存器中,idtr
由专门的指令lidt
指令进行修改 。
idtr
寄存器包含48位,结构如下,32位的基地址和16的大小。
门描述符
描述符中有一个专门的字段type,用于标志该描述符的类型,该字段占用5位,可以表示多达32种描述符,它可以表示段描述符或门描述符,以及具体是什么段描述符、什么门描述符。对于门描述符,它被用于在不同特权级之间穿越,门描述符 目前有四种,任务门、中断门、陷阱门、调用门,被中断(描述符表)用到的描述符类型有3种:
- 任务门,用于硬件任务切换,16位段选择符指向下一个任务的任务状态段(TSS),16位偏移被忽略,TSS中存放有下一个任务的cs和ip寄存器的值。任务状态段描述符(TSSD)放在全局描述符表中(GDT),考虑到兼容性,Linux并没有使用x86的硬件任务。
- 中断门,由16位段选择符+16位偏移指定中断入口地址,执行中断程序前,会关中断(设置EFLAGS寄存器IF标志为0)
- 陷阱门,同中断门相同,但是不关中断。
3种门描述符详细结构如图
64位机的门描述符结构如图
ps: Linux利用中断门处理中断,利用陷阱门处理异常。
由门描述符找到中断程序入口
中断描述符表中存放的门描述符,其中有两个字段用于定位中断入口地址:段选择符和偏移。先通过段选择符,在全局描述符表(GDT)中找到段描述符,然后从段描述符中取出段基地址,再将基地址加上偏移得到中断程序入口的线性地址(这里涉及分段、分页概念,具体参考x86内存寻址)。
中断硬件行为
中断调用
- 当一个中断(异常)发生时,硬件根据其中断号,在idtr指向的中断描述符表中,找到对应索引的中断描述符。
- 如果中断描述符是中断门或陷阱门,则
- 如果中断由外部中断或处理器探测异常触发,则检查当前cs的RPL(即CPL)与门描述符指定的段描述符的DPL的大小关系,如果
C
P
L
>
D
P
L
段
CPL>DPL_{段}
CPL>DPL段,则触发异常(中断不能降权),如果
C
P
L
=
D
P
L
段
CPL=DPL_{段}
CPL=DPL段,说明特权级没有发生变化,则不需要更换堆栈,直接在原栈上保存
ip、cs、eflags
,如果 C P L > D P L 段 CPL>DPL_{段} CPL>DPL段,说明特权提高了,需要更换堆栈,根据tr段寄存器找到任务状态段(TSS),在TSS中提取中断程序的ss和sp寄存器值(栈段和栈指针,由软件预先写入),得到新栈,然后在新栈中保存当前的ss、sp、ip、cs、eflags
,最后将新的ss和sp值加载到对应寄存器。 - 如果中断由编程异常触发,则还多两步操作:检查段描述符前,还需检查门描述符,判断,触发异常的程序是否有权限访问该门,即
C
P
L
≤
D
P
L
门
CPL≤DPL_{门}
CPL≤DPL门,这一步是防止用户态程序通过
int n
访问中断门提权。最后,如果异常生成了错误码(部分异常会产生错误吗,用于指示出错原因),则需要在新栈上压入错误码。
- 根据门描述符及其指定的段描述符,找到中断入口地址(段+偏移),加载到cs和ip寄存器,开始执行中断处理程序。
- 如果中断由外部中断或处理器探测异常触发,则检查当前cs的RPL(即CPL)与门描述符指定的段描述符的DPL的大小关系,如果
C
P
L
>
D
P
L
段
CPL>DPL_{段}
CPL>DPL段,则触发异常(中断不能降权),如果
C
P
L
=
D
P
L
段
CPL=DPL_{段}
CPL=DPL段,说明特权级没有发生变化,则不需要更换堆栈,直接在原栈上保存
- 如果中断描述符是任务门,处理流程与上面差不多,只不过,TSS不再通过tr寄存器获取,而是通过任务门描述符中的任务段选择符字段获取。
中断返回
中断返回通过iret
指令进行,返回过程是调用过程的逆过程。
IA-32e操作模式下的中断行为
之前说的中断行为都是IA-32操作模式下的行为,即32位机和仅使用32位模式的64位机,具体操作模式的知识,请参考前言中提到的文章。IA-32e模式即64位机相对于32位机扩展出来的模式,也称为长模式。长模式下,中断行为与IA-32有主要如下不同:
- 中断行为中操作的多数寄存器扩展为64位
- 中断描述符扩展成16字节,其中增加的ist字段,用于栈切换。因为长模式下,CPU非常有限的使用段寄存器,ss中的选择符被无视,IA-32操作模式我们知道,新的ss和sp是从TSS中获取的,但是现在不再是,ss选择符被强制为空,RPL被设置成CPL。
- 描述符中多出一个ist字段,这个字段是长模式为了特殊中断(NMI, double-fault, and machine-check)设计的,当然你可用于其他中断。当中断发生并且需要换栈时,如果ist为0,则行为和以前一样,否则,ist表示下标,指向TSS中的中断栈表(IST)项,cpu会将这个表项的指针加载到RSP寄存器。
可编程中断控制器
可编程中断控制器(programmable interrupt controller,PIC),它的主要用途有如下:
- 可设置中断的优先级1。两个中断事件同时出现时,高优先级的优先得到响应。高优先级会抢占低优先级,产生中断嵌套。同优先级中断不会抢占自己。
- 中断重定向。可以将外部设备的中断信号重新挂载到不同的CPU中断线上(对应不同中断号)
- 中断屏蔽。可暂时屏蔽不需要的中断。
- 中断分发2。对于多核CPU,每个cpu存在一个本地APIC,A代表高级(advanced),并且还存在一个总APIC,总APIC会根据各本地APIC中断的繁忙程度,进行调度分发中断。