80386除了保持8086/80386的相关功能外,还增强了中断处理能力,并引入了“异常”的概念。本文将介绍80386中断和异常的机制。这里下载本文所有源代码。
8086/8088把中断分为内部中断和外部中断两大类。为了支持多任务和虚拟存储器等功能,80386把外部中断称为“中断”,把内部中断称为“异常”。与8086/8088一样,80386通常在两条指令之间响应中断或异常。80386最多处理256种中断或异常。
对80386而言,中断是由异步的外部事件引起的。外部事件及中断响应与正执行的指令没有关系。通常,中断用于指示I/O设备的一次操作已完成。与8086/8088一样,80386有两根引脚INTR和NMI接受外部中断请求信号。INTR接受可屏蔽中断请求。NMI接受不可屏蔽中断请求。在80386中,标志寄存器EFLAGS中的IF标志决定是否屏蔽可屏蔽中断请求。
外部硬件在通过INTR发出中断请求信号的同时,还要向处理器给出一个8位的中断向量。处理器在响应可屏蔽中断请求时,读取这个由外部硬件给出的中断向量号。处理器对这个中断向量号并没有规定。但在具体的微机系统中,系统必须通过软件和硬件的配合设置,使得给出的这个中断向量号不仅与外部中断源对应,而且要避免中断向量号使用冲突情况的出现。可编程中断控制器芯片8259A可配合80386工作,能够根据设置向处理器提供上述中断向量号,还能处理中断请求的优先级。每个8259A芯片可以支持8路中断请求信号,如果使用9个8259A芯片(一个主片,8个从片),就可使80386在单个引脚INTR上接受多达64个中断源的中断请求信号。
处理器不屏蔽来自NMI的中断请求。处理器在响应NMI中断时,不从外部硬件接收中断向量号。与8086/8088一样,在80386中,不可屏蔽中断所对应的中断向量号固定为2。为了不可屏蔽中断的嵌套,每当接受一个NMI中断,处理器就在内部屏蔽了再次响应NMI,这一屏蔽过程直到执行中断返回指令IRET后才结束。所以,NMI处理程序应以IRET指令结束。
异常是80386在执行指令期间检测到不正常的或非法的条件所引起的。异常与正执行的指令有直接的联系。例如,执行除法指令时,除数等于0。再如,执行指令时发现特权级不正确。当发生这些情况时,指令就不能成功完成。软中断指令“INT n”和“INTO”也归类于异常而不称为中断,这是因为执行这些指令产生异常事件。
80386识别多种不同类别的异常,并赋予每一种类别以不同的中断向量号。异常发生后,处理器就象响应中断那样处理异常。即根据中断向量号,转相应的中断处理程序。把这种中断处理程序称为异常处理程序可能更合适。
根据引起异常的程序是否可被恢复和恢复点不同,把异常进一步分类为故障(Fault)、陷阱(Trap)和中止(Abort)。我们把对应的异常处理程序分别称为故障处理程序、陷阱处理程序和中止处理程序。
故障是在引起异常的指令之前,把异常情况通知给系统的一种异常。80386认为故障是可排除的。当控制转移到故障处理程序时,所保存的断点CS及EIP的值指向引起故障的指令。这样,在故障处理程序把故障排除后,执行IRET返回到引起故障的程序继续执行时,刚才引起故障的指令可重新得到执行。这种重新执行,不需要操作系统软件的额外参与。故障的发现可能在指令开始执行之前,也可能在指令执行期间。如果在指令执行期间检测到故障,那么中止故障指令,并把指令的操作数恢复为指令开始执行之前的值。这可保证故障指令的重新执行得到正确的结果。例如,在一条指令的执行期间,如果发现段不存在,那么停止该指令的执行,并通知系统产生段故障,对应的段故障处理程序可通过加载该段的方法来排除故障,之后,原指令就可成功执行,至少不再发生段不存在的故障。
陷阱是在引起异常的指令之后,把异常情况通知给系统的一种异常。当控制转移到异常处理程序时,所保存的断点CS及EIP的值指向引起陷阱的指令的下一条要执行的指令。下一条要执行的指令,不一定就是下一条指令。因此,陷阱处理程序并不是总能根据保存的断点,反推确定出产生异常的指令。在转入陷阱处理程序时,引起陷阱的指令应正常完成,它有可能改变了寄存器或存储单元。软中断指令、单步异常是陷阱的例子。
中止是在系统出现严重情况时,通知系统的一种异常。引起中止的指令是无法确定的。产生中止时,正执行的程序不能被恢复执行。系统接收中止后,处理程序要重新建立各种系统表格,并可能重新启动操作系统。硬件故障和系统表中出现非法值或不一致的值是中止的例子。
在一条指令执行期间,入检测到不只一个中断或异常,那么按下表所列优先级通知系统。把优先级最高的中断或异常通知系统,其它优先级较低的异常被废弃,而优先级较高的中断则保持悬挂。
80386响应 |
中断/异常类型 |
优先级 |
调试故障 |
最高 |
|
其它故障 |
↓ |
|
陷阱指令INT n和INTO |
↓ |
|
调试陷阱 |
↓ |
|
NMI中断 |
↓ |
|
INTR中断 |
最低 |
象中断分为多种类型一样,异常也可分为多种类型。
80386识别的多种不同类别的异常及赋予的对应中断向量号如下表所示。某些异常还以出错码的形式提供一些附加信息传递给异常处理程序,出错代码列中的“无”表示没有出错代码,“有”表示有出错代码。
异 |
向量号 |
异常名称 |
异常类型 |
出错代码 |
相关指令 |
0 |
除法出错 |
故障 |
无 |
DIV,IDIV |
|
1 |
调试异常 |
故障/陷阱 |
无 |
任何指令 |
|
3 |
单字节INT3 |
陷阱 |
无 |
INT 3 |
|
4 |
溢出 |
陷阱 |
无 |
INTO |
|
5 |
边界检查 |
故障 |
无 |
BOUNT |
|
6 |
非法操作码 |
故障 |
无 |
非法指令编码或操作数 |
|
7 |
设备不可用 |
故障 |
无 |
浮点指令或WAIT |
|
8 |
双重故障 |
中止 |
有 |
任何指令 |
|
9 |
协处理器段越界 |
中止 |
无 |
访问存储器的浮点指令 |
|
0AH |
无效TSS异常 |
故障 |
有 |
JMP、CALL、IRET或中断 |
|
0BH |
段不存在 |
故障 |
有 |
装载段寄存器的指令 |
|
0CH |
堆栈段异常 |
故障 |
有 |
装载SS寄存器的任何指令、对SS寻址的段访问的任何指令 |
|
0DH |
通用保护异常 |
故障 |
有 |
任何特权指令、任何访问存储器的指令 |
|
0EH |
页异常 |
故障 |
有 |
任何访问存储器的指令 |
|
10H |
协处理器出错 |
故障 |
无 |
浮点指令或WAIT |
|
11H—0FFH |
软中断 |
陷阱 |
无 |
INT n |
由上表可见,保护模式下的某些中断向量号的分配与实模式的中断向量号发生了冲突。实模式下的中断向量号的分配基于PC微机系统的8086/8088 CPU,上表中的中断向量号的分配是80386所规定的。实际上,Intel在宣布8086/8088时,保留了这些发生冲突的中断向量号。尽管发生这样的冲突,但以80386为CPU的微机系统仍可保持与以8086/8088为CPU的微机系统的兼容,原因是在80386的实模式下,几乎不发生那些中断向量号与外部硬件中断请求时所提供的中断向量号存在冲突的异常。需要注意的是,在保护模式下必须重新设置8259A中断控制器,以产生不与异常相冲突的硬件中断向量。
当发生故障,控制转移到故障处理程序时,所保存的断点CS及EIP的值指向引起故障的指令,以便在排除故障后恢复执行。
除法出错是一种故障。当执行DIV指令或IDIV指令时,如果除数等于0或者商太大,以至于存放商的操作数容纳不下,那么就产生这一故障。除法出错故障不提供出错码。
如果BOUND指令发现被测试的值超过了指令中给定的范围,那么就发生边界检查故障。边界检查故障不提供出错码。
如果80386不能把CS和EIP所指向存储单元的位模式识别为某条指令的部分,那么就发生非法操作码故障。当出现如下情况时,发生这样的故障:(1)操作码字段的内容不是一个合法的80386指令代码;(2)要求使用存储器操作数的场合,使用了寄存器操作数;(3)不能被加锁的指令前使用了LOCK前缀。非法操作码故障不提供操作码。
设备不可用故障支持80387数字协处理器。在没有80387协处理器硬件的系统中,可用该异常的处理程序代替协处理器的软件模拟器。在发生任务切换时,使得只有在新任务使用浮点指令时,才进行80387寄存器状态的切换。设备不可用故障不提供出错码。该故障在下列情况下产生:(1)在执行浮点指令时,控制寄存器CR0中的EM位或TS位为1;(2)在执行WAIT指令时,控制寄存器CR0中TS位及EM位都为1。需要注意的是,本异常的处理程序必须是一个过程而不能是任务,否则当处理程序发布一条IRET指令时,80386就设置TS位。然后协处理器再次执行这个发生故障的指令,发现TS是置位的,因此就再次发生异常7,结果是无休止的循环。处理程序能通过陷阱门被调用,因为执行期间可以允许中断。
当正从任务状态段TSS装入选择子时,如果发生了除了段不存在故障以外的段异常时,就发生无效TSS故障。在进入故障处理程序时,保存的CS及EIP指向发生故障的指令;或者该故障作为任务切换的一部分发生时,指向任务的第一条指令。
无效TSS故障提供了一个出错码,出错码的格式如下图所示。其中选择子部分是指向引起故障的TSS的选择子。16位的出错代码的主要成分是选择子,指向引起故障的TSS的选择子。高13位是选择子的索引部分,TI位是描述符表指示位。
出错代码的格式 |
|||
BIT15—BIT3 |
BIT2 |
BIT1 |
BIT0 |
选择子的索引部分 |
TI |
IDT |
EXT |
上图所示出错码格式是异常时出错码的一般格式。从图中可见出错码中不含选择子的RPL,而由IDT位和EXT位代替。当处理某一异常或外部中断时,又发生了某种异常,那么EXT位置1。当从中断描述符表IDT中读出表项并产生异常时,IDT位置1,这只在中断或异常的处理期间才会发生。当没有选择子时,构成出错码选择子部分的值为0。
一些引起无效TSS故障的原因如下:
TSS描述符中的段限长小于103;
无效的LDT描述符,或者LDT未出现;
堆栈段不是一个可写段;
堆栈段选择子索引的描述符超出描述符表界限;
堆栈段DPL与新的CPL不匹配;
堆栈段选择子的RPL不等于CPL;
代码段选择子索引的描述符超出描述符表界限;
代码段选择子不指向代码段;
非一致代码段的DPL不等于新的CPL;
一致代码段的DPL大于新的CPL;
对应DS、ES、FS或GS的选择子指向一个不可读段(如系统段);
对应DS、ES、FS或GS的选择子索引的描述符超出描述符表的界限。
处理器在把描述符装入非SS段寄存器的高速缓冲时,如果发现描述符其它方面有效,而P位为0(表示对应段不存在),那么在引用此描述符时就发生段不存在故障。有关SS段的情形纳入堆栈段故障。在进入故障处理程序时,保存的CS及EIP执行发生故障的指令;或者该故障作为任务切换的一部分发生时,指向任务的第一条指令。
段不存在故障提供了一个包含引起该故障的段选择子的出错代码。出错码的格式如上图所示。选择子索引部分为引起段不存在故障的段描述符选择子的索引。
当处理器检测到用SS寄存器进行寻址的段有关的某种问题时,就发生堆栈段故障。在进入故障处理程序时,保存的CS及EIP指向发生故障的指令;或者该故障作为任务切换的一部分发生时,指向任务的第一条指令。堆栈段故障提供一个出错码,出错码的格式也如上图。
具体地说,当出现下列三种情况时,将引起堆栈段故障:
(1)在堆栈操作时,偏移超出段界限所规定的范围。这种情况下的出错码是0。例如PUSH操作时,堆栈溢出。
(2)在由特权级变换所引起的对内层堆栈的操作时,偏移超出段界限所规定的范围。这种情况下的出错码包含有内层堆栈的选择子。
(3)装入到SS寄存器(高速缓冲寄存器)的描述符中的存在位为0。这种情况下的出错码包含有对应的选择子。
上述第一种情况是容易辨别的。第二和第三种情况的辨别要通过判断出错代码所含选择子所指示的描述符中的存在位进行。如果存在位为1,那么是第二种情况;否则是第三种情况。
除了明确列出的段异常外,其它的段异常都被视为通用保护故障。在进入故障处理程序时,保存的CS及EIP指向发生故障的指令;或者该故障作为任务切换的一部分发生时,指向任务的第一条指令。通用保护故障提供一个出错码,出错码的格式也如上图所示。
根据处理程序可能作出的响应,通用保护故障可分为如下两类:
(1)违反保护方式,但程序无须中止的异常。这类故障提供的出错码为0。这种异常在应用程序执行特权指令或I/O访问时发生,支持虚拟8086程序的系统或支持虚拟I/O访问的系统需要模拟这些指令,并在模拟完成产生故障的指令后,重新执行被中断的程序。
(2)违反保护方式,并导致程序终止的异常。这类故障提供的出错码可能为0,也可能不为0(能确定选择子时)。引起这类故障的一些原因如下:
向某个只读数据段或代码段写;
从某个只能执行的代码段读出;
将某个系统段描述符装入到数据段寄存器DS、ES、FS、GS或SS;
将控制转移到一个不可执行的段;
在通过段寄存器CS、DS、ES、FS或GS访问内存时,偏移超出段界限;
当访问某个描述符表时,超过描述符表段界限;
把PG位为1但PE位为0的控制信息装入到CR0寄存器;
切换到一个正忙的任务。
对上述两类通用保护故障的辨别,可通过检查引起故障的指令和出错码进行。如果出错码非0,那么肯定是第二类通用保护故障。如果出错码是0,那么需要进一步检查引起故障的指令,以确定它是否是系统支持的可以模拟的指令。
关于页故障的详细说明请见后面的文章。
协处理器出错故障指示协处理器发生了未被屏蔽的数字错误,如上溢或下溢。在引起故障的浮点指令之后的下一条浮点指令或WAIT指令,把协处理器出错作为一个故障通知给系统。协处理器出错故障不提供出错码。
调试异常有故障类型,也有陷阱类型。调试程序可以访问调试寄存器DR6,以确定调试异常的原因和类型。调试异常不提供出错码。
INT3是一条特别的单字节“INT n”指令。调试程序可利用该指令支持程序断点。INT3指令被看成是一种陷阱,而不是一个中断。当由于执行INT3指令进入异常3处理程序时,被保存的CS和EIP指向紧跟INT3的指令,即INT3后面的字节。INT3陷阱不提供出错码。
INTO指令提供条件陷阱。如果OF标志为1,那么INTO指令产生陷阱;否则不产生陷阱,继续执行INTO后面的指令。在进入溢出处理程序时,被保存的CS和EIP指向INTO指令的下一条指令。溢出陷阱不提供出错码。
当系统正在处理一个异常时,如果又检测到一个异常,处理器试图向系统通知一个双重故障,而不是通知第二个异常。双重故障属于中止类异常,所以在转入双重故障处理程序时,被保存的CS和EIP可能不指向引起双重故障的指令,而且指令的重新启动不支持双重故障。双重故障提供的出错码为0。
当正处理一个段故障异常时,有可能又产生一个页故障。在这种情况下,通知给系统的是一个页故障异常而不是双重故障异常。但是,如果正处理一个段故障或页故障时,又一个段故障被检测到;或者如果正处理一个页故障时,又一个页故障被检测到,那么就引起双重故障。
当正处理一个双重故障时,又一个段或页故障被检测到,那么处理器暂停执行指令,并进入关机方式。关机方式类似于处理器指令一条HLT指令后的状态:处理器空转,并维持到处理器接收到一个NMI中断请求或者被重新启动为止。在关机方式下,处理器不响应INTR中断请求。
双重故障通常指示系统表出现严重的问题,例如段描述符表、页表或中断描述符表出现问题。双重故障处理程序在重建系统表后,可能不得不重新启动操作系统。
协处理器段越界异常属于中止类异常,这是因为引起该异常的指令不能被重新启动。当浮点指令操作数超出段界限时,产生该中止异常。协处理器段越界异常不提供出错码。在异常处理程序入口保存的CS及EIP指向被中止的指令。这种中止不是系统的中止,而是只影响检测到这种异常时正执行的指令所在的程序。
80386实模式下的中断和异常的转移方法与8086相同。这里介绍的中断和异常的转移方法是指 80386在保护模式下响应中断和处理异常时所采用的转移方法。
与8086/8088一样,在响应中断或者处理异常时,80386根据中断向量号转对应的处理程序。但是,在保护模式下,80386不使用实模式下的中断向量表,而是使用中断描述符表IDT。在保护模式下,80386把中断向量号作为中断描述符表IDT中描述符的索引,而不再是中断向量表中的中断向量的索引。
象全局描述符表GDT一样,在整个系统中,中断描述符表IDT只有一个。中断描述符表寄存器IDTR指示IDT在内存中的位置。由于80386只识别256个中断向量号,所以IDT最大长度是2K。
中断描述符表IDT所含的描述符只能是中断门、陷阱门和任务门。也就是说,在保护模式下,80386只有通过中断门、陷阱门或任务门才能转移到对应的中断或异常处理程序。
从前文中给出的门描述符的格式可见,门描述符包含由选择子和偏移构成的48位全指针。另外,双字计数字段对中断门、陷阱门和任务门而言无意义。
由硬件自动实现的中断响应和异常处理的步骤如下:
首先,判断中断向量号要索引的门描述符是否超出IDT的界限。若超出界限,就引起通用保护故障,出错码为中断向量号乘8再加2。
其次,从IDT中取得对应的门描述符,分解出选择子、偏移量和描述符属性类型,并进行有关检查。描述符只能是任务门、286中断门、286陷阱门、386中断门或386陷阱门,否则就引起通用保护故障,出错码是中断向量号乘8再加2。如果是由INT n指令或INTO指令引起转移,还要检查中断门、陷阱门或任务门描述符中的DPL是否满足CPL<=DPL(对于其它的异常或中断,门中的DPL被忽略)。这种检查可以避免应用程序执行INT n指令时,使用分配给各种设备用的中断向量号。如果检查不通过,就引起通用保护故障,出错码是中断向量号乘8再加2。门描述符中的P位必须是1,表示门描述符是一个有效项,否则就引起段不存在故障,出错码是中断向量号乘8再加2。
最后,根据门描述符类型,分情况转入中断或异常处理程序。
对于异常处理,在开始上述步骤之前,还要根据异常类型确定返回点;如果有出错代码,则形成符合出错码格式的出错码,并在实际执行异常处理程序之前把出错码压入堆栈。为了保证栈的双字边界对齐,16位的出错码以32位的值压入,其中高16位的值未作定义,对于16位段也是如此。
如果中断向量号所指示的门描述符是386中断门或386陷阱门,那么控制转移到当前任务的一个处理程序过程,并且可以变换特权级。与其它调用门的CALL指令一样,从中断门和陷阱门中获取指向处理程序的48位全指针。其中16位选择子是对应处理程序或代码段的选择子,它指示全局描述符表GDT或局部描述符表LDT中的代码段描述符;32位偏移指示处理程序入口点在代码段内的偏移量。
通过中断门或陷阱门的转移过程如下所示,该过程由硬件自动进行。
(1)若选择子为空,则产生通用保护故障;
(2)取对应的描述符;
(3)若非存储段描述符,则产生通用保护故障;
(4)若非一致代码段且DPL<CPL且段存在,则切换到内层堆栈;< DIV>
(5)调整RPL=0;
(6)把描述符装入CS;
(7)若入口偏移越界,则产生通用保护故障;
(8)EFLAGS压入堆栈;
(9)CS压入堆栈;
(10)EIP压入堆栈;
(11)使TF=0,NT=0;
(12)若为中断门,则使IF=0;
(13)若有出错码,则把出错码压入堆栈;
(14)转入处理程序。
由上述转移过程可见,中断门或陷阱门中指示处理程序的选择子必须指向描述一个可执行的代码段的描述符。如果选择子为空,就引起通用保护故障,出错码是0。如果描述符不是代码段描述符,就引起通用保护故障,出错码含选择子。
中断或异常可以转移到同一特权级或内层特权级。上述指定处理程序代码段的描述符中的类型及DPL字段,决定了这种同一任务内的转移是否要发生特权级变换。如果是一个非一致代码段,并且DPL CPL 则产生通用保护异常。
上述转移过程中的第六步,也就是“把描述符装入CS”,是指把上述指定处理程序段的描述符装入CS的高速缓冲寄存器中,在这一步骤中要对描述符进行类似通过调用门进行转移的其它检查,包括是否代码段描述符和代码段描述符是否存在等,因此可能再发生异常。在对该描述符进行检查时,通过调整门中选择子的RPL=0(在处理器内部调整,而不影响存储器中的选择子的RPL字段)的方法,实现只考虑代码段的DPL,而不考虑门中选择子的RPL。把描述符装入CS之后,还要检查门描述符中给出的表示处理程序代码段入口的偏移是否越界,即是否超出段界限。如果越界,就引起出错码为0的通用保护故障。
从转移过程还可以看出,把标志寄存器和断点压入堆栈的做法和顺序与实模式是相同的,但这里每一次堆栈操作是一个双字,CS被扩展成32位。在16位段中亦是如此。
把TF置成0,表示不允许处理程序单步执行。把NT置成0,表示处理程序在利用中断返回指令IRET返回时,返回到同一任务而不是一个嵌套任务。需要注意的是,任何特权级的程序都可改变NT位,这样可以利用中断或陷阱处理程序完成任务切换。
通过中断门的转移和通过陷阱门的转移之间的差别只是对IF标志的处理。对于中断门,在转移过程中把IF置为0,使得在处理程序执行期间屏蔽掉INTR中断(当然,在中断处理程序中可以人为设置IF标志打开中断,以使得在处理程序执行期间允许响应可屏蔽中断);对于陷阱门,在转移过程中保持IF位不变,即如果IF位原来是1,那么通过陷阱门转移到处理程序之后仍允许INTR中断。因此,中断门最适宜于处理中断,而陷阱门适宜于处理异常。
在有出错码的情况下,转入处理程序之前还要把出错码压入堆栈。只有异常处理才可能有出错码。下图给出了通过中断门或陷阱门转移时的堆栈情况。(a)是没有变换特权级和没有出错码的情形;(b)是没有变换特权级有出错码的情形;(c)是变换特权级和没有出错码的内层堆栈的情形。(d)是变换特权级和有出错码的内层堆栈情形。注意图中每一项为双字。
如果中断向量号所只是的门描述符是任务门描述符,那么控制转移到一个作为独立的任务方式出现的处理程序。任务门中的选择子是指向描述对应处理程序任务的TSS段的选择子,即该选择子指示一个可用的286TSS或386TSS。通过任务门的转移与通过任务门到一个可用的386TSS的段间调用指令CALL的转移很相似,主要的区别是,对于提供出错码的异常处理,在完成任务切换之后,把出错码压入新任务的堆栈中(通过任务门进行转移时,返回地址和外层栈指针不压入新任务的堆栈)。
通过任务门的转移,在进入中断或异常处理程序时,标志寄存器EFLAGS中的NT位被置为1,表示是嵌套任务,则IRET指令返回时,沿TSS中的链接字段返回到最后一个被挂起的任务。
在响应中断或处理异常时,使用任务门可提供一个处理程序任务的自动调度。这种任务调度由硬件直接执行,并且越过包含在操作系统中的软件任务切换,这就为处理程序提供了一个快速的任务切换。
对中断的响应和异常的处理,80386允许通过使用中断门或陷阱门实现由当前任务之内的一个过程进行处理;也允许通过使用任务门实现由另一个任务进行处理。在当前任务之内的处理程序较为简单,并可以很快地转移到处理程序,但处理程序要负责保存及恢复处理器的寄存器等内容。转到不同任务的处理程序要花费较长时间,保存及恢复处理器寄存器内容的开销作为任务切换的一部分。使用当前任务内的处理程序的方法,在响应中断或处理异常时,对正执行任务的状态可直接进行访问,但是这就要求每一个任务之内都包含一个处理程序。使用独立任务的处理方法,使处理程序得到较好的隔离,但在响应中断或处理异常时,对原任务状态的访问变得较为复杂。需要注意得是,有些异常必须由中断门或陷阱门进行处理,如上面提到得异常7。
无效TSS异常必须使用任务门进行处理,以保证处理程序有一个有效得任务环境。其它得异常通常在任务环境之内进行处理。在任务内,异常被检测并且不必屏蔽中断,所以,所以使用陷阱门。由陷阱门指示的异常处理程序是一个由所有任务共享的过程,所以该处理程序最好置于全局地址空间之内。如果各个任务要求有不同的处理程序,那么全局异常处理程序可保存一个各处理程序的入口表,并为引起异常的任务调用相应的处理程序。
中断通常与正执行的任务没有关系,并可能从使用任务门提供的隔离中获得好处。要求较快响应的中断,通过中断门可以得到较好的处理。因为中断随时都可能发生,所以,通过中断门访问的中断处理程序,必须置于全局地址空间中,以便对所有的任务都有效。还需强调的是,80386程序绝不能调用一个低特权级的过程,当处理器调用一个中断或异常处理程序时,它实施相同的规则,所以,这样一个过程必须至少具有与在其任务上下文中被调用的、由任务所执行的最高特权级的过程相同的特权级。因此,在使用中断门时,中断处理程序通常必须被安排在特权级0,否则若正在特权级0执行时发生中断,则不能进入中断处理程序,而会引起通用保护故障(中断处理程序使用任务门时除外,因为任务切换可以从任何特权级切换到目标任务的任何特权级)。
中断返回指令IRET用于从中断或异常处理程序的返回。该指令的执行根据任务嵌套标志NT位是否为1分为两种情形。
NT位为1,表示是嵌套任务的返回。当前TSS中的链接字段保存有前一任务的TSS的选择子,取出该选择子进行任务切换就完成了返回。这种情形在由通过任务门转入的中断或异常处理程序返回时出现,因为在由中断门或陷阱门转入处理程序时,NT位已被清0。
NT位为0,表示当前任务内的返回。这种情形在由通过中断门或陷阱门转入的中断或异常处理程序返回时出现。具体进行的操作包括:从堆栈顶弹出返回指针EIP及CS,然后弹出EFLAGS值。弹出的CS选择子的RPL字段,确定返回后的特权级。如果返回选择子的RPL与CPL相同,则不进行特权级改变。若RPL规定了一个外层特权级,则需要特权级改变,从内层堆栈中弹出外层堆栈的指针ESP及SS的值。这些做法与RET指令相似。例如,使用CS选择子的RPL,而不是由选择子标识的段的DPL,是为了返回到不在DPL给定特权级的一致代码段。若弹出的CS的选择子的RPL规定了一个内层特权级,则产生通用保护故障。需要注意的是,对于IRET指令,保存在当前堆栈中的返回地址中的选择子字段必须指向代码段描述符。而不能是系统段或门描述符。否则将引起通用保护故障。
对于提供出错代码的异常处理程序,必须先人为地从堆栈中弹出出错代码,然后再执行IRET指令,及出错代码不会自动被处理器弹出或取消。
中断返回指令IRET不仅能够用于由中断/异常引起的嵌套任务的返回,而且也适用于由段间调用指令CALL通过任务门引起的嵌套任务的返回,如前文所述,在执行通过任务门进行任务切换的段间调用指令CALL时,标志寄存器中的NT位被置为1,表示任务嵌套。而RET指令不能实现此功能。
下面给出一个用于演示中断处理的实例。该实例的逻辑功能是,在屏幕的左上角以倒计时方式显示秒为单位的时间,在时间用完后结束。该实例演示内容包括:外部中断处理程序和陷阱处理程序。
本实例由如下几部分组成:
(1)全局描述符表GDT。GDT中除了含有常见的几个描述符外,还含有描述时钟中断处理程序所使用的代码段和数据段描述符,以及描述显示程序所使用的代码段和数据段描述符。
(2)中断描述符表IDT。为了在保护模式下响应中断和处理异常,必须有IDT。IDT含有256个门描述符。8号安排的是一个通向时钟中断处理程序的中断门,0FEH号安排的是通向显示处理程序的陷阱门,其它均安排成通向其它中断或异常处理程序的陷阱门。
(3)时钟中断处理程序的代码段和数据段。
(4)实现直接写显示缓冲区进行显示的程序代码段和数据段。
(5)处理其它中断或异常的处理程序的代码段。
(6)演示程序的代码段、数据段和堆栈段。
(7)实模式下执行的启动和结束程序代码段和数据段。
源程序清单如下:
;名称:ASM6.ASM
;功能:演示中断处理的实现
;编译:TASM ASM6.ASM
;连接:TLINK ASM6.OBJ
;----------------------------------------------------------------------------
INCLUDE 386SCD.INC
;----------------------------------------------------------------------------
;部分常量定义
;----------------------------------------------------------------------------
EOICOM = 20h ;外部中断处理结束命令
ICREGP = 20h ;中断控制寄存器端口地址
IMREGP = 21h ;中断屏蔽寄存器端口地址
;----------------------------------------------------------------------------
GDTSeg SEGMENT PARA USE16 ;全局描述符表数据段(16位)
;----------------------------------------------------------------------------
;全局描述符表GDT
GDT LABEL BYTE
;空描述符
DUMMY Desc <>
;规范段描述符
Normal Desc <0ffffh,,,ATDW,,>
;视频缓冲区段描述符(DPL=3)
VideoBuf Desc <0ffffh,8000h,0bh,ATDW,,>
;----------------------------------------------------------------------------
EFFGDT LABEL BYTE
;临时代码段描述符
TempCode Desc <0ffffh,TempCodeSeg,,ATCE,,>
;演示代码段描述符
DemoCode Desc <DemoCodeLen-1,DemoCodeSeg,,ATCE,,>
;演示数据段描述符
DemoData Desc <DemoDataLen-1,DemoDataSeg,,ATDW,,>
;演示堆栈段描述符
DemoStack Desc <DemoStackLen-1,DemoStackSeg,,ATDWA,,>
;0feh号中断处理程序(显示程序)代码段描述符
EchoCode Desc <EchoCodeLen-1,EchoCodeSeg,,ATCE,,>
;0feh号中断处理程序(显示程序)数据段描述符
EchoData Desc <EchoDataLen-1,EchoDataSeg,,ATDW,,>
;8号中断处理程序代码段描述符
TICode Desc <TICodeLen-1,TICodeSeg,,ATCE,,>
;8号中断处理程序数据段描述符
TIData Desc <TIDataLen-1,TIDataSeg,,ATDW,,>
;其它中断或异常处理程序代码段描述符
Other Desc <OtherCodeLen-1,OtherCodeSeg,,ATCE,,>
;----------------------------------------------------------------------------
GDTLen = $-GDT ;全局描述符表长度
GDNum = ($-EFFGDT)/(SIZE Desc) ;需特殊处理的描述符数
;----------------------------------------------------------------------------
Normal_Sel = Normal-GDT ;规范段描述符选择子
Video_Sel = VideoBuf-GDT ;视频缓冲区段描述符选择子
;----------------------------------------------------------------------------
TempCode_Sel = TempCode-GDT ;临时代码段的选择子
DemoCode_Sel = DemoCode-GDT ;演示代码段的选择子
DemoData_Sel = DemoData-GDT ;演示数据段的选择子
DemoStack_Sel = DemoStack-GDT ;演示堆栈段的选择子
EchoCode_Sel = EchoCode-GDT ;0feh号中断程序代码段选择子
EchoData_Sel = EchoData-GDT ;0feh号中断程序数据段选择子
TICode_Sel = TICode-GDT ;8号中断程序代码段选择子
TIData_Sel = TIData-GDT ;8号中断程序数据段选择子
Other_Sel = Other-GDT ;其它中断或异常代码段选择子
;----------------------------------------------------------------------------
GDTSeg ENDS ;全局描述符表段定义结束
;----------------------------------------------------------------------------
IDTSeg SEGMENT PARA USE16 ;中断描述符表数据段(16位)
;----------------------------------------------------------------------------
IDT LABEL BYTE ;中断描述符表
;0--7的8个陷阱门描述符
REPT 8
Gate <OtherBegin,Other_Sel,,AT386TGate,>
ENDM
;对应8号(时钟)中断处理程序的门描述符
Gate <TIBegin,TICode_Sel,,AT386IGate,>
;从9--0fdh的245个陷阱门描述符
REPT 245
Gate <OtherBegin,Other_Sel,,AT386TGate,>
ENDM
;对应0feh号中断处理程序的陷阱门描述符
Gate <EchoBegin,EchoCode_Sel,,AT386TGate,>
;对应0ffh号中断处理程序的陷阱门描述符
Gate <OtherBegin,Other_Sel,,AT386TGate,>
;----------------------------------------------------------------------------
IDTLen = $-IDT
;----------------------------------------------------------------------------
IDTSeg ENDS ;中断描述符表段定义结束
;----------------------------------------------------------------------------
;其它中断或异常处理程序的代码段
;----------------------------------------------------------------------------
OtherCodeSeg SEGMENT PARA USE16
ASSUME CS:OtherCodeSeg
;----------------------------------------------------------------------------
OtherBegin PROC FAR
mov ax,Video_Sel
mov es,ax
mov ah,17h ;在屏幕左上角显示兰底白字
mov al,'!' ;符号"!"
mov WORD PTR es:[0],ax
jmp $ ;无限循环
OtherBegin ENDP
;----------------------------------------------------------------------------
OtherCodeLen = $
OtherCodeSeg ENDS
;----------------------------------------------------------------------------
;8号中断处理程序的数据段
;----------------------------------------------------------------------------
TIDataSeg SEGMENT PARA USE16
Count DB 0 ;中断发生的计数器
TIDataLen = $
TIDataSeg ENDS
;----------------------------------------------------------------------------
;8号中断处理程序的代码段
;----------------------------------------------------------------------------
TICodeSeg SEGMENT PARA USE16
ASSUME CS:TICodeSeg,DS:TIDataSeg
;----------------------------------------------------------------------------
TIBegin PROC FAR
push eax ;保护现场
push ds
push fs
push gs
mov ax,TIData_Sel ;置中断处理程序数据段
mov ds,ax
mov ax,EchoData_Sel ;置显示过程数据段
mov fs,ax
mov ax,DemoData_Sel ;置演示程序数据段
mov gs,ax
cmp Count,0
jnz TI2 ;计数非0表示未到1秒
mov Count,18 ;每秒约18次
int 0feh ;调用0FEH号中断处理程序显示
cmp BYTE PTR fs:Mess,'0'
jnz TI1
mov BYTE PTR gs:Flag,1 ;显示符号'0'时置标记
TI1: dec BYTE PTR fs:Mess ;调整显示符号
TI2: dec Count ;调整计数
pop gs ;恢复现场
pop fs
pop ds
mov al,EOICOM ;通知中断控制器中断处理结束
out ICREGP,al
pop eax
iretd ;中断返回
TIBegin ENDP
;----------------------------------------------------------------------------
TICodeLen = $
TICodeSeg ENDS
;----------------------------------------------------------------------------
;0FEH号中断处理程序数据段
;----------------------------------------------------------------------------
EchoDataSeg SEGMENT PARA USE16
Mess DB '8',4eh
EchoDataLen = $
EchoDataSeg ENDS
;----------------------------------------------------------------------------
;0FEH号中断处理程序(显示程序)的代码段
;----------------------------------------------------------------------------
EchoCodeSeg SEGMENT PARA USE16
ASSUME CS:EchoCodeSeg,DS:EchoDataSeg
;----------------------------------------------------------------------------
EchoBegin PROC FAR
push ax ;保护现场
push ds
push es
mov ax,EchoData_Sel ;置显示过程数据段
mov ds,ax
mov ax,Video_Sel ;置视频缓冲区数据段
mov es,ax
mov ax,WORD PTR Mess
mov WORD PTR es:[0],ax
pop es
pop ds
pop ax
iretd
EchoBegin ENDP
;----------------------------------------------------------------------------
EchoCodeLen = $
EchoCodeSeg ENDS
;----------------------------------------------------------------------------
;演示任务的堆栈段
;----------------------------------------------------------------------------
DemoStackSeg SEGMENT PARA USE16
DemoStackLen = 1024
DB DemoStackLen DUP(0)
DemoStackSeg ENDS
;----------------------------------------------------------------------------
;演示任务的数据段
;----------------------------------------------------------------------------
DemoDataSeg SEGMENT PARA USE16
Flag DB 0
DemoDataLen = $
DemoDataSeg ENDS
;----------------------------------------------------------------------------
;演示任务的代码段
;----------------------------------------------------------------------------
DemoCodeSeg SEGMENT PARA USE16
ASSUME CS:DemoCodeSeg,DS:DemoDataSeg
;----------------------------------------------------------------------------
DemoBegin PROC FAR
mov ax,DemoStack_Sel ;置堆栈
mov ss,ax
mov sp,DemoStackLen ;置数据段
mov ax,DemoData_Sel
mov ds,ax
mov es,ax
mov fs,ax
mov gs,ax
mov al,11111110b ;置中断屏蔽字
out IMREGP,al ;只开发时钟中断
sti ;开中断
DemoConti: cmp BYTE PTR Flag,0 ;判标志
jz DemoConti ;直到不为0
cli ;关中断
;转回临时代码段,准备回实方式
JUMP16 TempCode_Sel,<OFFSET ToDos>
DemoBegin ENDP
;----------------------------------------------------------------------------
DemoCodeLen = $
DemoCodeSeg ENDS
;----------------------------------------------------------------------------
TempCodeSeg SEGMENT PARA USE16 ;临时任务的代码段
ASSUME CS:TempCodeSeg
;----------------------------------------------------------------------------
Virtual PROC FAR
JUMP16 DemoCode_Sel,DemoBegin ;转演示任务
ToDos: mov ax,Normal_Sel ;恢复实方式段描述符高速缓存
mov ds,ax
mov es,ax
mov fs,ax
mov gs,ax
mov ss,ax
mov eax,cr0 ;准备返回实模式
and al,11111110b
mov cr0,eax
JUMP16 <SEG Real>,<OFFSET Real>
Virtual ENDP
;----------------------------------------------------------------------------
TempCodeSeg ENDS
;============================================================================
RDataSeg SEGMENT PARA USE16 ;实方式数据段
VGDTR PDesc <GDTLen-1,> ;GDT伪描述符
VIDTR PDesc <IDTLen-1,> ;IDT伪描述符
NORVIDTR PDesc <3ffh,> ;用于保存原IDTR值
SPVar DW ? ;用于保存实方式下的SP
SSVar DW ? ;用于保存实方式下的SS
IMaskRegV DB ? ;用于保存原中断屏蔽寄存器值
RDataSeg ENDS
;----------------------------------------------------------------------------
RCodeSeg SEGMENT PARA USE16 ;实方式代码段
ASSUME CS:RCodeSeg,DS:RDataSeg
;----------------------------------------------------------------------------
Start PROC
mov ax,RDataSeg
mov ds,ax
cld
call InitGDT ;初始化全局描述符表GDT
call InitIDT ;初始化中断描述符表IDT
mov SSVar,ss ;保存堆栈指针
mov SPVar,sp
sidt QWORD PTR NORVIDTR ;保存IDTR
in al,IMREGP
mov BYTE PTR IMaskRegV,al
lgdt QWORD PTR VGDTR ;装载GDTR
cli ;关中断
lidt QWORD PTR VIDTR ;装载IDTR
mov eax,cr0
or al,1
mov cr0,eax
JUMP16 <TempCode_Sel>,<OFFSET Virtual>
Real: mov ax,RDataSeg
mov ds,ax
lss sp,DWORD PTR SPVar ;又回到实方式
lidt QWORD PTR NORVIDTR
mov al,IMaskRegV
out IMREGP,al
sti
mov ax,4c00h
int 21h
Start ENDP
;----------------------------------------------------------------------------
InitGDT PROC
push ds
mov ax,GDTSeg
mov ds,ax
mov cx,GDNum
mov si,OFFSET EFFGDT
InitG: mov ax,[si].BaseL
movzx eax,ax
shl eax,4
shld edx,eax,16
mov WORD PTR [si].BaseL,ax
mov BYTE PTR [si].BaseM,dl
mov BYTE PTR [si].BaseH,dh
add si,SIZE Desc
loop InitG
pop ds
mov bx,16
mov ax,GDTSeg
mul bx
mov WORD PTR VGDTR.Base,ax
mov WORD PTR VGDTR.Base+2,dx
ret
InitGDT ENDP
;----------------------------------------------------------------------------
InitIDT PROC
mov bx,16
mov ax,IDTSeg
mul bx
mov WORD PTR VIDTR.Base,ax
mov WORD PTR VIDTR.Base+2,dx
ret
InitIDT ENDP
;----------------------------------------------------------------------------
RCodeSeg ENDS
END Start
为了即简单又清楚地演示在保护模式下响应外部中断并进行处理,实例使用了时钟中断源,但没有通过重新设置中断控制器的方法改变对应的中断向量。所以,时钟中断使用的8H号中断向量号就与双重故障异常使用的中断向量号发生冲突。但实例仅是演示程序,所以只要保证不发生双重故障异常,就可避免冲突,从而不会影响演示。
设置中断屏蔽寄存器,仅开放时钟中断。所以,在开中断状态下,也只可能发生时钟中断,而不会发生其它外部中断。
由于通过中断门转时钟中断处理程序,所以在控制转移时不发生任务切换。但外部中断随时可能发生,因此中断处理程序必须采取保护现场等措施。作为演示程序,该中断处理程序检查和调整在其数据段中的计数器,满18次后,就认为已满一秒,再调整用于显示的倒计数信息。如果倒计数信息为0,那么就设置演示程序数据段中的时间为0标志。该中断处理程序通过约定的数据区与显示程序及演示程序交换信息。
为了演示陷阱及其处理,把显示过程安排成陷阱处理程序。上述时钟中断处理程序通过软中断调用指令INT调用该显示程序,以显示倒计数。在控制转移时,也没有任务切换。该陷阱处理程序相当于一个“软中断”处理程序,类似实模式下的BIOS中断INT 10H。
为了简单,除了8H号和0FEH号外,IDT中其它的门均通向一个处理程序。该处理程序用于处理其它中断或异常。处理过程也极其简单,在屏幕左上角显示蓝底白字的符号“!”,然后进入无限循环。实际上,按演示程序现在的安排,不可能发生这种情况。
为了简单,实例涉及的中断处理程序和异常处理程序都保持特权级0。所以,控制转移时不发生特权级变换。因此,没有使用其它堆栈。
由于IDT中门描述符没有32位段基地址,并且入口点偏移较小,所以就直接填写门描述符结构变量,没有额外再初始化。过程InitIDT只是设置IDT伪描述符。
再使IDT发挥作用之前,还要装载中断描述符表寄存器IDTR;但为了回到实模式后,恢复原来的IDTR之内容,所以先保存IDTR的内容。实例使用如下指令保存IDTR:
sidt QWORD PTR NORVIDTR
该指令的功能是把IDTR的内容保存到存储器中的伪描述符NORVIDTR中。该伪描述符的结构如前文所述的结构类型PDESC所示,低字是以字节为单位的界限,高双字是基地址。在后面的文章中将对SIDT指令作详细说明。
本实例使用如下指令装载IDTR寄存器:
lidt QWORD PTR VIDTR
lidt QWORD PTR NORVIDTR
LIDT指令类似于LGDT指令,在后面的文章中将对LIDT指令作详细说明。
下面给出一个用于模拟异常和演示异常处理的实例。该实例的逻辑功能是,在屏幕上显示一条提示用户以按键方式选择异常类型的字符,然后模拟指定的异常。该实例演示内容包括:除法出错故障处理、溢出陷阱处理、段不存在故障处理、堆栈段出错处理和通用保护故障处理;还有作为一个独立任务方式出现的陷阱处理程序。
为了演示以独立任务方式出现的陷阱处理程序,实例含有两个任务:演示任务和读键盘任务。实例由如下几部分组成:
(1)全局描述符表GDT和中断描述符表IDT;
(2)读键盘任务局部描述符表、任务状态段、堆栈段和代码段等;
(3)演示任务的局部描述符表、任务状态段、堆栈段、代码段和数据段等;
(4)作为演示任务一部分的有关陷阱处理和故障处理程序的代码段;
(5)作为演示任务一部分的显示出错码过程的代码段;
(6)实模式下执行的启动和结束程序代码段和数据段。
在切换到保护模式后,就进入临时代码段。为了简单,演示任务不发生特权级变换。演示步骤如下:
(1)从临时代码段转移到演示代码段。
(2)做演示准备。把演示任务的LDT选择子装入LDTR,并填入TSS,装载任务寄存器TR,建立演示任务堆栈,设置其它数据段寄存器。
(3)接收要模拟的异常类型号。通过软中断指令INT调用读键盘任务完成该步骤。读键盘任务只有在接收到指定的字符后才结束。接收的字符是0、4、B、C和D。
按接收的字符模拟异常。即根据键入的字符,执行有关程序片段。在这些片段中,有意安排了能引起有关故障或陷阱的指令。
结束演示,转临时代码段,返回DOS。
程序清单如下:
;名称:ASM7.ASM
;功能:模拟异常和演示异常处理
;编译:TASM ASM7.ASM
;连接:TLINK ASM7.OBJ
;----------------------------------------------------------------------------
INCLUDE 386SCD.INC
;----------------------------------------------------------------------------
GDTSeg SEGMENT PARA USE16 ;全局描述符表数据段(16位)
;----------------------------------------------------------------------------
;全局描述符表GDT
GDT LABEL BYTE
;空描述符
DUMMY Desc <>
;规范段描述符及选择子
Normal Desc <0ffffh,,,ATDW,,>
Normal_Sel = Normal-GDT
;视频缓冲区段描述符(DPL=3)及选择子
VideoBuf Desc <0ffffh,8000h,0bh,ATDW,,>
VideoBuf_Sel = VideoBuf-GDT
;----------------------------------------------------------------------------
EFFGDT LABEL BYTE
;临时代码段描述符及选择子
TempCode Desc <0ffffh,TempCodeSeg,,ATCE,,>
TempCode_Sel = TempCode-GDT
;演示代码段描述符及选择子
DemoCode Desc <DemoCodeLen-1,DemoCodeSeg,,ATCE,,>
DemoCode_Sel = DemoCode-GDT
;演示任务局部描述符表段描述符及选择子
DemoLDT Desc <DemoLDTLen-1,DemoLDTSeg,,ATLDT,,>
DemoLDT_Sel = DemoLDT-GDT
;演示任务TSS段描述符及选择子
DemoTSS Desc <DemoTSSLen-1,DemoTSSSeg,,AT386TSS,,>
DemoTSS_Sel = DemoTSS-GDT
;缓冲数据段描述符及选择子
XBuffer Desc <BufferLen-1,BufferSeg,,ATDW,,>
XBuffer_Sel = XBuffer-GDT
;读键盘任务局部描述符表段描述符及选择子
GKeyLDT Desc <GKeyLDTLen-1,GKeyLDTSeg,,ATLDT,,>
GKeyLDT_Sel = GKeyLDT-GDT
;读键盘任务TSS段描述符及选择子
GKeyTSS Desc <GKeyTSSLen-1,GKeyTSSSeg,,AT386TSS,,>
GKeyTSS_Sel = GKeyTSS-GDT
;显示陷阱处理程序代码段描述符及选择子
EchoCode Desc <EchoCodeLen-1,EchoCodeSeg,,ATCE,,>
EchoCode_Sel = EchoCode-GDT
;显示出错码过程代码段描述符及选择子
SubCode Desc <SubCodeLen-1,SubCodeSeg,,ATCE,,>
SubCode_Sel = SubCode-GDT
;其它中断或异常处理程序代码段描述符及选择子
Other Desc <OtherCodeLen-1,OtherCodeSeg,,ATCE,,>
Other_Sel = Other-GDT
;----------------------------------------------------------------------------
GDTLen = $-GDT ;全局描述符表长度
GDNum = ($-EFFGDT)/(SIZE Desc) ;需处理基地址的描述符个数
;----------------------------------------------------------------------------
GDTSeg ENDS ;全局描述符表段定义结束
;----------------------------------------------------------------------------
IDTSeg SEGMENT PARA USE16 ;中断描述符表数据段(16位)
;----------------------------------------------------------------------------
IDT LABEL BYTE ;中断描述符表
;0号陷阱门描述符(对应除法出错故障)
Gate <DivBegin,Divide_Sel,,AT386TGate,>
;从1--3的3个陷阱门描述符
REPT 3
Gate <OtherBegin,Other_Sel,,AT386TGate,>
ENDM
;4号陷阱门描述符(对应溢出陷阱)
Gate <OFBegin,OF_Sel,,AT386TGate,>
;从5--0ah的的6个陷阱门描述符
REPT 6
Gate <OtherBegin,Other_Sel,,AT386TGate,>
ENDM
;0bh号陷阱门描述符(对应段不存在故障)
Gate <SNPBegin,SNP_Sel,,AT386TGate,>
;0ch号陷阱门描述符(对应堆栈段故障)
Gate <SSEBegin,SSE_Sel,,AT386TGate,>
;0dh号陷阱门描述符(对应通用保护故障)
Gate <GPBegin,GP_Sel,,AT386TGate,>
;从0eh--0edh的240个陷阱门描述符
REPT 240
Gate <OtherBegin,Other_Sel,,AT386TGate,>
ENDM
;对应0feh号陷阱门描述符(对应显示中断处理程序)
Gate <EchoBegin,EchoCode_Sel,,AT386TGate,>
;0ffh号任务门描述符(对应读键盘中断处理任务)
Gate <,GKeyTSS_Sel,,ATTaskGate,>
;----------------------------------------------------------------------------
IDTLen = $-IDT
;----------------------------------------------------------------------------
IDTSeg ENDS ;中断描述符表段定义结束
;----------------------------------------------------------------------------
;读键盘任务局部描述符表段
;----------------------------------------------------------------------------
GKeyLDTSeg SEGMENT PARA USE16
;----------------------------------------------------------------------------
GLDT LABEL BYTE
;代码段描述符及选择子
GKeyCode Desc <0ffffh,GKeyCodeSeg,,ATCE,,>
GKeyCode_Sel = GKeyCode-GLDT+TIL
;堆栈段描述符及选择子
GKeyStack Desc <GKeyStackLen-1,GKeyStackSeg,,ATDWA,,>
GKeyStack_Sel = GKeyStack-GLDT+TIL
;----------------------------------------------------------------------------
GKeyLDNum = ($-GLDT)/(SIZE Desc) ;需初始化基地址的描述符个数
GKeyLDTLen = $ ;局部描述符表段长度
;----------------------------------------------------------------------------
GKeyLDTSeg ENDS
;----------------------------------------------------------------------------
;读键盘任务TSS段
;----------------------------------------------------------------------------
GKeyTSSSeg SEGMENT PARA USE16
DD 0 ;链接字
DD ? ;0级堆栈指针
DW ?,?
DD ? ;1级堆栈指针
DW ?,?
DD ? ;2级堆栈指针
DW ?,?
DD 0 ;CR3
DW GKeyBegin,0 ;EIP
DD 0 ;EFLAGS
DD 0 ;EAX
DD 0 ;ECX
DD 0 ;EDX
DD 0 ;EBX
DW GKeyStackLen,0 ;ESP
DD 0 ;EBP
DD 0 ;ESI
DD 0 ;EDI
DW Normal_Sel,0 ;ES
DW GKeyCode_Sel,0 ;CS
DW GKeyStack_Sel,0 ;SS
DW Normal_Sel,0 ;DS
DW Normal_Sel,0 ;FS
DW Normal_Sel,0 ;GS
DW GKeyLDT_Sel,0 ;LDTR
DW 0 ;调试陷阱标志
DW $+2 ;指向I/O许可位图的偏移
DB 0ffh ;I/O许可位图结束字节
GKeyTSSLen = $
GKeyTSSSeg ENDS
;----------------------------------------------------------------------------
;读键盘任务堆栈段
;----------------------------------------------------------------------------
GKeyStackSeg SEGMENT PARA USE16
GKeyStackLen = 1024
DB GKeyStackLen DUP(0)
GKeyStackSeg ENDS
;----------------------------------------------------------------------------
;读键盘任务代码段
;----------------------------------------------------------------------------
GKeyCodeSeg SEGMENT PARA USE16
ASSUME CS:GKeyCodeSeg,DS:RDataSeg,ES:BufferSeg
;----------------------------------------------------------------------------
GKeyBegin PROC FAR
push ds
push es
push fs
push gs
mov ax,Normal_Sel
mov ss,ax ;准备转实方式
mov eax,cr0
and al,11111110b
mov cr0,eax ;转实方式
JUMP16 <SEG GetKey>,<OFFSET GetKey>
GetKey: mov ax,RDataSeg ;实方式
mov ds,ax
mov ebp,esp ;恢复实方式部分现场
lss sp,DWORD PTR SPVar
lidt QWORD PTR NORVIDTR
sti
mov dx,OFFSET Mess
mov ah,9
int 21h ;显示提示信息
GetKey1: mov ah,0
int 16h ;读键盘
cmp al,'0'
jz GetKey2
cmp al,'4'
jz GetKey2
and al,11011111b ;小写转大写
cmp al,'B'
jb GetKey1
cmp al,'D'
ja GetKey1 ;只有[0,4,b,c,d]有效
GetKey2: mov dl,al
mov ah,2
int 21h ;显示所按字符
mov ax,BufferSeg
mov es,ax
mov BYTE PTR es:KeyASCII,dl ;保存到缓冲数据段
cli ;准备返回保护方式
lidt QWORD PTR VIDTR
mov eax,cr0
or al,1
mov cr0,eax
JUMP16 <GKeyCode_Sel>,<OFFSET GetKeyV>
GetKeyV: mov ax,GKeyStack_Sel ;又进入保护方式
mov ss,ax
mov esp,ebp
pop gs
pop fs
pop es
pop ds
iretd
jmp GKeyBegin
GKeyBegin ENDP
;----------------------------------------------------------------------------
GKeyCodeLen = $
GKeyCodeSeg ENDS
;----------------------------------------------------------------------------
;其它中断或异常处理程序的代码段
;----------------------------------------------------------------------------
OtherCodeSeg SEGMENT PARA USE16
ASSUME CS:OtherCodeSeg
;----------------------------------------------------------------------------
OtherBegin PROC FAR
mov si,OFFSET MessOther
int 0feh ;显示提示信息
mov WORD PTR es:[0],ax
jmp $ ;进入无限循环
OtherBegin ENDP
;----------------------------------------------------------------------------
OtherCodeLen = $
OtherCodeSeg ENDS
;----------------------------------------------------------------------------
;除法出错故障处理程序代码段
;----------------------------------------------------------------------------
DivCodeSeg SEGMENT PARA USE16
ASSUME CS:DivCodeSeg
;----------------------------------------------------------------------------
DivBegin PROC FAR
mov si,OFFSET Mess0
mov di,0
int 0feh ;显示提示信息
shr ax,1 ;处理模拟的除法错误
iretd ;返回
DivBegin ENDP
;----------------------------------------------------------------------------
DivCodeLen = $
DivCodeSeg ENDS
;----------------------------------------------------------------------------
;溢出陷阱处理程序代码段
;----------------------------------------------------------------------------
OFCodeSeg SEGMENT PARA USE16
ASSUME CS:OFCodeSeg
;----------------------------------------------------------------------------
OFBegin PROC FAR
mov si,OFFSET Mess4
mov di,0
int 0feh ;显示提示信息
iretd ;返回
OFBegin ENDP
;----------------------------------------------------------------------------
OFCodeLen = $
OFCodeSeg ENDS
;----------------------------------------------------------------------------
;段不存在故障处理程序代码段
;----------------------------------------------------------------------------
SNPCodeSeg SEGMENT PARA USE16
ASSUME CS:SNPCodeSeg
;----------------------------------------------------------------------------
SNPBegin PROC FAR
mov si,OFFSET MessB
mov di,0
int 0feh ;显示提示信息
pop eax ;弹出出错代码
CALL16 SubCode_Sel,SubBegin ;显示出错代码
pop eax
add eax,2 ;按模拟的引起段不存在指令
push eax ;调整返回地址
iretd
SNPBegin ENDP
;----------------------------------------------------------------------------
SNPCodeLen = $
SNPCodeSeg ENDS
;----------------------------------------------------------------------------
;堆栈段故障处理程序代码段
;----------------------------------------------------------------------------
SSECodeSeg SEGMENT PARA USE16
ASSUME CS:SSECodeSeg
;----------------------------------------------------------------------------
SSEBegin PROC FAR
mov si,OFFSET MessC
mov di,0
int 0feh ;显示提示信息
pop eax ;弹出出错代码
CALL16 SubCode_Sel,SubBegin ;显示出错代码
pop eax
add eax,4 ;按模拟的引起堆栈段错误的
push eax ;指令调整返回地址
iretd
SSEBegin ENDP
;----------------------------------------------------------------------------
SSECodeLen = $
SSECodeSeg ENDS
;----------------------------------------------------------------------------
;通用保护故障处理程序代码段
;----------------------------------------------------------------------------
GPCodeSeg SEGMENT PARA USE16
ASSUME CS:GPCodeSeg
;----------------------------------------------------------------------------
GPBegin PROC FAR
push ebp
mov ebp,esp
push eax
push esi
push edi ;保护现场
mov si,OFFSET MessD
mov di,0
int 0feh ;显示提示信息
mov eax,[bp+4] ;从堆栈中取出出错代码
CALL16 SubCode_Sel,SubBegin ;显示出错代码
pop edi
pop esi
pop eax ;恢复部分现场
add DWORD PTR [ebp+8],2 ;按模拟的故障指令调整返回
pop ebp ;地址
add esp,4 ;废除堆栈中的出错代码
iretd
GPBegin ENDP
;----------------------------------------------------------------------------
;显示出错码过程代码段
;----------------------------------------------------------------------------
SubCodeSeg SEGMENT PARA USE16
ASSUME CS:SubCodeSeG
;----------------------------------------------------------------------------
SubBegin PROC ;AX中含出错代码
push ax ;保护现场
push cx
push dx
push si
push di
mov si,OFFSET ErrCode
mov dx,ax
mov cx,4
SubR1: rol dx,4 ;把16位出错代码转换成4位
mov al,dl ;十六进制数的ASCII码并保存
and al,0fh
add al,30h
cmp al,'9'
jbe SubR2
add al,7
SubR2: mov [si],al
inc si
loop SubR1
mov si,OFFSET ErrMess
Mov di,80*2 ;从第二行行首开始
int 0feh ;显示出错码
pop di ;恢复现场
pop si
pop dx
pop cx
pop ax
retf ;返回
SubBegin ENDP
;----------------------------------------------------------------------------
SubCodeLen = $
SubCodeSeg ENDS
;----------------------------------------------------------------------------
GPCodeLen = $
GPCodeSeg ENDS
;----------------------------------------------------------------------------
;实现显示的陷阱处理程序代码段
;入口参数--DS:SI指向显示信息串,ES:DI指向显示缓冲区
;----------------------------------------------------------------------------
EchoCodeSeg SEGMENT PARA USE16
ASSUME CS:EchoCodeSeg
;----------------------------------------------------------------------------
EchoBegin PROC FAR
pushad ;保护现场
cld
mov ah,7
mov al,20h
mov cx,80
push di
rep stosw ;清所在显示行
pop di
Echo1: lodsb
or al,al
jz Echo2
stosw ;显示指定信息串
jmp Echo1
Echo2: popad ;恢复现场
iretd
EchoBegin ENDP
;----------------------------------------------------------------------------
EchoCodeLen = $
EchoCodeSeg ENDS
;----------------------------------------------------------------------------
;缓冲区数据段
;----------------------------------------------------------------------------
BufferSeg SEGMENT PARA USE16
KeyASCII DB ?
Buffer DB 128 DUP(?)
BufferLen = $
BufferSeg ENDS
;----------------------------------------------------------------------------
;演示任务局部描述符表段
;----------------------------------------------------------------------------
DemoLDTSeg SEGMENT PARA USE16
;----------------------------------------------------------------------------
DLDT LABEL BYTE
;演示任务TSS段作为数据段的描述符及选择子
ToDemoTSS Desc <DemoTSSLen-1,DemoTSSSeg,,ATDW,,>
ToDemoTSS_Sel = ToDemoTSS-DLDT+TIL
;演示任务堆栈段描述符及选择子
DemoStack Desc <DemoStackLen-1,DemoStackSeg,,ATDWA,,>
DemoStack_Sel = DemoStack-DLDT+TIL
;演示任务数据段描述符及选择子
DemoData Desc <DemoDataLen-1,DemoDataSeg,,ATDW,,>
DemoData_Sel = DemoData-DLDT+TIL
;除法出错故障处理程序代码段描述符及选择子
Divide Desc <DivCodeLen-1,DivCodeSeg,,ATCE,,>
Divide_Sel = Divide-DLDT+TIL
;溢出陷阱处理程序代码段描述符及选择子
OverFlow Desc <OFCodeLen-1,OFCodeSeg,,ATCE,,>
OF_Sel = OverFlow-DLDT+TIL
;段不存在故障处理程序代码段描述符及选择子
SNPCode Desc <SNPCodeLen-1,SNPCodeSeg,,ATCE,,>
SNP_Sel = SNPCode-DLDT+TIL
;堆栈段出错故障处理程序代码段描述符及选择子
SSECode Desc <SSECodeLen-1,SSECodeSeg,,ATCE,,>
SSE_Sel = SSECode-DLDT+TIL
;通用保护故障处理程序代码段描述符及选择子
GPCode Desc <GPCodeLen-1,GPCodeSeg,,ATCE,,>
GP_Sel = GPCode-DLDT+TIL
;为模拟段不存在故障而安排的数据段描述符及选择子
TestNPS Desc <0ffffh,,,ATDW-80h,,>
TestNPS_Sel = TestNPS-DLDT+TIL
;----------------------------------------------------------------------------
DemoLDNum = ($-DLDT)/(SIZE Desc) ;LDT描述符个数
DemoLDTLen =$
;----------------------------------------------------------------------------
DemoLDTSeg ENDS
;----------------------------------------------------------------------------
;演示任务TSS段
;----------------------------------------------------------------------------
DemoTSSSeg SEGMENT PARA USE16
DemoTaskSS TSS <>
DB 0ffh
DemoTSSLen = $
DemoTSSSeg ENDS
;----------------------------------------------------------------------------
;演示任务的堆栈段
;----------------------------------------------------------------------------
DemoStackSeg SEGMENT PARA USE16
DemoStackLen = 1024
DB DemoStackLen DUP(0)
DemoStackSeg ENDS
;----------------------------------------------------------------------------
;演示任务的数据段
;----------------------------------------------------------------------------
DemoDataSeg SEGMENT PARA USE16
Mess0 DB 'Divide Error (Exception 0)',0
Mess4 DB 'Overflow (Exception 4)',0
MessB DB 'Segment Not Present (Exception 11)',0
MessC DB 'Stack Segment (Exception 12)',0
MessD DB 'General Protection (Exception 13)',0
MessOther DB 'Other Exception',0
ErrMess DB 'Error Code = '
ErrCode DB 4 DUP(0),'H',0
DemoDataLen = $
DemoDataSeg ENDS
;----------------------------------------------------------------------------
;演示任务的代码段
;----------------------------------------------------------------------------
DemoCodeSeg SEGMENT PARA USE16
ASSUME CS:DemoCodeSeg,DS:DemoDataSeg
;----------------------------------------------------------------------------
DemoBegin PROC FAR
mov ax,DemoLDT_Sel
lldt ax ;装载LDTR
mov ax,DemoStack_Sel ;置堆栈
mov ss,ax
mov esp,DemoStackLen
mov ax,ToDemoTSS_Sel
mov gs,ax ;把演示任务LDT选择子填入TSS
mov WORD PTR gs:DemoTaskSS.TRLDTR,DemoLDT_Sel
mov ax,DemoTSS_Sel
ltr ax ;装载TR
mov ax,DemoData_Sel
mov ds,ax ;装载其它数据段寄存器
mov ax,VideoBuf_Sel
mov es,ax
mov ax,XBuffer_Sel
mov fs,ax
mov ax,XBuffer_Sel
mov gs,ax
int 0ffh ;接收要模拟的异常类型号
mov al,BYTE PTR fs:KeyASCII ;按接收的字符模拟异常号
cmp al,'0'
jnz Demo4
mov ax,2000
mov cl,2 ;模拟除法出错故障
div cl ;该指令长2字节
jmp Over
Demo4: cmp al,'4'
jnz Demo11
mov al,100
add al,50
into ;模拟溢出陷阱
JMP OVER
Demo11: cmp al,'B'
jnz Demo12
mov ax,TestNPS_Sel ;模拟段不存在故障
mov gs,ax ;该指令长2字节
JMP Over
Demo12: cmp al,'C'
jnz Demo13
mov ebp,esp ;模拟堆栈出错故障
mov al,[ebp] ;该指令长4字节
jmp Over
Demo13: mov ax,DemoTSS_Sel ;模拟通用保护故障
mov gs,ax ;该指令长2字节
Over: ;转临时代码段
JUMP16 TempCode_Sel,<OFFSET ToDos>
DemoBegin ENDP
;----------------------------------------------------------------------------
DemoCodeLen = $
DemoCodeSeg ENDS
;----------------------------------------------------------------------------
TempCodeSeg SEGMENT PARA USE16 ;临时任务的代码段
ASSUME CS:TempCodeSeg
;----------------------------------------------------------------------------
Virtual PROC FAR
JUMP16 DemoCode_Sel,DemoBegin ;转演示任务
ToDos: mov ax,Normal_Sel ;恢复实方式段描述符高速缓存
mov ds,ax
mov es,ax
mov fs,ax
mov gs,ax
mov ss,ax
mov eax,cr0 ;准备返回实模式
and al,11111110b
mov cr0,eax
JUMP16 <SEG Real>,<OFFSET Real>
Virtual ENDP
;----------------------------------------------------------------------------
TempCodeSeg ENDS
;============================================================================
RDataSeg SEGMENT PARA USE16 ;实方式数据段
VGDTR PDesc <GDTLen-1,> ;GDT伪描述符
VIDTR PDesc <IDTLen-1,> ;IDT伪描述符
NORVIDTR PDesc <3ffh,> ;用于保存原IDTR值
SPVar DW ? ;用于保存实方式下的SP
SSVar DW ? ;用于保存实方式下的SS
Mess DB 'Press a key[0,4,B,C,D]:$';提示信息
RDataSeg ENDS
;----------------------------------------------------------------------------
RCodeSeg SEGMENT PARA USE16 ;实方式代码段
ASSUME CS:RCodeSeg,DS:RDataSeg
;----------------------------------------------------------------------------
Start PROC
mov ax,RDataSeg
mov ds,ax
cld
call InitGDT ;初始化全局描述符表GDT
call InitIDT ;初始化中断描述符表IDT
mov ax,GKeyLDTSeg
mov fs,ax
mov cx,GKeyLDNum
mov si,OFFSET GLDT
CALL InitLDT
mov ax,DemoLDTSeg
mov fs,ax
mov cx,DemoLDNum
mov si,OFFSET DLDT
CALL InitLDT
mov SSVar,ss ;保存堆栈指针
mov SPVar,sp
lgdt QWORD PTR VGDTR ;装载GDTR
sidt QWORD PTR NORVIDTR ;保存IDTR
cli ;关中断
lidt QWORD PTR VIDTR ;装载IDTR
mov eax,cr0
or al,1
mov cr0,eax
JUMP16 <TempCode_Sel>,<OFFSET Virtual>
Real: mov ax,RDataSeg
mov ds,ax
lss sp,DWORD PTR SPVar ;又回到实方式
lidt QWORD PTR NORVIDTR
sti
mov ax,4c00h
int 21h
Start ENDP
;----------------------------------------------------------------------------
InitGDT PROC
push ds
mov ax,GDTSeg
mov ds,ax
mov cx,GDNum
mov si,OFFSET EFFGDT
InitG: mov ax,[si].BaseL
movzx eax,ax
shl eax,4
shld edx,eax,16
mov WORD PTR [si].BaseL,ax
mov BYTE PTR [si].BaseM,dl
mov BYTE PTR [si].BaseH,dh
add si,SIZE Desc
loop InitG
pop ds
mov bx,16
mov ax,GDTSeg
mul bx
mov WORD PTR VGDTR.Base,ax
mov WORD PTR VGDTR.Base+2,dx
ret
InitGDT ENDP
;----------------------------------------------------------------------------
InitIDT PROC
mov bx,16
mov ax,IDTSeg
mul bx
mov WORD PTR VIDTR.Base,ax
mov WORD PTR VIDTR.Base+2,dx
ret
InitIDT ENDP
;----------------------------------------------------------------------------
;入口参数:FS:SI=第一个要初始化的描述符,CX=要初始化的描述符数
;----------------------------------------------------------------------------
InitLDT PROC
ILDT: mov ax,WORD PTR FS:[si].BaseL
movzx eax,ax
shl eax,4
shld edx,eax,16
mov WORD PTR fs:[si].BaseL,ax
mov BYTE PTR fs:[si].BaseM,dl
mov BYTE PTR fs:[si].BaseH,dh
add si,SIZE Desc
loop ILDT
ret
InitLDT ENDP
;----------------------------------------------------------------------------
RCodeSeg ENDS
END Start
上述模拟与演示程序的许多内容与实例六相同,下面就各异常处理程序和读键盘任务的实现作些说明:
从源程序可见,除法出错是在执行故意安排的被除数为2000,而除数为2的无符号除指令时引起的。作为演示,除法出错故障处理程序先显示一条提示信息,然后把存放被除数AX的内容右移一位,然后就返回。由于除法出错为故障类异常,所以在故障处理结束后,仍执行该无符号除指令。显然将再次引起同样的故障,仍把被除数右移一位。由于每次处理时都把被除数减半,所以几次故障后就不发生该故障了。
作为演示的溢出陷阱处理程序较简单。先显示一条提示信息,然后就返回。因为溢出异常为陷阱类异常,所以在陷阱处理结束后,就直接返回到引起陷阱指令的下一条指令。
从源程序可见,段不存在故障是在执行故意安排的把一个选择子送段寄存器GS的指令时引起的。该选择子索引的描述符中的存在位P被置为0,表示对应段不在内存。在正常情况下,段不存在故障处理程序要把对应的段装入内存,再把描述符内的P位修改为1,于是,在故障处理结束后,引起故障的指令可得到顺序执行。为了简单,这里安排的故障处理程序先显示一条提示信息,然后显示出错码,最后调整堆栈中的返回地址并返回。段不存在故障提供一个出错码,该故障处理程序利用POP指令把它用堆栈中弹出,这样堆栈指针就指向返回地址。由于段不存在异常属于故障类异常,所以返回点仍是引起故障的指令。因此,演示程序调整了堆栈中的返回地址,使其返回到引起故障的指令的下一条指令。
引起堆栈出错故障的原因有多种,实例通过执行故意安排的偏移超过段界限的堆栈段访问指令来模拟堆栈段出错故障的产生。作为演示的堆栈出错故障处理程序比较简单,先显示一条信息,然后显示出错码,最后调整堆栈中的返回地址并返回。
引起通用保护故障处理程序的原因有多种,实例通过把一个指向系统段描述符的选择子装入数据段寄存器GS来模拟通用保护故障的产生。作为演示的通用保护故障处理程序,象上述两个故障处理程序一样比较简单,先显示一条提示信息,然后显示出错码,最后调整堆栈中的返回地址并返回,但在废除堆栈中的出错码和调整堆栈中的返回地址时采用了其它方法。
在实例中,通向上述各种异常处理程序的门都是陷阱门。所以,在发生异常而转入这些异常处理程序时,都不发生任务切换。于是,这些异常处理仍作为演示任务的一部分。
正常情况下,异常处理程序应该注意现场的保护和恢复,但为了简单,作为演示的异常处理程序没有能够切实地保护现场。注意,这些异常处理程序所采用的处理方法与所模拟的指令有关,不适用于一般情况。
实例采用一个过程用于显示出错代码,该过程的入口参数是AX含出错码。利用该过程不仅缩短程序,而且也用于表现异常处理程序的实现。
在实例的IDT中,0FFH号门描述符是任务门,指向一个独立的任务。该任务的功能是读键盘,接收一个指定范围内的字符。演示任务通过指令“INT 0FFH”来调用它,接收一个代表需要模拟异常的字符。
为了简单,该任务在实模式下读键盘,接收指定范围内的字符。为此,该任务每次经历如下步骤:(1)转到实模式。此前要作必要的准备,转到实模式后,要恢复必须的实模式下的部分现场。(2)接收指定的字符。调用DOS功能显示提示信息,调用BIOS中断读键盘,如果在指定范围内,那么就显示,并保存在约定的数据段中。(3)转回到保护模式,此前也要作必要的准备。
尽管在任务切换时,自动利用TSS保护和恢复现场,但由于该任务相当于一个读键盘的过程,所以在开始任务时,还通过堆栈保护必要的现场,在结束任务时恢复现场。请特别注意,安排在该任务代码段中的IRETD指令之后的转移指令的作用。当执行IRETD指令时,由于NT位为1,所以按反向链进行任务切换,同时保存各寄存器到当前的TSS,为了下次进入时仍能从头开始执行此任务,所以在IRETD后加一条转移到该任务开头的指令。
如上所述,中断/异常可引起任务切换、任务内特权级变换和任务内无特权级变换的转移。至此,任务切换、任务内特权级变换和任务内无特权级变换转移的各种途径已全部列出。
任务之间切换的途径如下图所示。段间转移指令JMP、段间调用指令CALL、软中断指令INT和中断返回指令IRET引起的任务切换是主动的任务切换,或者说是当前任务要求的任务切换。中断和异常(不包括软中断指令)引起的任务切换是被动的任务切换,或者说是不受当前任务左右的任务切换。
伴随着任务切换,特权级当然可能发生变换。只要任务切换发生,这种特权级的变换取决于目标任务,而与当前任务无关。
任务内特权级变换的途径如下图所示。图中特权级m是外层特权级,特权级n是内层特权级。通常RET与CALL对应;IRET与INT、中断/异常对应。但也可以通过在堆栈中建立合适环境的手段,使RET或IRET从内层特权级变换到外层特权级。
任务内相同特权级转移的途径如下图所示。由图可见,任务内相同特权级转移的途径多种多样。