保护模式教程11

 

十一.虚拟8086模式

继推出80386之后,Intel又推出了80386PentiumPentium PRO。这些处理器都具有实模式和保护模式两种工作方式。前面已介绍过,实模式与8086兼容,可以运行DOS及以其为平台的几乎所有软件;但在实模式下,处理器不能发挥自身的优越性能,不能支持多用户、多任务操作系统的运行。为了充分发挥处理器的功能,同时使DOS及以其为平台的软件继续有效地运行,从80386开始增加了虚拟8086模式。本文将介绍虚拟8086模式。这里下载本文源代码

<>V86模式

1.V86模式

虚拟8086模式是保护模式下的一种工作方式,也称为V8086模式,或者简称为V86模式。在虚拟8086模式下,处理器类似于8086。寻址的地址空间是1M字节;段寄存器的内容作为段值解释;20位存储单元地址由段值乘以16加偏移构成。在V86模式下,代码段总是可写的,这与实模式相同,同理,数据段也是可执行的,只不过可能会发生异常。所以,在虚拟8086模式下,可以运行DOS及以其为平台的软件。但V86模式毕竟是虚拟8086的一种方式,所以不完全等同于8086

当标志寄存器中的VM位为1时,处理器就处于V86模式。此时,其当前特权级由处理器自动设置为3

2.V86任务

8086程序可以直接在V86模式下运行,而V86模式受到称为V86监控程序的控制。V86监控程序和在V86模式下的8086程序构成的任务称为虚拟8086任务,或者简称为V86任务。V86任务形成一个由处理器硬件和属于系统软件的监控程序组成的虚拟8086V86监控程序控制V86外部界面、中断和I/O。硬件提供该任务最低端1M字节线性地址空间的虚拟存储空间,包含虚拟寄存器的TSS,并执行处理这些寄存器和地址空间的指令。

80386V86任务作为与其它任务具有同等地位的一个任务。它可以支持多个V86任务,每个V86任务是相对独立的。所以,通过V86模式这种形式,运行8086程序可充分发挥处理器的能力和充分利用系统资源。

<>进入和离开V86模式

保护模式和V86模式之间的切换情形如下图所示。图中左面部分为V86任务。从图中可见,V86模式与保护模式的切换可发生在V86任务之内,这种切换是V86模式下的8086程序与保护模式下的监控程序之间的转换;V86模式与保护模式的切换可发生在任务之间,这种切换是V86任务与其它任务的切换。此外,V86监控程序与其它任务之间的切换是普通的任务切换。

保护模式教程11_第1张图片

由于80386没有提供直接改变VM标志的指令,并且只有当前特权级CPL=0时,对VM的改变才有效,所以V86模式与保护模式的切换不能简单地通过改变VM位而进行。下面介绍V86模式与保护模式之间的切换,也就是如何进入和离开V86模式。为了方便,先介绍如何离开V86模式。

1.离开V86模式

V86模式下,如果处理器响应中断/异常,那么就会退出当前V86任务的V86模式。

V86模式下,处理器对中断/异常的响应处理不同于真正的8086,而仍然采用保护模式下对中断/异常响应处理的方法。所以,在V86模式下,不是根据位于线性地址空间最低端的中断向量表内的对应中断向量转入处理程序,而是根据中断描述符表IDT内的对应门描述符的指示转入处理程序。

(1)V86任务内离开V86模式

如果对应的门描述符是386中断门或386陷阱门,那么就发生在当前V86任务内从V86模式到保护模式的转换。80386要求执行这种中断/异常处理程序时的CPL必须等于0

由于V86模式下的CPL=3,而转换到保护模式后的CPL=0,所以这种转换包含了特权级的变换。在转入处理程序之前,处理器先将V86模式下的段寄存器GSFSDSES压入0级堆栈,并在进入保护模式下的处理程序之前装入空选择子。为保持使堆栈对齐,把段寄存器压入堆栈时一律按32位值压入,低16位是段寄存器的值,高16位为空。于是,转换后的0级堆栈如下图所示。其中,段寄存器SSCS的值也是V86模式下的段值。图(a)是没有出错码的情形;图(b)是有出错码的情形。

保护模式教程11_第2张图片

在这种V86任务内从V86模式转换到保护模式的过程中,为了保证中断/异常处理程序工作于特权级0,对目标代码段描述符特权级进行检查,如果由目标代码段描述符特权级决定的CPL不等于0,将引起通用保护异常。此外,标志寄存器EFLAGS中的VM位被清0,从而使得中断/异常处理在保护模式下进行,也即离开V86模式。

