x86 - 操作系统:中断陷阱异常故障终止

Posted 嗷大墨

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了x86 - 操作系统:中断陷阱异常故障终止相关的知识,希望对你有一定的参考价值。

系列文章

x86 - CPU架构/寄存器详解 (一)x86、8086、i386、IA-32 是什么?
x86 - CPU架构/寄存器详解 (二) 实模式(8086模式)
x86 - CPU架构/寄存器详解 (三) 保护模式
x86 - 分段与分页详解
x86 - 特权级别 CPL / RPL / DPL / IOPL
x86 - 操作系统:中断、陷阱、异常、故障、终止
x86 - 描述符详解:存储/系统段描述符、门描述符

本文内容:

广义分类

类别对CPU来说和当前CPU所执行的指令的关系CPU接下来的事情程序员和用户的态度
中断被动的异步执行,没关系跳转到对应的 ISR希望有对应的中断,以使得CPU可以响应对应的中断,执行对应的ISR
异常被动的同步执行,有关系,当前指令执行出问题会导致异常跳转到对应的异常处理不希望出现异常,如果出现了,那往往是指令执行出现某些错误了
陷阱主动的同步执行,有关系,执行当前软中断指令才能够进入的软中断执行对应的软中断处理函数对于想要实现调试功能的程序员,有需要此陷阱的必要,其他人不用关心此点

狭义分类(x86分类)

类别原因异步/同步返回行为
中断来自I/O设备的信号异步总是返回到下一条指令
陷阱有意的异常同步总是返回到下一条指令
故障潜在可恢复的错误同步返回到当前指令
终止不可恢复的错误同步不会返回

概念

广义的中断概念

  中断,广义的来说通常被定义为一个事件,该事件触发改变处理器执行指令的顺序。狭义地来说,针对80x86体系,中断被分为中断(Interrupt)和异常(Exception),又叫同步中断和异步中断。
  在广义中断中,根据中断源的不同,可以把中断分为硬件中断和软件中断两大类,而硬件中断又可以分为外部中断和内部中断两类;
  同时,广义中断可以简单的分为中断(外部中断)以及陷阱(广义的陷阱也叫作异常,相当于内部中断+软中断);
  最后,在x86体系中,广义中断可以分为中断(外部中断)、故障、陷阱、中止。

  简单来说,中断(Interrupt)是由硬件产生的异步中断,而异常(Exception)则是处理器产生的同步中断。

硬件中断

  硬件中断是由外围硬件设备发出的中断信号引发的,以请求处理器提供服务,当 I/O 接口发出中断请求时,会被像8259A和 I/O APIC这样的中断控制器收集,并发送到处理器。硬件中断完全是随机产生的,与处理器的执行并不同步。当中断发生时,处理器要先执行完当前的指令,然后才对中断进行处理。
  硬件中断又可以分为 外部中断 和 内部中断(异常):

  • 外部中断:一般是指由计算机外设发出的中断请求,如:键盘中断、打印机中断、定时器中断等。外部中断是可以屏蔽的中断,也就是说,利用中断控制器可以屏蔽这些外部设备的中断请求。
  • 内部中断:是指因硬件出错(如突然掉电、奇偶校验错等)或运算出错(除数为零、运算溢出、单步中断等)所引起的中断。

软中断

  软中断是由 int 指令引起的中断处理。其实并不是真正的中断,它们只是可被调用执行的一般程序。例如:ROM BIOS中的各种外部设备管理中断服务程序(键盘管理中断、显示器管理中断、打印机管理中断等)以及DOS的系统功能调用(INT 21H)或者系统调用等都是软件中断。这类中断也不需要中断识别总线周期,中断号在指令中给出。
  • int3 是断点中断指令,机器指令码为CC。这条指令在调试程序的时候很有用。指令都是连续存放的,所谓的断点,就是某条指令的起始地址。int3是单字节指令,这是有意设计的。当需要设置断点时,可以将断点处那条指令的第1字节改成0xc,原字节予以保存。当处理器执行到int3时,即发生3号中断,转去执行相应的中断处理程序。
  • int n,注意,int3和int 3不是一回事。前者的机器码为CC,后者则是CD03,这就是通常所说的int n,其操作码为0xCD,第2字节的操作数给出了中断号,int n 的本质上仍然是异常。
  • into 是溢出中断指令,机器码为0xCE。处理器执行这条指令时,如果标志寄存器OF位是1,将会产生4号中断;反之这条指令什么也不做。
  要搞清楚什么是软中断,什么是硬中断,就必须了解软件中断存在的机理。现代的单片机应用中,往往伴随着操作系统的应用,单片机为了方便操作系统编程,会保留一些特权指令,方便操作系统控制整个机器,也为了方便软件中的一些原子操作,这些原子操作不允许中断破坏。
  软中断指令表面上类似于函数调用,与函数调用相比,更重要的功能是使单片机进入特权运行状态,在这个状态下,操作系统可以做一些用户状态下不能使用的功能。像51这类没有特权功能的单片机是不存在也没有必要存在软件中断功能的。区别软硬件中断的方法很简单,CPU的手册会告诉你哪条指令会产生软件中断。

