0%

操作系统读书笔记03

os读书笔记3

中断和异常处理

中断和异常处理概述

什么是中断和异常?

中断和异常是强制性的执行流转移。效果是从当前正在执行的程序和任务转移到一个特殊的句柄的例程或任务。

中断产生于硬件发出信号,中断的产生是随机的,对正在执行的程序来说是异步的。

软件对于外部设备的请求,比如IO读入、调用别的硬件设备的情况下可以产生中断,使用的是INT n指令。

异常是在处理器执行指令的过程中发现错误而产生的,比如除零异常等。

处理器可以检测出多种不同的错误,包括保护异常,页错误,内部机器错误。

处理器如何处理?

处理器收到中断信号或检测到异常时,便会挂起当前正在运行的进程或任务,前去执行中断或异常处理程序,中断或异常处理程序执行完之后,处理器一般会继续执行被终端的进程或者任务,有两种情况例外:无法从发生的异常中恢复、中断使当前的程序终止。

实模式和保护模式下,中断向量表一样吗?

在实模式和保护模式下,中断向量表是不一样的。在实模式下,中断向量表是一个固定的地址,称为中断向量表基址,其中包含了一系列的中断向量,每个向量对应着一个特定的中断号。而在保护模式下,中断向量表被称为中断描述符表,它保存了每个中断的处理程序地址和相关的属性信息。

有关中断和异常了解性的内容

异常和中断向量

异常和中断向量存储着每种异常、中断对应的处理程序的入口地址。

异常向量和中断向量的作用是将发生的异常和中断与相应的处理程序关联起来,使处理器能够根据异常或中断类型找到对应的处理程序并进行处理。这样可以保证系统能够有效地响应和处理异常和中断事件。

向量号0到31分配给异常和NMI中断使用,但是未完全使用,部分未使用的作为保留留给未来使用。

32到255之间的向量号留给用户使用。

这些中断不在 Intel 的保留部分之列,一般被分配给外部 I/O 设备,允许它们通过某个外部硬件中断机制向处理器传递信号。

中断源和异常源

中断源

中断有两个来源,一个是硬件中断(外部中断),另一个是软件中断。

硬件中断是通过处理器的引脚接收的。任何通过 INTR 引脚或局部 APIC 传递到处理器的外部中断都被称作可屏蔽硬件中断。通过INTR 引脚传递的可屏蔽硬件中断可使用所有 Intel 架构定义的中断向量(0~255);而通过局部 APIC 传递的部分只能使用 16~255 号向量。使用 EFLAGS 寄存器的 IF 位就可以屏蔽全部可屏蔽硬件中断。当 0 号中断到 15 号中断通过局部 APIC 传递时,APIC 会指出错误的向量号。

软件中断

将中断向量号作为 INT 指令的操作数即可通过 INT 指令在程序中产生中断。比如,指令 INT 35 即可调用第 35 号中断处理例程。
0 到 255 号中断均可使用 INT 指令调用。但是,当处理器预先定义好的 NMI 中断被这样调用时,处理器作出的响应与真正 NMI 中断发生时的响应并不一样。也就是说,执行 INT 2(NMI的向量号)时,NMI 处理例程被调用,但是处理器的 NMI 硬件处理并未被激活。
EFLAGS 的 IF 位并不能屏蔽由 INT 指令产生的中断。

异常源

异常信号有三个来源,一个是处理器检测到的程序错误异常,一个是软件产生的异常,另外一个是机器检测异常。

  • 程序错误异常:当检测到程序错误异常时,处理器产生一个或多个异常。Intel 为每个处理器可检测到的异常定义了一个向量号。异常又进一步被划分为错误,陷阱和终止。

  • 软件产生的异常:INTO,INT 3和BOUND指令允许在软件中产生异常。这些指令允许在指令流中检测指定的异常条件。

  • 机器检测异常:P6 系列和 Pentium 处理器同时提供了内部和外部的机器检测机制,用来检查内部芯片部件的操作和总线传输。这些机制组成了扩展异常机制(并不能独立完成)。当检测到一个机器检测错误时,处理器发出一个机器检测异常(18 号向量),并返回一个出错码。

异常的分类:故障、陷阱和中止

故障是一种通常能被修正的异常,修正过后,程序能够不失连续性地继续运行。当报告错误发生时,处理器将机器状态恢复到执行错误之前的状态。