这种情况下,相应的中断/异常处理在当前V86任务之内进行。中断异常处理程序可以检查保存在堆栈中的EFLAGS映象,根据VM位的值来确定被中断程序的工作模式。如果VM=1,那么被中断的程序工作于V86模式,是8086程序;否则,被中断的程序工作于保护模式,是V86监控程序。

(2)任务切换离开V86模式

如果对应的门描述符是任务门,那么就发生从当前V86任务到其它任务的切换,也就离开了当前V86任务的V86方式。象普通任务切换一样,V86模式的各通用寄存器、段寄存器、指令指针和标志寄存器EFLAGS等保存到原V86任务的386TSS中。被保存的段寄存器的内容是V86模式下的段值。被保存的EFLAGS内的VM=1

这种情况下,相应的中断异常处理在另一个任务内进行。目标任务可以是普通任务,也可以是另一个V86任务。如果目标任务TSS内的EFLAGS字段内的VM=1,那么就转入另一个V86任务的V86模式。

2.进入V86模式

与离开V86模式的两条途径相对应,有两条进入V86模式的途径。

(1)通过IRET指令进入V86模式

通过在中断/异常处理结束时使用IRET指令返回被中断的程序继续执行。指令IRET的执行步骤如下所示:

(1)NT=1,则进行任务切换,然后转步骤6

(2)否则从堆栈中弹出EIPCSEFLAGS

(3)VM=1CPL=0,则恢复外层堆栈及其它段寄存器,然后转步骤6

(4)若无特权级变换则转步骤6

(5)否则恢复外层堆栈;

(6)结束

尽管上述步骤不够细致和没包括异常情况,但还是体现了指令IRET执行时所处理的三种情形。第一种情形是当前EFLAGS中的NT=1,也即嵌套任务返回,那么就进行任务切换,指向目标任务TSS的选择子在当前任务TSS的连接字段。NT=0表示当前中断/异常处理程序与被中断程序属于同一任务,于是就从堆栈弹出EIPCSEFLAGS。第二和第三中情形是NT=0的条件下产生的。第二种情形是弹出的EFLAGSVM=0,表示被中断的程序是普通保护模式程序,那么就考虑特权级变换,如果向外层返回,那么就恢复外层堆栈指针。不允许向内层返回,否则将会引起通用保护异常。前文中介绍的IRET指令的动作只考虑了情形一和情形二,并不是IRET指令的完整动作。

第三种情形是弹出的EFLAGSVM=1且当前正运行程序的CPL=0,表示被中断的程序是V86模式下的8086程序,当前是从同一V86任务下的中断/异常处理程序返回。由于V86模式的特权级是3,所以要进行堆栈切换,也即从堆栈中弹出3级堆栈的指针(ESPSS)。此外,还从堆栈中弹出段寄存器ESDSFSGS。在这种情形下,弹到各段寄存器(包括CSSS)的内容都作为段值,而非选择子。这种处理动作对应于上述第一种离开V86模式的情形,有关堆栈操作与上图所示的堆栈内容相符。当然,如果产生异常时提供出错码,那么异常处理程序在利用IRET指令返回时,必须确保堆栈指针指向如上图所示保存EIP的单元。简单的实现方法是,异常处理程序在执行IRET前,先从堆栈中弹出出错码。

利用指令IRET处理的这第三种情形,可以方便地从V86任务下的中断/异常处理程序返回到V86模式下的8086程序。利用这条途径还可以直接进入V86模式。为此,先在0级堆栈中形成如上图(a)所示的栈顶。对应EIP值是V86模式下要执行的8086程序入口点的16位偏移;对应CS值是V86模式下要执行的8086程序入口点的段值;对应EFLAGS值中的VM位必须是1;对应SSESP的值是要执行的8086程序的堆栈指针;对应ESDSFSGS的值是相应的段值。然后,在CPL=0NT=0的情况下,执行IRET指令。实际上,这种进入V86模式的途径是,先建立一个V86模式下执行的8086程序被中断而离开V86模式的环境,然后再返回。需要注意的是,若当前正执行程序的CPL不为0,则再执行IRET指令时不会进入V86模式,但也不产生异常,EFLAGS中的VM位被处理器自动清0

不能通过RET指令进入V86模式,因为它不改变EFLAGS的内容。任何不能修改EFLAGSVM位的指令均不能切换到V86模式。