BIOS中断

  调用中断处理程序比较方便,知道中断号以及参数传递规则就行了。事实上操作系统加载完自己之后,以中断处理程序的形式提供了很多基础功能,如硬盘读写功能,并把该例程的地址填写到中断向量表中。这样,无论在什么时候,用户程序需要该功能时,直接发出一个软中断即可。
  在以软中断形式提供的功能中,最有名的是BIOS中断,之所以称为BIOS中断,是因为这些中断功能是在计算机加电之后,BIOS程序执行期间建立起来的。换句话说,这些中断功能在加载和执行主引导扇区之前,就已经可以使用了。

  BIOS可能会为一些简单的外围设备提供初始化代码和功能调用代码,并填写中断向量表,但也有一些BIOS中断是由外部设备接口自己建立的。
  首先,每个外部设备接口,包括各种板卡,如网卡、显卡、键盘接口电路、硬件控制器等,都有自己的只读存储器(Read Only Memory,ROM),类似于BIOS芯片,这些ROM中提供了它自己的功能调用例程,以及本设备的初始化代码。按照规范,前两个单元的内容是0x55和0xAA,第三个单元是本ROM中以512字节为单位的代码长度;从第四个单元开始,就是实际的ROM代码。
  其次,我们知道,从内存物理地址A0000开始,到FFFF结束,有相当一部分空间是留给外围设备的。如果设备存在,那么,它自带的ROM会映射到分配给它的地址范围内。
  ○ 在计算机启动期间,BIOS程序会以2KB为单位搜索内存地址C0000~E0000之间的区域。当它发现某个区域的头两个字节是0x55和0xAA时,那意味着该区域有ROM代码存在,是有效的;
  ○ 接着,它对该区域做累加和检査,看结果是否和第三个单元相符。如果相符,就从第四个单元进入。这时,处理器执行的是硬件自带的程序指令,这些指令初始化外部设备的相关寄存器和工作状态;
  ○ 最后,填写相关的中断向量表,使它们指向自带的中断处理过程。

广义的陷阱概念

  在异常的介绍中会提到陷阱(Trap)的概念,陷阱虽然被归为异常的一种(在80x86体系中),但是通常意义上的陷阱其实是异常(内部中断)以及软中断的综合概念,是广义中断中的一部分。在操作系统中,有三种情况会导致CPU的控制流发生转移:用户态中通过系统调用进入内核态;异常发生,如除零、访问非法地址;设备中断,如硬盘完成读写请求。上面这些情况可以统称为陷阱(trap)。
  陷阱指令可以使执行流程从用户态陷入内核(这也就是为什么叫做陷阱)并把控制权转移给操作系统,使得用户程序可以调用内核函数和使用硬件从而获得操作系统所提供的服务。操作系统有很多系统调用接口供用程序调用,系统调用为用户程序提供手段,以便请求操作系统完成某些特权任务。系统调用可有多种方式,取决于底层处理器提供的功能。不管哪种,它都是进程请求操作系统执行功能的方法。系统调用通常会陷入中断向量的某个指定位置。这一般可由通用trap指令来完成,不过也有的系统(如 MIPS 系列)由专用syscall指令来完成系统调用。
  陷阱在一般情况下应该是透明的,即当执行完处理程序后能够恢复之前程序的状态。这就要求在陷入内核态时,内核要保存之前的寄存器等状态信息,当执行完处理程序之后再进行恢复。

优先级

  在一条指令执行期间,如果检测到不只一个中断或异常,那么按下表所列优先级通知系统。把优先级最高的中断或异常通知系统,其它优先级较低的异常被废弃,而优先级较高的中断则保持悬挂。