错误处理程序的返回地址和EIP指向产出错误的指令。

只有少数几个异常被报告为错误,它们是不可恢复的,且处理器的上下文中的内容也会有部分丢失。

一个例子:当执行 POPAD
指令是堆栈越过了堆栈段的尾部。异常处理例程会看到 CS:EIP 恢复原样,就好象 POPAD从未执行,但处理器状态却被改变了(通用寄存器)。

这种情况被视为程序错误,若应用程序产生这样的错误则会被操作系统终止。

陷阱是一种异常,当引起陷阱的指令发生时,马上产生该异常。陷阱不影响程序的连续性,陷阱处理程序的返回地址指向引起陷阱指令的下一条指令。

中止是另一种异常,它并不总是报告产生异常指令的确切位置,也不允许引起终止的进程或任务重新执行。中止被用于报告严重错误,比如硬件错误,不一致或非法系统值。

程序或任务的重新执行

对于故障类异常,返回地址指向产生错误的指令。所以当错误处理程序返回时,产生错误的指令将会重新执行。常见的错误就是缺页错误,错误处理程序会调用mmap一段内存再返回,这个时候再执行一次产生错误的指令,使程序不失去连续性。

对于陷阱类异常,返回地址指向产生陷阱指令的下一条指令。当一条转移指令执行过程中检测到陷阱时,返回地址指针则反映了执行转向的情况。比如当执行JMP指令时,检测到有陷阱异常,返回地址指针指向的是JMP的目的地址,而不是JMP指令后的下一条指令。所有的陷阱异常保证进程或任务的继续执行不失连续性。

中止类异常不支持程序或任务继续执行。终止处理例程的作用是:当有终止异常发生时,收集处理器的各种相关诊断信息,并关闭进程或系统。

开启和禁止中断

根据处理器的状态和 EFLAGS 的 IF 位和 RF 位,处理器可以禁止某些中断的产生。

任何通过 INTR 引脚或局部 APIC 传递到处理器的外部中断都被称作可屏蔽硬件中断。通过INTR 引脚传递的可屏蔽硬件中断可使用所有 Intel 架构定义的中断向量(0~255);而通过局部 APIC 传递的部分只能使用 16~255 号向量。

使用 EFLAGS 寄存器的 IF 位就可以屏蔽全部可屏蔽硬件中断。当 0 号中断到 15 号中断通过局部 APIC 传递时,APIC 会指出错误的向量号

异常和中断的优先级

指令边界有多个异常或中断发生,处理器将以预定的顺序来为它们提供服务。

各类异常和中断源的优先关系如图所示:

Untitled

中断描述符表

中断描述符表(IDT)为每一个异常或中断向量对应的例程或任务分配了一个门描述符。和GDT和LDT一样,IDT也是由一系列由 8 个字节组成的描述符组成的(保护模式下)。和 GDT 不同的是,IDT 中的第一个元不是 NULL 描述符。异常或中断向量号乘上 8 即可得到IDT 中的描述符的索引(即门描述符包含的字节数)由于只有 256 个中断或异常向量,所以 IDT 不必包含多于 256 个描述符。并且可以包含不足 256 个的描述符,因为只有那些确实发生的异常或中断才需要一个描述符。所有 IDT 中的空描述符须将存在位置位 0。

如何构成?

和GDT表类似,构成由:

  1. 段选择子:IDT 中的每个条目都由一个段选择子组成,用于标识处理程序所在的代码段或数据段。

  2. 偏移量:每个中断或异常都有一个关联的处理程序,偏移量用于指示处理程序在代码段中的起始位置。

  3. 标志位:IDT 中的每个条目还包含一些标志位,用于定义中断或异常的类型、访问权限等。

IDT 的结构是一个数组,每个数组元素代表一个中断向量,从 0 到 255。

如何获得中断处理程序的地址?

在操作系统初始化过程中,会将中断处理程序的地址注册到中断向量表中的相应条目中。

当中断发生时,CPU根据中断号从中断向量表中找到对应的条目,并获取其中保存的中断处理程序的地址。CPU会跳转到这个地址,开始执行中断处理程序。

即通过访问中断向量表对应的条目来获得中断处理程序的地址。

如何设置中断描述符表寄存器?