(2)通过任务切换进入V86模式

通过任务切换的途径,可以从其它任务进入V86任务内的V86模式。

利用在前文介绍的任务切换的方法可以进行任务切换。如果目标任务由386TSS描述,并且其中EFLAGS字段内的VM位为1,那么在切换到目标任务时,也就进入了V86模式。在切换到V86模式时,CPL被规定为3。目标任务TSS中的各段寄存器字段被解释为8086可以接受的段值,而不是选择子。任务切换时也将装载LDTRCR3

程序在V86模式下执行时,EFLAGS寄存器中的NT位被忽略,所以,不能在V86模式下用IRET指令完成一个任务切换,并使其工作于V86以外的工作模式。在V86模式下执行IRET指令时,将弹出 IPCSFLAGS寄存器,以恢复被中断的程序,而不考虑NT位的值。

如果利用这条途径建立V86任务并进入V86模式,那么主要是把对应386TSSEFLAGS字段内的VM位置1,把8086程序的有关段值填入对应386TSS中的相应段寄存器字段。此外,如果V86监控程序需要用到LDT,那么还要填写LDTR字段;如果需要采用分页机制,那么还要填写CR3字段(当新任务为V86模式的任务时,只装入段寄存器,而没有装入描述符投影寄存器的动作)

<>演示进入和离开V86模式的实例(实例十一)

下面给出一个用于演示进入和离开V86模式的实例。该实例的逻辑功能是,以驻留方式结束程序,退出时已处于V86模式。该实例演示内容包括:两种方式进入V86模式和两种方式离开V86模式; V86模式下的8086程序如何调用实模式下的软中断处理程序。

1.演示步骤和源程序清单

为了便于演示,本实例含有三个任务:临时任务,V86任务和INTFF任务。在实模式下做必要的初始化工作后切换到保护模式,也即进入临时任务,开始演示。演示分两个阶段:第一阶段进入V86任务的V86模式,并驻留退出;第二阶段进入INTFF任务,切换到临时任务,并返回实模式。

第一阶段的演示步骤如下:

(1)开始临时任务后,作切换到V86任务的准备;

(2)切换到V86任务,由于V86任务TSS中的EFLAGS字段内的VM=1,所以伴随着任务切换就进入了V86模式。

(3)进入V86任务的V86模式后,显示提示信息,驻留结束,出现DOS提示符,第一阶段至此结束。

V86模式下,可进行各种操作,运行其它8086程序。如果8086程序引起通用保护异常,那么在屏幕上显示提示信息,并中止该8086程序。如果在8086程序中执行“INT 0FFH”指令,开始第二阶段。第二阶段的演示步骤如下:

(1)进入INTFF任务后,显示提示信息,切换到临时任务;

(2)在临时任务内切换到实模式;

(3)在实模式下中止发出“INT 0FFH”的程序

源程序由如下几部分组成:

(1)全局描述符表GDT

(2)中断描述符表IDT(只适用于V86任务)

(3)INTFF任务的TSS段、LDT段、0级堆栈段和代码段;

(4)V86任务的TSS段、LDT段、0级堆栈段、3级堆栈段及数据段,通用保护异常处理程序段和其它中断/异常处理程序段,V86模式下的8086程序段;

(5)临时任务的TSS段和代码段;

(6)实模式下的初始化代码段及有关过程。

源程序清单如下:

;名称:ASM11.ASM
;功能:演示进入和离开V86方式
;编译:TASM ASM11.ASM
;连接:TLINK ASM11.OBJ
;============================================================================
INCLUDE         386SCD.INC
;============================================================================
GDTSeg          SEGMENT PARA USE16                ;全局描述符表数据段(16)
;----------------------------------------------------------------------------
                ;全局描述符表
GDT             LABEL   BYTE
                ;空描述符
DUMMY           Desc    <>
                ;规范段描述符及选择子
Normal          Desc    <0ffffh,,,ATDW,,>
Normal_Sel      =       Normal-GDT
                ;显示缓冲区数据段描述符及选择子
Video           Desc    <07fffh,8000h,0bh,ATDW,,>
Video_Sel       =       Video-GDT
;----------------------------------------------------------------------------
EFFGDT          LABEL   BYTE
                ;V86任务TSS段描述符及选择子
V86TSS          Desc    <V86TSSLen-1,V86TSSSeg,,AT386TSS,,>
V86TSS_Sel      =       V86TSS-GDT
                ;V86任务局部描述符表的描述符及选择子