外部中断/中断(Interrupt)

  从处理器外部产生的中断叫外部中断,也就是狭义的中断概念。
  X86计算机的 CPU 为中断只提供了两条外接引脚:非屏蔽中断(Non Maskable Interrupt,NMI)和 可屏蔽中断(Interrupt,INTER)。其中 NMI 通常用于电源掉电和物理存储器奇偶校验;INTR可以通过设置中断屏蔽位来进行中断屏蔽,它主要用于接受外部硬件的中断信号,这些信号由中断控制器传递给 CPU。
  当中断发生时,处理器通过中断引脚NMI和INTR得到通知。此外它还应当知道发生了什么事,以便采取适当的处理措施。每种类型的中断都被统一编号,这称为中断类型号、中断向量或者中断号。

非屏蔽中断

  由于不可屏蔽中断的特殊性——几乎所有触发NMI的事件对处理器来说都是致命的,也不可恢复。所以这种情况下,就没有抢救的必要了。处理器不屏蔽来自NMI的中断请求。处理器在响应NMI中断时,不从外部硬件接收中断向量号。与8086/8088一样,在80386中,不可屏蔽中断所对应的中断向量号固定为2。为了不可屏蔽中断的嵌套,每当接受–个NMI中断,处理器就在内部屏蔽了再次响应NMI,这一屏蔽过程直到执行中断返回指令IRET后才结束。所以,NMI处理程序应以IRET指令结束。

可屏蔽中断

  可屏蔽中断通过INTR引脚进入处理器,处理器每次只能处理一个中断。而且多个设备同时发出中断请求的几率也是很高的,所以需要一个中断代理来处理多设备以及并发仲裁的问题。在个人计算机中,用得最多的中断代理就是8259芯片,即中断控制器,从8086处理器开始,它就一直提供着这种服务。即使是现在,在绝大多数单处理器的计算机中,也依然有它的存在。注意,所有的IRQ中断都是可屏蔽中断。

常见的中断控制器有两种:

可编程中断控制器8259A

  Intel处理器允许256个中断,中断号的范围是0~255,8259负责提供其中的15个,但中断号并不固定,允许软件根据自己的需要灵活设置中断号,以防止发生冲突。该中断控制器芯片有自己的端口号,可以像访问其他外部设备一样用in和out指令来改变它的状态,包括各引脚的中断号。所以它又叫可编程中断控制器。
  一个8259A芯片的组成可以分为5个主要的逻辑控件:中断屏蔽寄存器(IMR)、中断请求寄存器(IRR)、优先级仲裁单元(Prioriry Resolver)、中断向量寄存器(ISR)和控制逻辑单元(Control Logic)。一个8259A芯片有IRQ0~IRQ7七个IRQ引脚,一个IRQ对应着一个中断号,一个中断号对应着一个中断向量,一个中断向量对应着一个中断处理子程序(ISR,Interrupt Service Routine)。
  每片8259只有8个中断输入引脚,而在个人计算机上使用它,需要两块。如图所示,第一块8259芯片的代理输出INT直接送到处理器的INTR引脚,这是主片(Master);第二块8259芯片的INT输出送到第一块的引脚2上,是从片(Slave),两块芯片之间形成级联(Cascade)关系。

  如此一来,两块8259芯片可以向处理器提供15个中断信号。当时,接在8259上的15个设备都是相当重要的,如PS2键盘和鼠标、串行口、并行口、软磁盘驱动器、IDE硬盘等。现在,这些设备很多都已淘汰或者正在淘汰中,根据需要,这些中断引脚可以被其他设备使用。
  如图所示,8259的主片引脚 0(IR0)接的是系统定时器/计数器芯片;从片的引脚 0(IR0)接的是实时时钟芯片RTC。在8259芯片内部,有中断屏蔽寄存器(Interrupt Mask Register,IMR),这是个8位寄存器,对应着该芯片的8个中断输入引脚,对应的位是0还是1,决定了从该引脚来的中断信号是否能够通过8259送往处理器(0表示允许,1表示阻断,和一般的逻辑是反过来的)。当外部设备通过某个引脚送来一个中断请求信号时,如果它没有被IMR阻断,那就可以被送往处理器。8259的主片的端口号是0x20和0x21,从片的端口号是0xa0和0xa1,可以通过这些端口访问8259芯片,设置它的工作方式,包括IMR的内容。
  中断能否被处理,除了看8259芯片外,还取决于处理器。在处理器内部,标志寄存器有一个标志位IF,这就是中断标志(Interrupt Flag)。当为0时,所有从处理器INTR引脚来的中断信号都被忽略掉;当其为1时,处理器可以接受和响应中断。IF标志位可以通过两条指令 cli / sti 来关闭 / 开启中断。