构建好IDT表之后,在x86架构下,可以使用lidt汇编指令加载IDTR寄存器,IDTR寄存器是CPU的寄存器,用来存储IDT表的基地址和大小。

Untitled

IDT 描述符

IDT包含以下三种门描述符

  • 任务门描述符

  • 中断门描述符

  • 陷阱们描述符

Untitled

任务门格式和GDT/LDT中的任务门一样,包含异常或中断处理程序的TSS的段选择符

中断门和陷阱门和调用门类似,包含一个指针(段选择符和offset),处理器用来将执行流转移至异常或中断处理代码段中的处理程序。但是在处理EFLAGS的IF位的方式上有所不同。

中断与异常处理

中断过程调用的流程是怎样的?

当程序触发中断时,会通过IDTR查询IDT,获得中断处理程序的段选择子和偏移,再查询GDT或LDT获得段描述符,配合生成一个指向中断处理程序的地址,之后将EFLAGS寄存器、CS寄存器、EIP寄存器的当前值压栈保存,然后转到中断处理程序执行,执行完返回到程序中正常执行。

Untitled

不同优先级上,处理方式一样吗?

不一样。

相同优先级的情况下,不会发生堆栈切换。

而中断程序优先级高于原程序的情况下,会发生堆栈切换。

中断程序优先级低于原程序的情况下,则会发生报错。

如果发生堆栈切换,处理器会做哪些操作?

Untitled

指向返回后使用的栈指针也被压入栈中,也就是SS寄存器和ESP寄存器。处理器要用到的堆栈段选择符和栈指针则从当前进程的TSS中得到。

处理器将 EFLAGS,SS,ESP,CS,EIP,还有出错码从当前进程的堆栈拷贝到处理例程的堆栈。

在处理程序执行完毕返回的时候还需要还原堆栈。

如果没发生堆栈切换,处理器会做哪些操作?

没有发生对战切换,处理程序和原程序将会使用同一个堆栈。处理器会将Error Code,EIP,CS,EFLAGS压入栈中。

处理程序执行完毕返回的时候只需要还原EIP即可。

中断处理过程后,如何返回,处理器做了哪些操作?

处理器会将压入栈的内容弹出来,恢复寄存器。

弹出EFLAGS寄存器值即可恢复EFLAGS寄存器至执行中断处理程序前。

弹出CS、EIP等即可恢复。

如果在调用处理例程时堆栈发生了切换,则在返回时,IRET 指令还将切换回被中断进程的堆栈。

异常和中断处理过程的保护

如果异常和中断处理例程的特权级比 CPL 底,则处理器不允许这种调用发生。否则将产生通用保护异常(#GP)。

因为中断和异常向量没有 RPL,所以当发生中断和异常时,并不检查 RPL。

仅当中断或异常由 INT n, INT 3,或 INTO 指令产生时,处理器才检查中断或陷阱门的DPL。此时,CPL 必须小于或等于门的 DPL。这种限制防止了运行于 3 级的应用程序或进程使用软件中断来访问异常处理的关键代码,如页错误处理例程,因为这些例程位于更高一级的代码段中(数值上更小的特权级)。对于由硬件产生的中断和处理器检测到的异常,处理器则忽略掉中断或陷阱门中的 DPL。

异常和中断的发生通常是随机的,这些特权规则有效地为异常和中断处理例程能运行在哪些特权级加上了限制。

异常和中断处理过程的标志使用方式

访问异常或中断处理程序时,在将EFLAGS寄存器的内容保存进栈后,处理器会清EFLAGS寄存器的TF位。

当调用异常和中断处理例程时,处理器在将 EFLAGS寄存器的内容保存进栈后,还会清 VM,RF,和 NT 位。

清 TF 位则可以禁止指令跟踪,以使中断响应不受影响。后继的 IRET 指令则使用保存在栈中的 EFLAGS 寄存器中的值,恢复TF(和 VM,RF,及 NT)位。

中断门与陷阱门的唯一区别是什么?

在于处理器处理EFLAGS寄存器的IF位的方式。

当通过中断门访问异常或中断处理例程时,处理器清除 IF 位,阻止别的中断干扰当前的中断处理程序。后继的 IRET 指令用存储在栈中的 EFLAGS 的内容恢复 IF 的值。

而陷阱门调用处理程序时,IF 位不受影响。