V86LDT          Desc    <V86LDTLen-1,V86LDTSeg,,ATLDT,,>
V86LDT_Sel      =       V86LDT-GDT
                ;IntFF任务TSS段描述符及选择子
IntFFTSS        Desc    <IntFFTSSLen-1,IntFFTSSSeg,,AT386TSS,,>
IntFFTSS_Sel    =       IntFFTSS-GDT
                ;IntFF任务局部描述符表的描述符及选择子
IntFFLDT        Desc    <IntFFLDTLen-1,IntFFLDTSeg,,ATLDT,,>
IntFFLDT_Sel    =       IntFFLDT-GDT
                ;临时任务的任务状态段描述符及选择子
TempTSS         Desc    <TempTSSLen-1,TempTSSSeg,,AT386TSS,,>
TempTSS_Sel     =       TempTSS-GDT
                ;临时任务代码段描述符及选择子
TempCode        Desc    <0ffffh,TempCodeSeg,,ATCE,,>
TempCode_Sel    =       TempCode-GDT
;----------------------------------------------------------------------------
GDNum           =       ($-EFFGDT)/(SIZE Desc)    ;需特殊处理的描述符数
GDTLen          =       $-GDT                     ;全局描述符表长度
GDTSeg          ENDS                              ;全局描述符表段定义结束
;============================================================================
IDTSeg          SEGMENT PARA USE16                ;V86任务使用的中断描述符表
IDT             LABEL   BYTE
                ;对应0--12号中断/异常的中断门描述符
                REPT    13
                Gate    <,TPCode_Sel,,AT386IGate+DPL3,>
                ENDM
                ;通用保护故障处理程序门描述符
                Gate   <GPBegin,GPCode_Sel,,AT386TGate+DPL3,>
                ;对应14--254号中断/异常的中断门描述符
                REPT    241
                Gate    <,TPCode_Sel,,AT386IGate+DPL3,>
                ENDM
                ;对应255号中断的任务门描述符
                Gate    <,IntFFTSS_Sel,,ATTaskGate+DPL3,>
;----------------------------------------------------------------------------
IDTLen          =       $-IDT
IDTSeg          ENDS                              ;中断描述符表段定义结束
;============================================================================
;IntFF任务的TSS
IntFFTSSSeg     SEGMENT PARA USE16
                DD      0                         ;链接字
                DD      0                         ;0级堆栈指针(实例不使用)
                DW      0,0                       ;0级堆栈选择子(实例不使用)
                DD      0                         ;1级堆栈指针(实例不使用)
                DW      0,0                       ;1级堆栈选择子(实例不使用)
                DD      0                         ;2级堆栈指针(实例不使用)
                DW      0,0                       ;2级堆栈选择子(实例不使用)
                DD      0                         ;CR3
                DW      IntFFBegin,0              ;EIP
                DD      0                         ;EFLAGS
                DD      0                         ;EAX
                DD      0                         ;ECX
                DD      0                         ;EDX
                DD      0                         ;EBX
                DD      IntFFStackLen             ;ESP
                DD      0                         ;EBP
                DD      0                         ;ESI
                DD      0                         ;EDI
                DW      Normal_Sel,0              ;ES
                DW      IntFFCode_Sel,0           ;CS
                DW      IntFFStack_Sel,0          ;SS
                DW      Normal_Sel,0              ;DS
                DW      Normal_Sel,0              ;FS
                DW      Normal_Sel,0              ;GS
                DW      IntFFLDT_Sel,0            ;LDTR
                DW      0                         ;调试陷阱标志
                DW      $+2                       ;指向I/O许可位图
                DB      0ffh                      ;I/O许可位图结束标志
;----------------------------------------------------------------------------
IntFFTSSLen     =       $
IntFFTSSSeg     ENDS
;============================================================================
;IntFF任务的LDT
IntFFLDTSeg     SEGMENT PARA USE16
FLDT            LABEL   BYTE
                ;0级堆栈段描述符及选择子
IntFFStack      Desc    <IntFFStackLen-1,IntFFStackSeg,,ATDWA,,>
IntFFStack_Sel  =       IntFFStack-FLDT+TIL
                ;代码段描述符及选择子
IntFFCode       Desc    <IntFFCodeLen-1,IntFFCodeSeg,,ATCER,,>
IntFFCode_Sel   =       IntFFCode-FLDT+TIL
;----------------------------------------------------------------------------
IntFFLDNum      =       ($-FLDT)/(SIZE Desc)
IntFFLDTLen     =       $
IntFFLDTSeg     ENDS
;============================================================================
;IntFF任务的堆栈
IntFFStackSeg   SEGMENT PARA USE16
IntFFStackLen   =       512
                DB      IntFFStackLen DUP(0)