高级可编程中断控制器(APIC)

  8259A 只适合单 CPU 的情况,为了充分挖掘 SMP 体系结构的并行性,能够把中断传递给系统中的每个 CPU 至关重要。基于此理由,Intel 引入了一种名为 I/O 高级可编程控制器的新组件,来替代老式的 8259A 可编程中断控制器。该组件包含两大组成部分:一是“本地 APIC”,主要负责传递中断信号到指定的处理器;举例来说,一台具有三个处理器的机器,则它必须相对的要有三个本地 APIC。另外一个重要的部分是 I/O APIC,主要是收集来自 I/O 装置的 Interrupt 信号且在当那些装置需要中断时发送信号到本地 APIC,系统中最多可拥有 8 个 I/O APIC。
  每个本地 APIC 都有 32 位的寄存器,一个内部时钟,一个本地定时设备以及为本地中断保留的两条额外的 IRQ 线 LINT0 和 LINT1。所有本地 APIC 都连接到 I/O APIC,形成一个多级 APIC 系统:
  目前大部分单处理器系统都包含一个 I/O APIC 芯片,可以通过以下两种方式来对这种芯片进行配置:
  1) 作为一种标准的 8259A 工作方式。本地 APIC 被禁止,外部 I/O APIC 连接到 CPU,两条 LINT0 和 LINT1 分别连接到 INTR 和 NMI 引脚。
  2) 作为一种标准外部 I/O APIC。本地 APIC 被激活,且所有的外部中断都通过 I/O APIC 接收。

  在CPU中集成了APIC,在SMP上,主板上有一个(至少一个,有的主板有多个IO-APIC,用来更好的分发中断信号)全局的APIC,它负责从外设接收中断信号,再分发到CPU上,这个全局的APIC被称作IO-APIC。还有一部分是“本地 APIC”,主要负责传递中断信号到指定的处理器;举例来说,一台具有三个处理器的机器,则它必须相对的要有三个本地 APIC。

内部中断/异常(Exception)

  异常就是内部中断,它们是处理器内部产生的中断,是由执行的指令引起的,表示在指令执行的过程中遇到了错误的状况,当处理器执行一条非法指令,或者因条件不具备,指令不能正常执行时,将引发这种类型的中断。
  以上所列的情况都是异常情况,所以内部中断又叫异常或者异常中断。比如,在执行除法指令 divi/div 时,遇到了被0除的情况(除数是0);再比如,使用jmp指令发起任务切换时,指令的操作数不是一个有效的 TSS 描述符选择子。
  内部中断不受标志寄存器IF位的影响,也不需要中断识别总线周期,它们的中断类型是固定的,可以立即转入相应的处理过程。
  异常分为三种:

  1. 第一种是程序错误异常,指处理器在执行指令的过程中,检测到了程序中的错误,并由此而引发的异常。
  2. 第二种是软件引发的异常。这类异常通常由into, int3和bound指令主动发起。这些指令允许在指令流的当前点上检查实施异常处理的条件是否满足。举个例子来说, into指令在执行时,将检查EFLAGS寄存器的OF标志位,如果满足为"1"的条件,则引发异常。
  3. 第三种是机器检查异常。这种异常是处理器型号相关的,也就是说,每种处理器都不太一样。无论如何,处理器提供了一种对硬件芯片内部和总线处理进行检查的机制,当检测到有错误时,将引发此类异常。

  根据异常情况的性质和严重性,异常又分为以下三种,并分别实施不同的处理:
  故障(Faults),故障通常是可以纠正的,比如,当处理器执行一个访问内存的指令时,发现那个段或者页不在内存中(P=0),此时,可以在异常处理程序中予以纠正(分配内存,或者执行磁盘的换入换出操作),返回时,程序可以重新启动并不失连续性。为了做到这一点,当故障发生时,处理器把机器状态恢复到引起故障的那条指令之前的状态,在进入异常处理程序时,压入栈中的返回地址(CS和EIP的内容)是指向引起故障的那条指令的,而不像通常那样指向下一条指令,如此一来,当中断返回时,将重新执行引起故障的那条指令,而且不再出错(如果引起异常的情况已经妥善处置),这意味着,异常并不总是意味着坏消息,相反,很多时候,它是有益的,就像益虫。如果没有异常,虚拟内存管理将无从谈起。
  陷阱(Traps)是在引起异常的指令之后,把异常情况通知给系统的一种异常。陷阱中断通常在执行了截获陷阱条件的指令之后立即产生,如果陷阱条件成立的话。陷阱通常用于调试目的,包括调试陷阱、单步中断指令 int3 、溢出检测指令 into。陷阱中断允许程序或者任务在从中断处理过程返回之后继续进行而不失连续性。因此,当此异常发生时,在转入异常处理程序之前,处理器在栈中压入陷, 截获指令的下一条指令的地址。
