X86汇编语言-从实模式到保护模式—笔记(49)-第17章 中断和异常处理与抢占式多任(1)
第17章中断和异常的处理与抢占式多任务中断和异常中断的内容是很重要的。至少,我们需要知道处理器会引发哪些异常,在什么情况下会引发异常,这对于编写正确的程序,以及调试这些程序来说,是非常重要的。
17.1 中断和异常
【17.1.1 中断和异常概述】
中断和异常的作用是指示系统中的某个地方发生了一些事情,需要引起处理器(包括正在执行中的程序和任务)的注意。处理器将从当前正在执行的程序或任务转移到另一个例程或者任务中去。该例程叫做中断处理程序,或者异常处理程序。如果是一个任务,则发生任务切换。
1、中断,包括硬件中断和软中断
硬件中断是由外围硬件设备发出的中断信号引发的,以请求处理器提供服务。中段发生时,处理器要先执行完当前的指令,然后才对中断进行处理。
软中断是由int n指令引发的中断处理,n是中断号或者叫类型吗。
2、异常,处理器内部产生的中断。表示在指令执行的过程中遇到了错误的状况。当处理器执行一条非法指令,或者因条件不具备,指令不能正常执行时,将引发这种类型的中断。
异常分为三种:一是程序错误异常;二是软件引发的异常,通常由into、int3和bound指令主动发起;三是机器检查异常。
根据异常情况的性质和严重性,异常又分为以下三种,并分别实施不同的处理:
故障(Faults)。通常是可以纠正的,当故障发生时,处理器把及其状态恢复到引起故障的那条指令之前的状态,在进入异常处理程序时,压入栈中的返回地址(CS和EIP的内容)是指向引起故障的那条指令的,而不像通常那样指向下一条指令。如此一来,当中断返回时,将重新执行引起故障的那条指令,而且不再出错。
陷阱(Traps)陷阱中断通常在执行了截获陷阱条件的指令之后立即产生,如果陷阱条件成立的话。陷阱通常用于调试目的,比如单步中断指令int3和溢出检测指令into。陷阱中断允许程序或者任务在从中断处理过程返回之后继续进行而不失连续性。因此,当此异常发生时,在转入异常处理程序之前,处理器在栈中压入陷阱截获指令的下一条指令的地址。
终止(Aborts)终止标志着最严重的错误,诸如硬件错误、系统表(GDT、LDT等)中的数据不一致或者无效。这类异常总是无法精确地报告引起错误的指令的位置,在这种错误发生时,程序或者任务都不可能重新启动。一个比较典型的终止类型异常就是“双重故障”(中断号为8),当发生一次异常后,处理器在转入该中断的处理程序时,又发生另外的异常(如该中断处理程序所在的段不在内存中,或者栈溢出)。通常操作系统只能把该任务从系统中抹去。
中断和异常发生时,处理器将挂起当前正在执行的过程或者任务,然后执行中断和异常处理过程。返回时,处理器恢复程序或者任务的执行,而且被打断的程序或任务的执行不失连续性,除非遇到一个终止类型的异常。对于某些异常,处理器在转入异常处理程序之前,会在当前栈中压入一个称谓错误代码的数值,帮助程序进一步诊断异常产生的位置和原因。
当中断和异常发生时,NMI和异常的向量是由处理器自动给出的;硬件中断的向量是由I/O中断控制器芯片送给处理器的;软中断的向量是由指令中的操作数给出的。
从80486之后开始,处理器内部一般集成了浮点运算部件x87 FPU,不再需要安装独立的数学协处理器,所以有些和浮点运算有关的异常可能不会产生(比如向量为9的协处理器段超越故障),wait和fwait指令用于主处理器和浮点处理器部件(FPU)之间的同步,它们应当放在浮点指令之后,以捕捉任何浮点异常。
从1993年的Pentium处理器开始,引入了用于加速多媒体处理的多媒体扩展技术(MMX),该技术使用单指令多数据(Single-Instruction,Multiple-Data,SIMD)执行模式,以便于在64位的寄存器内实施并行的整数计算。在之后的岁月里,随着处理器的更新换代,该项技术也多次扩展,第一次扩展被称为SSE(SIMD Extension),第二是SSE2,第三次是SSE3。和SIMD有关的异常是从Pentium III处理器开始引入的。
bound(Check Array Index Against Bounds)指令用于检查数组的索引是否在边界之外,其格式为
bound r16,m16
bound r32,m32
其具有两个操作数,目的操作数是寄存器,包含了数组的索引;源操作数必须指向内存位置,在哪里包含了两个成对出现的字或者双字,分别是数组索引的下限和上限。如果执行bound指令,数组的索引小于下标的下限,或者大于下标的上限,则产生异常。
ud2(Undefined Instruction)指令是从Pentium Pro处理器开始引入的,它只有操作码而没有操作数,机器代码为0F 0B。
ud2 ;机器码0F 0B
执行该指令时,会引发一个无效操作码异常。该指令没有别的用处,典型地用于软件测试。尽管异常是用该指令故意引发的,但是,在转入异常处理程序时,压入栈中的指令指针是指向该指令的,而非下一条指令。
【17.1.2 中断描述符表、中断门和陷阱门】
是实模式下,位于内存最低端的1KB内存,是中断向量表(IVT),定义了256中中断的入口地址,包括16位段地址和16位段内偏移量。当中断发生时,处理器要么自发陈胜一个中断向量,要么从int n指令中得到中断向量,或者从外部的中断控制器接受一个中断向量。然后,它将该向量作为索引访问中断向量表。具体的做法是,将中断向量乘以4,作为表内偏移量访问中断向量表,从中取得中断处理器过程的段地址和偏移地址,并转到那里执行。
在保护模式下,处理器对中断的管理是相似的,但并非使用传统的中断向量表来保存中断处理过程的地址,而是中断描述符表(Interrupt Descriptor Table,IDT)。顾名思义,在这个表里,保存的是和中断处理过程有关的描述符,包括中断门、陷阱门和任务门。
事实上,调用门、任务门、中断门和陷阱门的描述符非常相似,从大的方面来说,因为都用于实施控制转移,故都包括16位的目标代码段选择子,以及32位的段内偏移量。
从图17-1中可以见到,中断门和陷阱门仅仅有一比特的差别。中断门和陷阱门描述符只允许存放在IDT内,任务门可以位于GDT、LDT和IDT中。
是实模式下的中断向量表(IVT)不同,保护模式下的IDT不要求必须位于内存的最低端。事实上,在处理器内部,有一个48位的中断描述符表寄存器(Interrupt Descriptor Table Register,IDTR),保存着中断描述符表在内存中的线性基地址和界限。如图17-2所示,和GDT一样,因为整个系统中只需要一个IDT就够了,所以,GDTR与IDTR不想LDTR和TR,没有也不需要选择器部分。
这就意味着,中断描述符表IDT可以位于内存中的任何地方,只要IDTR指向了它,整个中断系统就可以正常工作。为了利用高速缓存使处理器的工作性能最大化,建议IDT的基地址是8字节对齐的(地址的数值能够8整除)。处理器复位时,IDTR的基地址部分为0,界限部分的值为0xFFFF。16位的表界限值意味着IDT和GDT、LDT一样,表的大小可以是64KB,但是,事实上,因为处理器只能识别256种中断,故通常只使用2KB,其他空余的槽位应当将描述符的P位清零。最后,与GDT不同的是,IDT中的第一个描述符也是有效的。
如图17-3所示,在保护模式下,当中断和异常发生时,处理器用中断向量乘以8的结果去访问IDT,从中取得对应的描述符。因为IDT在内存中的位置是由IDTR指示的,所以这很容易做到。
注意,从图17-3中可以看出,这里没有考虑分页,也没有考虑门描述符是任务门的情况,因为任务门的处理比较特殊。中断门和陷阱门中有目标代码段描述符的选择子,以及段内偏移量。取决于选择子的TI位,处理器访问GDT或者LDT,取出目标代码段的描述符。接着,从目标代码段的描述符中取得目标代码段所在的基地址,再同门描述符中的偏移量相加,就得到了中断处理程序的32位线性地址。如果没有开启分页功能,该线性地址就是物理地址;否则,送页部件转换成物理地址。注意,当处理器用中断向量访问IDT时,要访问的位置超出了IDT的界限,则产生常规保护异常(#GP)。
【17.1.3 中断和异常处理程序的保护】
和通过调用门实施的控制转移一样,处理器要对中断和异常处理程序进行特权级保护。当目标代码段描述符的特权级(可以用门描述符中的段选择子,从GDT或LDT中找到)低于当前特权级CPL时,即,在数值上,CPL<目标代码段的DPL时,不允许将控制转移到中断或异常处理程序,违反此规则引发常规保护异常(#GP)
不过,中断和异常处理程序的特权级保护也有一些特别之外。具体表现在:
1、因为中断和异常的向量中没有RPL字段,故当处理器进入中断或异常处理程序,或者通过任务门发起任务切换时,不检查RPL;
2、中断门、陷阱门也有自己的描述符特权级DPL,即门的DPL,参见图17-1.但是,通常情况下不针对该DPL进行检查,除了用软中断int n和单步中断int3,以及into引发的中断和异常。在这种情况下,当前特权级CPL必须高于,或者和门的特权级DPL相同,即,在数值上,CPL≤门描述符的DPL。
这主要是为了防止低特权级的软件通过软中断指令访问一些只为内核服务的例程,如页故障处理。相反地,对于硬件中断和处理器检测到异常情况而引发的中断处理,不检查门的DPL。
中断和异常是随机产生的,不可预测。但是,有一点是可以确定的,即,它总是发生在某个任务内,是在某个任务正在执行的时候产生的,即使整个系统内只有一个任务。
当中断和异常发生时任务可能正在特权级别为0的全局空间(内核)中执行,也可能正在特权级别为3的局部空间内执行。因此,当处理器将控制转移到中断或异常处理程序时,如果处理程序运行在较高的特权级别上(数值上较低的),那么,将转换栈:
1、根据处理器程序的特权级别,从当前任务的TSS中取得栈段选择子和栈指针。处理器把旧栈的选择子和栈指针压入新栈。毕竟,中断处理器程序也是当前任务的一部分。
2、处理器把EFLAGS、CS和EIP的当前状态压入新栈。
3、对于由错误代码的异常,处理器还要把错误代码压入新栈,紧挨着EIP之后,如如17-4(a)所示。
4、如果中断处理程序的特权级别和当前特权级别一致,则不用转换栈。
5、处理器把EFLAGS、CS和EIP的当前状态压入当前栈。、
6、对于有错误代码的异常,处理器还要把错误代码压入当前栈,紧挨着EIP之后,如图17-4(b)所示。
中断门和陷阱门的区别不大,通过中断门进入中断处理程序时,EFLAGS寄存器的IF位被处理器自动清零,以禁止嵌套的中断,当中断返回时,将从栈中恢复EFLAGS寄存器的原始状态。陷阱中断的优先级较低,当通过陷阱门进入中断处理程序时,EFLAGS寄存器的IF位不变,以允许其他中断优先处理。
EFLAGS寄存器的IF位仅影响硬件中断,对NMI、异常和int n的软件中断不起作用。
【17.1.4 中断任务】
当中断和异常发生时,如果根据中断向量从IDT中找到的描述符是任务门,则不是进行一般的中断处理过程,而是发起任务切换。如图17-5所示,这是通过中断发起任务切换的原理。
用中断发起任务切换,直觉上的好处是方便。比如,因为硬件中断的发生时客观的,很容易用它来实现一个剥夺式的、抢占式的多任务系统(硬件调度机制)。
不过,这并不是它的最主要的目的。想象一下,当前任务正在执行的时候,突然发生了终止类型的异常,比如双重故障(#DF),会怎么样。在这种情况下,要想用iretd指令返回到那个任务继续正常执行已无可能。在这种情况下,如果把双重故障的处理程序定义成任务,会非常恰当。当双重故障发生时,执行任务切换,切换到内核任务重去,从容地把发生故障的任务从系统中抹去,回收内存空间,并重新调度其他任务的执行,会是最好的解决办法。具体地说,在中断机制中使用任务门可以获得以下好处:
1、被中断的那个程序或任务的整个执行环境可以被完整地保存起来(保存到它的TSS中)。
2、由于接管控制的是一个新的任务,因此,可以使用一个全新的0特权级栈。这可以有效地防止因当前任务的0特权级栈遭到破坏而使系统崩溃。
3、由于是切换到一个新任务,因此,它有一个独立的地址空间。
当然,和一般的中断处理过程相比,利用中断发起任务切换也有不利的一面,那就是速度很慢,毕竟要保存大量的机器状态,并进行一系列的特权级和内存访问的检查工作。
因中断和异常而发起任务切换时,不再保存CS、EIP和状态,但是,在任务切换工作完成后,处理器要把错误代码压入新任务的栈中(如果有错误代码的话)。
任务是不可重入的,因此,在进入中断任务之后和执行iretd指令之前,必须关中断,以防止因相同的中断再次发生而产生常规保护异常(#GP)。作为对任务门的保护,和中断门、陷阱门一样,只对通过int3、int n和into指令发起的任务切换实施特权级检查,即,只有在数值上符合以下条件,才允许通过以上指令发起任务切换:CPL≤任务门的DPL。在其他异常和硬件中断的情况下,不检查任务门的特权级。另外,由于是任务切换,部队目标代码段的特权级别进行检查。
页:
[1]