IntFFStackSeg   ENDS
;============================================================================
;IntFF任务的代码段
IntFFCodeSeg    SEGMENT PARA USE16
                ASSUME  CS:IntFFCodeSeg
;----------------------------------------------------------------------------
IntFFMess       DB      'Return to real mode.'
IntFFMessLen    =       $-IntFFMess
;----------------------------------------------------------------------------
IntFFBegin      PROC    FAR
                mov     si,OFFSET IntFFMess
                mov     ax,Video_Sel
                mov     es,ax
                mov     di,0
                mov     ah,17h
                mov     cx,IntFFMessLen
                cld
INext:          mov     al,BYTE PTR cs:[si]
                inc     si
                stosw
                loop    INext
                JUMP16  TempTSS_Sel,0
IntFFBegin      ENDP
;----------------------------------------------------------------------------
IntFFCodeLen    =       $
IntFFCodeSeg    ENDS
;============================================================================
;V86任务的TSS
V86TSSSeg       SEGMENT PARA USE16
                DD      0                         ;链接字
                DD      V86Stack0Len              ;0级堆栈指针
                DW      V86Stack0_Sel,0           ;0级堆栈选择子
                DD      0                         ;1级堆栈指针(实例不使用)
                DW      0,0                       ;1级堆栈选择子(实例不使用)
                DD      0                         ;2级堆栈指针(实例不使用)
                DW      0,0                       ;2级堆栈选择子(实例不使用)
                DD      0                         ;CR3
                DW      V86Begin,0                ;EIP
                DD      IOPL3 OR VMFL             ;EFLAGS(IO特权级为3,VM=1)
                DD      0                         ;EAX
                DD      0                         ;ECX
                DD      0                         ;EDX
                DD      0                         ;EBX
                DD      V86Stack3Len              ;ESP
                DD      0                         ;EBP
                DD      0                         ;ESI
                DD      0                         ;EDI
                DW      V86CodeSeg,0              ;ES
                DW      V86CodeSeg,0              ;CS
                DW      V86Stack3Seg,0            ;SS
                DW      V86CodeSeg,0              ;DS
                DW      V86CodeSeg,0              ;FS
                DW      V86CodeSeg,0              ;GS
                DW      V86LDT_Sel,0              ;LDTR
                DW      0                         ;调试陷阱标志
                DW      $+2                       ;指向I/O许可位图
                DB      4000h/8 DUP(0)            ;I/O许可位图
                DB      0ffh                      ;I/O许可位图结束标志
;----------------------------------------------------------------------------
V86TSSLen       =       $
V86TSSSeg       ENDS
;============================================================================
;V86任务的LDT
V86LDTSeg       SEGMENT PARA USE16
VLDT            LABEL   BYTE
;----------------------------------------------------------------------------
                ;V86任务线性地址空间中最低端1M字节段的描述符及描述符
VAllMem         Desc    <0ffffh,,,ATDWA,0fh,>
VAllMem_Sel     =       VAllMem-VLDT+TIL
                ;V86任务0级堆栈段描述符及选择子
V86Stack0       Desc    <V86Stack0Len-1,V86Stack0Seg,,ATDWA,,>
V86Stack0_Sel   =       V86Stack0-VLDT+TIL
                ;V86任务数据段描述符及选择子
V86Data         Desc    <V86DataLen-1,V86DataSeg,,ATDR,,>
V86Data_Sel     =       V86Data-VLDT+TIL
                ;V86任务中断/异常处理程序代码段描述符及选择子
TPCode          Desc    <TPCodeLen-1,TPCodeSeg,,ATCE,,>
TPCode_Sel      =       TPCode-VLDT+TIL
                ;V86任务通用保护异常处理程序代码段描述符及选择子
GPCode          Desc    <GPCodeLen-1,GPCodeSeg,,ATCE,,>
GPCode_Sel      =       GPCode-VLDT+TIL
;----------------------------------------------------------------------------
V86LDNum        =       ($-VLDT)/(SIZE Desc)
V86LDTLen       =       $
V86LDTSeg       ENDS
;============================================================================
;V86任务的0级堆栈
V86Stack0Seg    SEGMENT PARA USE16
V86Stack0Len    =       512
                DB      V86Stack0Len DUP(0)