陷阱最重要的用途是在用户程序和内核之间提供系统调用接口。陷阱总返回到当前指令的下一条指令。现代的CPU都是有优先级概念的,用户程序运行在低优先级,操作系统运行在高优先级。高优先级的一些指令低优先级无法执行。有一些操作只能由操作系统来执行,用户想要执行这些操作的时候就要通知操作系统,让操作系统来执行。用户态的程序就是用这种方法来通知操作系统的。
  终止(Aborts),终止标志着最严重的错误,诸如硬件错误、系统表(GDT, LDT等)中的数据不一致或者无效,主要包括 双重故障异常(异常8)、协处理器段越界(异常9)。
这类异常总是无法精确地报告引起错误的指令的位置,在这种错误发生时,程序或者任务都不可能重新启动。一个比较典型的终止类异常是“双重故障”(中断号为8), 当发生一次异常后,处理器在转入该中断的处理程序时,又发生另外的异常(如该中断处理程序所在的段不在内存中,或者栈溢出),对于中断处理程序来说,很难从栈中获得有关如何纠正此类错误的明确信息,往往是发生极为重大的错误时才伴随着这种异常,所以再继续执行引起此异常的程序或任务已相当困难,操作系统通常只能把该任务从系统中抹去。

中断向量表 / 中断描述符表

中断程序执行方式

当发生中断时,有两种不同的中断程序执行方式:

向量中断

  由硬件提供中断服务程序入口地址,当中断为向量中断时,直接跳转到预先提供的中断服务程序执行,这种处理方式响应速度快
  向量中断模式用于RESET、NMI、异常处理。当向量中断产生时,控制器直接将PC赋值,如跳到0x0000000d处,而在0x0000000d地址处通常放置ISR服务程序地址LDR PC, =ISR_HANDLER。
  无论是由硬件设备触发的中断请求还是由CPU产生的异常,处理程序都在IDT表。保护模式下的中断向量分配如下:

非向量中断

  由软件件提供中断服务程序入口地址。当中断为非向量中断时,无论是什么外部中断源发出的中断,cpu将跳到指定的一段程序执行(称为中断解析程序),在解析程序里,通过判断相应的中断状态寄存器找到对应的中断源,跳转到相应的中断执行程序。
  非向量中断有点类似软件中断的处理方式,但是软中断(SWI)与非向量中断不同,它的入口是0x0000,0008。进入软中断后,系统变为管理模式。而非向量中断入口是0x0000,0018。它引导系统进入fiq/irq模式。这种处理方式简单,但是要通过软件查询来判断具体的中断服务程序,所以延迟时间较长。
  非向量中断模式,有一个寄存器标识位,跳转到统一的函数地址,此函数通过判别寄存器标识位和优先级关系进行中断处理。向量中断模式是当CPU读取位于0x18处的IRQ中断指令的时候,系统自动读取对应于该中断源确定地址上的指令取代0x18处的指令,通过跳转指令系统就直接跳转到对应地址函数中,节省了中断处理时间提高了中断处理速度。例如 ADC 中断的向量地址为0xC0,则在0xC0处放如下代码:ldr PC,=HandlerADC 当ADC中断产生的时候系统会自动跳转到HandlerADC函数中处理中断。
  非向量中断模式处理方式是一种传统的中断处理方法,当系统产生中断的时候,系统将INTPND寄存器中对应标志位置位,然后跳转到位于0x18处的统一中断函数中;该函数通过读取INTPND寄存器中对应标志位来判断中断源,并根据优先级关系再跳到对应中断源的处理代码中处理中断。

实模式 - 中断向量表 IVT

  在实模式下,位于内存最底端的1KB内存,是中断向量表(IVT,Interrupt Vector Table),定义了256种中断的入口地址,包括16位段地址和16位段内偏移量。当中断发生时,处理器要么自发产生一个中断向量,要么从 int n 指令中得到中断向量,或者从外部的中断控制器接受一个中断向量。然后,它将该向量作为索引访问中断向量表。具体的做法是,将中断向量乘以4,作为表内偏移量访问中断向量表,从中取得中断处理过程的段地址和偏移地址,并转到那里执行;
  当处于实模式下时,IDT 被初始化并由 BIOS 程序所使用。然而,一旦操作系统开始接管,IDT 就被移到内存的另一个区域,并进行第二次初始化,发生中断时操作系统会使用自己专门的中断服务程序(例程)(interrupt service routine,ISR)。

保护模式 - 中断描述符表 IDT

  在保护模式下,处理器对中断的管理是相似的,但并非使用传统的中断向量表来保存中断处理过程的地址,而是中断描述符表(IDT,地址存储在48位的中断描述符表寄存器 IDTR),表里保存的是和中断处理过程有关的描述符(8个字节),包括中断门、陷阱门、任务门


  事实上,调用门、任务门、中断门和陷阱门的描述符都十分相似(不同的门描述符将会在接下来的文章中详细介绍),从大的方面来说,因为都用于实施控制转移,因此都包括16位的目标代码段选择子,以及32位的段内偏移量。此外,中断门和陷阱门描述符只允许存放在IDT内,任务门可以位于GDT、LDT和IDT中
  和实模式下的中断向量表(IVT)不同,保护模式下的IDT不要求必须位于内存的最低端。
  和GDT一样,IDTR指向IDT,因为整个系统只需要一个IDT,所以GDTR和IDTR都直接存储目标地址,不需要选择器部分。这就意味着,中断描述符表IDT可以位于内存中的任何地方,只要IDTR指向了它,整个中断系统就可以正常工作。为了利用高速缓存使处理器的工作性能最大化,建议IDT的基地址是8字节对齐的(地址的数值能够被8整除)。处理器复位时, IDTR的基地址部分为0,界限部分的值为OxFFFF,16位的表界限值意味着IDT和GDT, LDT一样,表的大小可以是64KB,事实上,因为处理器只能识别256种中断,故通常只使用2KB,其他空余的槽位应当将描述符的P位清零。最后,与GDT不同的是, IDT中的第一个描述符也是有效的。

通过不同的门进行处理

  对中断的响应和异常的处理,80386允许通过使用中断门或陷阱门实现由当前任务之内的一个过程进行处理;也允许通过使用任务门实现由另一个任务进行处理。在当前任务之内的处理程序较为简单,并可以很快地转移到处理程序,但处理程序要负责保存及恢复处理器的寄存器等内容。转到不同任务的处理程序要花费较长时间,保存及恢复处理器寄存器内容的开销作为任务切换的一部分。
  使用当前任务内的处理程序的方法,在响应中断或处理异常时,对正执行任务的状态可直接进行访问,但是这就要求每一个任务之内都包含一个处理程序。使用独立任务的处理方法,使处理程序得到较好的隔离,但在响应中断或处理异常时,对原任务状态的访问变得较为复杂。需要注意得是,有些异常必须由中断门或陷阱门进行处理,如设备不可用故障(异常7),该故障在下列情况下产生:(1)在执行浮点指令时,控制寄存器CR0中的EM位或TS位为1;(2)在执行WAIT指令时,控制寄存器CR0中TS位及EM位都为1。需要注意的是,本异常的处理程序必须是一个过程而不能是任务,否则当处理程序发布一条IRET指令时,80386就设置TS位。然后协处理器再次执行这个发生故障的指令,发现TS是置位的,因此就再次发生异常7,结果是无休止的循环。处理程序能通过陷阱门被调用,因为执行期间可以允许中断。
  无效TSS异常必须使用任务门进行处理,以保证处理程序有一个有效得任务环境。其它的异常通常在任务环境之内进行处理。在任务内,异常被检测并且不必屏蔽中断,所以使用陷阱门。由陷阱门指示的异常处理程序是一个由所有任务共享的过程,所以该处理程序最好置于全局地址空间之内。如果各个任务要求有不同的处理程序,那么全局异常处理程序可保存一个各处理程序的入口表,并为引起异常的任务调用相应的处理程序。
  中断通常与正执行的任务没有关系,并可能从使用任务门提供的隔离中获得好处。要求较快响应的中断,通过中断门可以得到较好的处理。因为中断随时都可能发生,所以,通过中断门访问的中断处理程序,必须置于全局地址空间中,以便对所有的任务都有效。还需强调的是,80386程序绝不能调用一个低特权级的过程,当处理器调用一个中断或异常处理程序时,它实施相同的规则,所以,这样一个过程必须至少具有与在其任务上下文中被调用的、由任务所执行的最高特权级的过程相同的特权级。因此,在使用中断门时,中断处理程序通常必须被安排在特权级0,否则若正在特权级0执行时发生中断,则不能进入中断处理程序,而会引起通用保护故障(中断处理程序使用任务门时除外,因为任务切换可以从任何特权级切换到目标任务的任何特权级)。
  IDT初始化:
    内核在启用中断机制之前,必须把IDT表的起始地址载入IDTR寄存器,并初始化表中的每一个表项。
    IDT被初始化两次。第一次是在BIOS程序中,此时CPU还运行在实模式下,IDT被初始化并由bootloader程序使用。一旦操作系统启动,IDT会被搬运到RAM的受保护区域并被第二次初始化。
    在系统的初始化阶段,内核用来设置IDTR寄存器,专用汇编指令是lidt。

  异常及中断处理:
    当CPU执行完当前指令后,需要判断是否发生了异常或者中断。如果确实发生,则:
    1、确定所发生的异常或者中断在IDT表中的id(0-255)
    2、通过IDTR寄存器加载IDT表的基地址,并读取对应id的表项
    3、CPU通过相应表项的门描述符的段选择子,跳转到异常或者中断处理程序中执行。

  但要注意下面两点:
    1、权限检查:检查CPU的当前权限CPL与IDT表中相应表项的DPL,权限低的代码可以访问权限级别高的代码
    2、检查是否发生了特权级的变化。若中断发生时CPU运行在用户空间 ,而中断处理程序运行在内核态,特权级发生了变化,会引起堆栈的更换,即从用户堆栈切换到内核堆栈。而当中断发生在内核态时,即CPU 在内核中运行时,则不会更换堆栈。

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