V86Stack0Seg    ENDS
;============================================================================
;V86任务的3级堆栈
V86Stack3Seg    SEGMENT PARA USE16
V86Stack3Len    =       1024
                DB      V86Stack3Len DUP(0)
V86Stack3Seg    ENDS
;============================================================================
;V86任务数据段
V86DataSeg      SEGMENT PARA USE16
GPErrMess       DB      '......General Protection Error......'
GPErrMessLen    =       $-GPErrMess
V86DataLen      =       $
V86DataSeg      ENDS
;============================================================================
;定义部分代表堆栈单元的符号
Perr            EQU     <WORD PTR [BP+0]>
Pip             EQU     <WORD PTR [bp+4]>
Pcs             EQU     <WORD PTR [bp+8]>
Pflag           EQU     <WORD PTR [bp+12]>
Psp             EQU     <WORD PTR [bp+16]>
Pss             EQU     <WORD PTR [bp+20]>
Pes             EQU     <WORD PTR [bp+24]>
Pds             EQU     <WORD PTR [bp+28]>
Pfs             EQU     <WORD PTR [bp+32]>
Pgs             EQU     <WORD PTR [bp+36]>
;============================================================================
;V86任务下的中断/异常处理程序代码段
TPCodeSeg       SEGMENT PARA USE16
                ASSUME  CS:TPCodeSeg
;----------------------------------------------------------------------------
TPBegin         PROC    FAR
Count           =       0
                REPT    256                       ;对应256个入口
                IF      Count EQ 21h
Ent21H          LABEL   BYTE                      ;在第21H项处定义标号Ent21H
                ENDIF
                push    bp
                mov     bp,Count                  ;置中断向量号到BP
                jmp     Process                   ;都转统一的处理程序
Count           =       Count+1
                ENDM
Process:        push    bp                        ;保存BP
                mov     bp,sp                     ;堆栈指针送BP
                push    eax
                push    ebx                       ;保存EAXEBX
                ;V86堆栈顶形成返回点的现场
                mov     ax,VAllMem_Sel            ;转载描述最低1M字节线性地址
                mov     ds,ax                     ;  空间的描述符选择子
                xor     eax,eax
                mov     ax,Psp                    ;修改在V86任务0级堆栈中保存
                sub     ax,3*2                    ;  3级堆栈的指针,减3个字
                mov     Psp,ax                    ;  即在栈顶空出3个字
                xor     ebx,ebx
                mov     bx,Pss                    ;使EBX指向V86堆栈顶
                shl     ebx,4
                add     ebx,eax
                mov     ax,Pip                    ;把保存在0级堆栈中的返回地址
                mov     WORD PTR [ebx],ax         ;的偏移部分送V86堆栈
                mov     ax,Pcs
                mov     WORD PTR [ebx+2],ax       ;段值部分送V86堆栈
                mov     ax,Pflag
                mov     WORD PTR [ebx+4],ax       ;标志值送V86堆栈
                ;用对应的中断向量值代替返回地址
                mov     bx,[bp]                   ;取中断向量号
                shl     bx,2                      ;4
                mov     ax,[bx]                   ;取实模式下对应中断向量的偏移
                mov     Pip,ax                    ;代替0级堆栈中的EIP
                mov     ax,[bx+2]                 ;取实模式下对应中断向量的段值
                mov     Pcs,ax                    ;代替0级堆栈中的CS
                pop     ebx                       ;恢复现场
                pop     eax
                pop     bp
                pop     bp
                ;从保护方式返回V86方式
                ;先转入对应的中断处理程序,再返回中断发生处
                iretd
TPBegin         ENDP
;----------------------------------------------------------------------------
TPCodeLen       =       $
TPCodeSeg       ENDS
;============================================================================
;V86任务下的通用保护故障处理程序代码段
;----------------------------------------------------------------------------
GPCodeSeg       SEGMENT PARA USE32
                ASSUME  CS:GPCodeSeg
;----------------------------------------------------------------------------
GPBegin         PROC    FAR
                mov     ax,V86Data_Sel
                mov     ds,ax
                mov     si,OFFSET GPErrMess
                mov     ax,Video_Sel
                mov     es,ax
                mov     di,0
                mov     ah,17h
                mov     cx,GPErrMessLen
                cld
GNext:          lodsb
                stosw
                loop    GNext
                add     esp,4
                mov     ax,4c01h
                JUMP16  TPCode_Sel,Ent21H