权限检查

  和通过调用门实施的控制转移一样,处理器要对中断和异常处理程序进行特权级保护。当目标代码段描述符的特权级(可以用门描述符中的段选择子,从GDT或LDT中找到)低于当前特权级CPL时,即,在数值上,
    CPL<目标代码段的DPL
时,不允许将控制转移到中断或异常处理程序,违反此规则将引发常规保护异常(#GP)。
  不过,中断和异常处理程序的特权级保护也有一些特别之外。具体表现在:
   ○ 因为中断和异常的向量中没有RPL字段,故当处理器进入中断或异常处理程序,或者通过任务门发起任务切换时,不检查RPL.
   ○ 中断门、陷阱门也有自己的描述符特权级DPL,即门的DPL,但是,通常情况下不针对该DPL进行检查,除了用软中断int n和单步中断int3,以及into引发的中断和异常。在这种情况下,当前特权级CPL必须高于或者和门的特权级DPL相同,即,在数值上,
    CPL≤门描述符的DPL
  这主要是为了防止低特权级的软件通过软中断指令访问一些只为内核服务的例程,如页故障处理。相反地,对于硬件中断和处理器检测到异常情况而引发的中断处理,不检查门的DPL.中断和异常是随机产生的,不可预测。但是,有一点是可以确定的,即,它总是发生在某个任务内,是在某个任务正在执行的时候产生的,即使整个系统内只有一个任务。

中断处理

  中断和异常发生时,处理器将挂起当前正在执行的过程或者任务,然后执行中断和异常处理过程。返回时,处理器恢复程序或者任务的执行,而且被打断的程序或任务的执行不失连续性,除非遇到一个终止类型的异常。对于某些异常,处理器在转入异常处理程序之前,会在当前栈中压入一个称为错误代码的数值,帮助程序进一步诊断异常产生的位置和原因。
  中断处理过程如下:

  1. 在IDT中寻找门描述符:当通过一条INT指令进入一个中断服务程序时,在指令中给出一个中断向量。CPU先根据该向量在中断向量表中找到 IDT 中的门描述符,在这种情况下一般总是中断门。
  2. 检查门权限:将这个门的DPL(描述符优先级)与CPU的CPL(当前运行优先级)相比,CPL必须小于或等于DPL,也就是当前程序优先级别不低于中断处理程序,才能成功执行。注意,这里的检查只是检查当前的程序是否有访问该中断门的权限,只有通过权限检查才能去获取段选择码等信息,从而找到相应的段描述符。不过,如果中断是由外部产生或是因CPU异常而产生的话,那就免去了这一层检验。
  3. 检查段权限:穿过了中断门之后,还要进一步将目标代码段描述符中的DPL与CPL比较,目标段的DPL必须小于或等于CPL。也就是说,通过中断门时只允许保持或提升CPU的运行级别,而不允许降低其运行级别。这两个环节中的任何一个失败都会产生一次一般保护错误(General protection fault,GPF)。
  4. 保存现场以及堆栈操作:进入中断服务程序时,CPU要将当前EFLAGS寄存器的内容以及返回地址压入堆栈,返回地址是由段寄存器CS的内容和取指令指针EIP的内容共同组成的。如果中断是由异常引起的,则还要讲一个表示异常的出错代码也压入堆栈。进一步,如果中断服务程序的运行级别,也就是目标代码段的DPL,与中断发生时的CPL不同,那就要引起更换堆栈。CPU会根据寄存器TR的内容找到当前TSS结构,并根据目标代码的DPL,从这TSS结构中取出新的堆栈指针(SS加ESP),并装入其堆栈段寄存器SS和堆栈指针寄存器ESP,达到更换堆栈的目的。在这种情况下,CPU不但将EFLAGS、返回地址以及出错代码压入堆栈,还要先将原来的堆栈指针也压入新的堆栈
  5. 从中断处理程序中返回
      ○ 在执行完中断处理程序以后,如果有错误代码,需要先把错误代码出栈
      ○ 从栈中依次弹出EIP和CS以及EFLAGS寄存器的值
      ○ 栈恢复
        - 判断当前的特权级别和弹出的CS选择子的特权级别,如果不同,说明之前有过栈切换,从栈中再弹出ESP和SS寄存器的值
        - 如果相同,则继续使用当前栈

  堆栈操作过程如下:
  当中断和异常发生时,任务可能正在特权级别为0的全局空间(内核)中执行,也可能正在特权级别为3的局部空间内执行。因此,当处理器将控制转移到中断或异常处理程序时,如果处理程序运行在较高的特权级别上(数值上较低的),那么,将转换栈:

  • 根据处理程序的特权级别,从当前任务的TSS中取得栈段选择子和栈指针。处理器把旧栈的选择子和栈指针压入新栈。毕竟,中断处理程序也是当前任务的一部分;
  • 处理器把EFLAGS、CS和EIP的当前状态压入新栈;
  • 对于有错误代码的异常,处理器还要把错误代码压入新栈,紧挨着EIP之后,帮助程序进一步诊断异常产生的位置和原因;
  • 如果中断处理程序的特权级别和当前特权级别一致,则不用转换栈;
  • 处理器把EFLAGS、 CS和EIP的当前状态压入当前栈;
  • 对于有错误代码的异常,处理器还要把错误代码压入当前栈,紧挨着EIP之后。

参考资料

  《x86汇编语言 从实模式到保护模式》
  https://blog.csdn.net/q_l_s/article/details/56009472
  https://petpwiuta.github.io/…
  https://www.cnblogs.com/dongguolei/p/7896458.html
  https://www.cnblogs.com/vicrobert/p/4162538.html

异常处理的返回

异常处理的返回

异常可以分为四类:中断(interrupt)、陷阱(trap)、故障(fault)和终止(abort)。 这几种异常处理之后又有不同的返回方式,总的来讲:

类别 原因 异步/同步 返回行为
中断 来自I/O设备的信号 异步 总是返回到下一条指令
陷阱 有意的异常 同步 总是返回到下一条指令
故障 潜在可恢复的错误 同步 可能返回到当前指令
终止 不可恢复的错误 同步 不会返回

中断

中断是来自I/O设备的信号,在中断处理结束后会返回下一条指令。

陷阱

陷阱是有意的异常,是执行一条指令的结果。就像中断处理程序一样,陷阱处理程序将控制返回到下一条指令。

故障

故障由错误情况引起,它可能能够被故障处理程序修正。根据故障是否能够被修复,故障处理程序要么重新执行引起故障的指令,要么终止。

终止

终止是不可恢复的致命错误造成的结果,通常是一些硬件错误。Linux/x86-64系统中的异常终止处理程序将控制传递给一个内核abort例程,该例程会终止这个应用程序。

以上参考《深入理解计算机系统 原书第三版》

以上是关于x86 - 操作系统:中断陷阱异常故障终止的主要内容,如果未能解决你的问题,请参考以下文章

异常处理的返回

linux中断子系统

中断异常和系统调用

2018-2019-1 20165304 《信息安全系统设计基础》第七周学习总结

2018-2019-1 20165329 《信息安全系统设计基础》第七周学习总结

Intel异常的分类:错误,陷阱,终止