GPBegin         ENDP
;----------------------------------------------------------------------------
GPCodeLen       =       $
GPCodeSeg       ENDS
;============================================================================
;V86方式执行的8086程序段
V86CodeSeg      SEGMENT PARA USE16
                ASSUME  CS:V86CodeSeg,DS:V86CodeSeg
Message         DB      'V86 is OK!',0dh,0ah,24h
V86Begin        PROC    FAR
                mov     ah,9
                mov     dx,OFFSET Message
                int     21h
                mov     ax,RCodeSeg
                sub     ax,GDTSeg
                mov     dx,OFFSET TSRLine+15
                shr     dx,4
                add     dx,ax
                add     dx,10h
                mov     ax,3100h
                int     21h
V86Begin        ENDP
V86CodeSeg      ENDS
;============================================================================
TempTSSSeg      SEGMENT PARA USE16                ;临时任务的TSS
                TSS     <>
                DB      0ffh                      ;I/O许可位图结束标志
TempTSSLen      =       $
TempTSSSeg      ENDS
;============================================================================
TempCodeSeg     SEGMENT PARA USE16                ;临时任务的代码段
                ASSUME  CS:TempCodeSeg
;----------------------------------------------------------------------------
Virtual         PROC    FAR
                mov     ax,Normal_Sel
                mov     ds,ax
                mov     es,ax
                mov     fs,ax
                mov     gs,ax
                mov     ss,ax
                mov     ax,TempTSS_Sel            ;装载TR
                ltr     ax
                JUMP16  V86TSS_Sel,0              ;直接切换到演示任务
ToDos:          clts
                mov     eax,cr0                   ;准备返回实模式
                and     al,11111110b
                mov     cr0,eax
                JUMP16  <SEG Real>,<OFFSET Real>
Virtual         ENDP
;----------------------------------------------------------------------------
TempCodeSeg     ENDS
;============================================================================
RDataSeg        SEGMENT PARA USE16                ;实方式数据段
RDataSeg        ENDS
;============================================================================
RCodeSeg        SEGMENT PARA USE16
                ASSUME  CS:RCodeSeg,DS:RCodeSeg
;----------------------------------------------------------------------------
VGDTR           PDesc   <GDTLen-1,>               ;GDT伪描述符
VIDTR           PDesc   <IDTLen-1,>               ;IDT伪描述符
NORVIDTR        PDesc   <>                        ;用于保存原IDTR
SPVar           DW      ?                         ;用于保存实方式下的SP
SSVar           DW      ?                         ;用于保存实方式下的SS
;----------------------------------------------------------------------------
Start           PROC
                mov     ax,RCodeSeg
                mov     ds,ax
                cld
                call    InitGDT                   ;初始化全局描述符表GDT
                call    InitIDT                   ;初始化中断描述符表IDT
                mov     ax,V86LDTSeg
                mov     fs,ax
                mov     cx,V86LDNum
                mov     si,OFFSET VLDT
                call    InitLDT
                mov     ax,IntFFLDTSeg
                mov     fs,ax
                mov     cx,IntFFLDNum
                mov     si,OFFSET FLDT
                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,cs
                mov     ds,ax
                lss     sp,DWORD PTR SPVar        ;又回到实方式
                lidt    QWORD PTR NORVIDTR
                sti
                mov     ax,4c00h
                int     21h
Start           ENDP
;----------------------------------------------------------------------------
TSRLine         LABEL   BYTE
;----------------------------------------------------------------------------
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
;----------------------------------------------------------------------------
;入口参数:FS:SI=第一个要初始化的描述符,CX=要初始化的描述符数
;----------------------------------------------------------------------------
InitLDT         PROC
                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    InitLDT
                ret
InitLDT         ENDP
;----------------------------------------------------------------------------
InitIDT         PROC
                push    ds
                mov     ax,IDTSeg
                mov     ds,ax
                mov     cx,256-1
                mov     si,OFFSET IDT
                mov     ax,OFFSET TPBegin
IIDT1:          cmp     cx,256-1-13
                jz      IIDT2
                mov     [si],ax
IIDT2:          add     si,8
                add     ax,7
                loop    IIDT1
                pop     ds
                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

用于演示INTFF任务的源程序如下:

;功能:用于演示V86模式下的INTFF任务
;编译:TASM INTFF.ASM
;连接:TLINK INTFF.OBJ
;====================================================================
Text            SEGMENT
                ASSUME  cs:Text,ds:Text
;---------------------------------------
Start           PROC
                int     0ffh
                mov     ax,4c00h
                int     21h
Start           ENDP
;---------------------------------------
Text            ENDS
                END     Start

2.说明

(1)IDT表的初始化

为了方便地书写IDT表,采用了重复汇编。从采用重复汇编方式定义的IDT表可见,除对应通用保护异常的13号陷阱门描述符和255号任务门描述符外,其它中断门描述符内的偏移均未设定。为此,在实模式下初始化时,把相应的入口偏移填入这些门描述符。从源程序可见,这些处理程序的入口偏移也用重复汇编书写,并且字节数相同,所以间隔等长。

(2)任务切换方式进入V86模式

实例从临时任务切换到V86任务时进入V86模式。在V86任务的TSS中,EFLAGS字段内的VM=1,所以伴随着任务切换,就进入了V86模式。为此,TSS中对应各段寄存器字段内的初始值都是V86模式下要执行的8086程序各段的段值,而非选择子。由于在发生中断/异常时要进入V86任务的特权级0,所以初始化了0级堆栈指针。V86任务使用局部描述符表LDT,所以TSS中对应字段填有相应的选择子。

(3)V86模式下对中断的处理

实例对V86模式下响应中断和执行软中断指令“INT n”的处理方法是,转实模式下的对应中断处理程序。具体步骤如下:

1)V86模式堆栈(V86任务的3级堆栈)顶形成返回点的现场;

2)用实模式下对应的中断向量值代替返回地址;

3)从保护模式返回V86模式。由于堆栈中保存的EFLAGS内的VM=1,所以在保护模式下执行IRET指令时,返回V86模式;由于在0级堆栈中保存的返回地址(段值和偏移)已被修改成实模式下的中断向量,所以这时的返回也就是转入实模式下的对应中断处理程序;由于在V86堆栈顶已安排了返回地址,所以在执行实模式下对应中断处理程序时,遇到IRET指令就返回到V86任务的被中断处。这种处理方法似乎绕了个弯。但就是利用这个方法有效地调用了DOS功能,方便地显示了提示信息“V86 is OK.”,顺利地实现了驻留退出。

这种处理方法没有充分考虑异常,更没有考虑异常时的出错码。

(4)V86任务的通用保护异常处理

在演示的第一阶段和第二阶段,不会发生任何异常。但在这两个阶段之间,由于允许执行其它程序,可能引起异常。为了简单,实例只考虑了通用保护异常,而未考虑其它异常。该可能发生异常的阶段是在V86模式下。如果发生通用保护异常,那么就转入V86任务的通用保护异常处理程序。通用保护异常处理程序在保护模式下显示提示信息 “......General Protection Error......”,然后再转21H号中断处理程序,通过设置入口参数AX=4C01H,中止引起通用保护异常的程序。

(5)INTFF任务

为了演示以任务切换方式离开V86模式,在实例中安排了这一任务,并称之为INTFF任务。由于中断描述符表IDT中的255(0FFH)号描述符是任务门描述符,所以当在V86模式下执行软中断指令“INT 0FFH”时便从V86模式切换到INTFF任务。INTFF任务的TSS是初始化好的,在INTFF任务下不发生特权级变换,不使用局部描述符表LDTINTFF任务先显示提示信息 “Return to real mode.”,然后再切换到临时任务。

<>V86模式下的敏感指令

V86模式下,CPL=3,执行特权指令时,或者要引起出错码为0的通用保护故障,或者要引起非法操作码故障。

V86模式下,输入/输出指令的敏感条件有所变化。指令CLISTI的敏感条件不变,由于CPL=3,所以如果IOPL<3,那么执行CLISTI指令将引起通用保护故障。输入/输出指令ININSOUTOUTS的敏感条件仅仅是当前V86任务TSS内的I/O许可位图,而忽略EFLAGS中的IOPL。输入/输出指令ININSOUTOUTS是否可以执行与CPL是否小于IOPL无关,而直接由I/O许可位图对应位决定,如果输入/输出指令所使用I/O地址对应的I/O许可位图内的各位都位0,则输入输出指令可正常执行,否则引起通用保护故障。

此外,在V86模式下,指令PUSHFPOPFINT nIRET却对IOPL敏感。也就是说,在V86模式下,当IOPL<3时,执行指令PUSHFPOPFINT nIRET会引起出错码为0的通用保护故障。

采取上述措施的目的是使操作系统软件可以支持一个虚拟EFLAGS”寄存器。

你可能感兴趣的:(保护模式教程11)