3.关于实例四的说明
程序中部分片段的背景和实现方法在前面的实例中做过介绍,下面主要就如何实现任务内特权级变换做些说明:
(1)通过段间返回指令实现特权级变换
实例在两处使用段间返回指令实现任务内的特权级变换。一处是在0级的过渡代码段中用段间RET指令从特权级0变换到特权级3的演示代码段。该处RET指令并不对应CALL指令。实例从实模式切换到保护模式后CPL=0。为了演示如何通过调用门调用内层程序,要设法使CPL>0。为此,实例先建立一个已发生的从外层到内层变换的环境,即按上图所示在当前堆栈(0级堆栈)中放入外层堆栈的指针和外层演示程序的入口指针,形成一个如下图所示的0级堆栈,无需传递参数。然后,执行段间返回指令RET,从堆栈中弹出3级演示代码段的选择子,RPL=3,而当时CPL=0,所以导致向外层变换特权级,从0级的过渡代码段变换到3级的演示代码段,同时切换到3级堆栈。
另一处是从1级的显示子程序EchoSub返回到3级的演示程序段。该处的RET指令与演示程序中使用的通过调用门的段间调用指令CALL相对应,执行段间返回指令RET时的1级堆栈也如上图所示,其中的返回地址和外层栈指针由CALL指令压入。
(2)通过调用门实现特权级变换
实例在两处使用了段间调用指令,通过调用门实现特权级的变换。一处是3级演示代码通过调用门ToEchoGate调用1级的显示子程序。调用门ToEchoGate自身的DPL=3,只有这样,3级的演示代码才能够使用该调用门。由于调用门内的选择子Echo_Sel3所指示的显示子程序代码段描述符DPL=1,而当时CPL=3,所以引起从外层特权级向内层特权级的变换,使CPL=1。同时形成如上图所示的1级堆栈。虽然调用门内的选择子Echo_Sel3的RPL=3,大于目标代码段的DPL,但没有关系,因为在通过调用门转移时,门内指示目标代码段的选择子RPL总被作0对待。
另一处是3级演示代码还通过调用门ToT32GateB调用了0级的过渡代码。该处使用的调用门描述符DPL也等于3。由于调用门内的选择子T32Code_Sel所指示的过渡代码段描述符的DPL=0,而当时CPL=3,所以引起从3特权级向0特权级的变换,使CPL=0。同时形成如上图所示的0级堆栈。但该处的调用实际上是 “有去无回”的,调用的目的是转移到0级的过渡代码,准备返回实模式。由于从3级的演示代码到0级的过渡代码要发生特权级变换,所以不能使用转移指令JMP,必须使用调用指令CALL。
(3)通过调用门实现无特权级变换
在临时代码段中,使用调用门ToT32GateA转移到过渡代码段。尽管调用门内的选择子T32Code_Sel所指示的过渡代码段描述符的DPL=0,但当时CPL=0,所以不发生特权级变换。正是这个原因,才可以使用段间转移指令JMP。
(4)子程序EchoSub的实现
子程序EchoSub的功能是显示调用程序执行时的特权级。调用程序的执行特权级在代码段寄存器CS内选择子的RPL字段,在调用EchoSub时,CS寄存器的内容被压入堆栈。子程序从堆栈取得调用程序的代码段选择子,再从中分离出RPL就可得调用程序的执行特权级。
(5)装载任务状态段寄存器TR
在任务内发生特权级变换时堆栈也随着自动切换,外层堆栈指针保存在内层堆栈中,而内层堆栈指针存放在当前任务的TSS中。所以,在从外层向内层变换时,要访问TSS(从内层向外层转移时不需要访问TSS,而只需内层栈中保存的栈指针)。实例在进入保护模式下的临时代码段后,通过如下两条指令装载任务状态段寄存器TR,使其指向已预置好的任务的TSS: mov ax,DemoTSS_Sel
ltr ax
LTR指令是专门用于装载任务状态段寄存器TR的指令。该指令的操作数是对应TSS段描述符的选择子。LTR指令从GDT中取出相应的TSS段描述符,把TSS段描述符的基地址和界限等信息装入TR的高速缓冲寄存器中。
<五>任务切换
利用段间转移指令JMP或者段间调用指令CALL,通过任务门或直接通过任务状态段,可以切换到别的任务。此外,在中断/异常或者执行IRET指令时也可能发生任务切换。需要注意的是,因为RET指令的目标地址只能使用代码段描述符,所以,不能通过RET指令实现任务切换。
1.直接通过TSS进行任务切换
段间转移指令JMP或段间调用指令CALL所含指针的选择子指示一个可用任务状态段TSS描述符时,正常情况下就发生从当前任务到由该可用TSS对应任务(目标任务)的切换。目标任务的入口点由目标任务TSS内的CS和EIP字段所规定的指针确定。这样的JMP或CALL指令内的偏移被丢弃。另外,对于段间调用指令CALL,若目标选择子指示一TSS段描述符或任务门时,则返回地址和外层栈指针并不压入堆栈。
处理器采用与访问数据段相同的特权级规则控制对TSS段描述符的访问。TSS段描述符的DPL规定了访问该描述符的最外层特权级,只有在相同级别或更内层级别的程序才可以访问它。同时,还要求指示它的选择子的RPL必须满足RPL<=TSS的DPL的条件。当这些条件满足时,就开始任务切换。
2.通过任务门进行任务切换
任务门内的选择子指示某个任务的TSS描述符。当段间转移指令JMP或段间调用指令CALL所含指针的选择子指示一个任务门时,正常情况下就发生任务切换,即从当前任务切换到由任务门内的选择子所指示的TSS描述符对应的任务(目标任务)。这样的JMP或CALL指令内的偏移被丢弃;任务门内的偏移也无意义。
处理器采用与访问数据段相同的特权级规则控制对任务门的访问。任务门的DPL规定了访问该任务门的最外层特权级,只有在同级或更内层级别的程序才可以访问它。同时,还要求指示任务门的选择子RPL必须满足RPL<=任务门的DPL的条件。在这些条件满足时,再检查任务门内的选择子,要求该选择子指示GDT中的可用的TSS描述符。对于任务门所指向的TSS描述符的DPL不进行特权级检查。检查通过以后,就开始任务切换。
3.任务切换过程
根据指示目标任务TSS描述符的选择子进行任务切换的一般过程如下:
第一,测试目标任务状态段的界限。TSS用于保存任务的各种状态信息,不同的任务,TSS中可以有数量不等的其他信息,根据任务状态段的基本格式,TSS的界限应大于或等于103(104-1)。
第二,把寄存器现场保存到当前任务的TSS。把通用寄存器、段寄存器、EIP及EFLAGS的当前值保存到当前TSS中。保存的EIP值是返回地址,指向引起任务切换指令的下一条指令。但不把LDTR和CR3的内容保存到TSS中。
第三,把指示目标任务TSS的选择子装入TR寄存器中。同时把对应TSS的描述符装入TR的高速缓冲寄存器中。此后,当前任务改称为原任务,目标任务改称为当前任务。
第四,基本恢复当前任务(目标任务)的寄存器现场。根据保存在TSS中的内容,恢复各通用寄存器、段寄存器、EFLAGS及EIP。在装入寄存器的过程中,为了能正确地处理可能发生的异常,只把对应选择子装入各段寄存器。此时选择子的P位为0。还装载CR3寄存器。
第五,进行链接处理。如果需要链接,那么将指向原任务TSS的选择子写入当前任务TSS的链接字段,把当前任务TSS描述符类型改为“忙”(并不修改原任务状态段描述符的“忙”位),并将标志寄存器EFLAGS中的NT位置1,表示是嵌套任务。如果需要解链,那么把原任务TSS描述符类型改为“可用”。如果无解链处理,那么将原任务TSS描述符类型置为“可用”,当前任务TSS描述符类型置为“忙”。由于JMP指令引起的任务切换不实施链接/解链处理;由CALL指令、中断、IRET指令引起的任务切换要实施链接/解链处理。
第六,把CR0中的TS标志置为1,这表示已发生过任务切换,在当前任务使用协处理器指令时,产生自陷。由自陷处理程序完成有关协处理器现场的保存和恢复。这有利于快速地进行任务切换。
第七,把TSS中的CS选择子的RPL作为当前任务特权级设置为CPL。又因为装入CS高速缓冲寄存器时要检测CPL=代码段描述符的DPL,所以TSS中的选择子所指示的代码段描述符的DPL必须等于该选择子的RPL。任务切换可以在一个任务的任何特权级发生,并且可以切换到另一任务的任何特权级。
第八,装载LDTR寄存器。一个任务可以有自己的LDT,也可以没有。当任务没有LDT时,TSS中LDT选择子为0。如果TSS中LDT选择子非空,则从GDT中读出对应的LDT描述符,在经过测试后,把所读的LDT描述符装入LDTR高速缓冲寄存器。如果LDT选择子为空,则将LDT的存在位置为0,表明任务不使用LDT。
第九,装载代码段寄存器CS、堆栈段寄存器SS和各数据段寄存器及其高速缓冲寄存器。在装入代码段高速缓存之前,也要进行特权检查,处理器调整TSS中的CS选择子的RPL=0,装入之后,调整CS的RPL等于目标代码段的DPL。堆栈段使用的是TSS中的SS和SP字段的值,而不是使用内层栈保存区中的指针,即使发生了向内层特权级的变换。这与任务内的通过调用门的转移不同。
第十,把调试寄存器DR7中的局部启用位置为0,以清除局部于原任务的各个断点和方式。
4.关于任务状态和嵌套的说明
需要注意的是,任务切换不能递归。
在段间转移指令JMP引起任务切换时,不实施链接,不导致任务的嵌套。它要求目标任务是可用任务。切换过程中把原任务置为“可用”,目标任务置为“忙”。
在段间调用指令CALL引起任务切换时,实施链接,导致任务的嵌套。它要求目标任务是可用的任务。在切换过程中把目标任务置为“忙”,原任务仍保持“忙”;标志寄存器EFLAGS中的NT位被置为1,表示任务是嵌套任务。
在由中断异常引起任务切换时,实施链接,导致任务的嵌套。要求目标任务是可用的任务。在切换过程中把目标任务置为“忙”,原任务仍保持“忙”;标志寄存器EFLAGS中的NT位被置为1,表示任务是嵌套任务。
在执行IRET指令时引起任务切换,那么实施解链。要求目标任务是忙的任务。在切换过程中把原任务置为“可用”,目标任务仍保持“忙”。关于中断/异常如何引起任务切换和指令IRET如何考虑任务切换的内容将在后面的文章中论述。
<六>演示任务切换的实例(实例五)
下面给出一个用于演示任务切换的实例。该实例的逻辑功能是在切换后显示原任务的挂起点(EIP)的值。该实例演示内容包括:直接通过TSS段的任务切换,通过任务门的任务切换,任务内特权级的变换及参数传递。
1.实现步骤
为了达到演示任务切换和特权级变换的目的,实例五在保护方式下涉及到两个任务,一个任务称为临时任务,另一个任务称为演示任务。演示任务的功能是演示通过调用门实现特权级的变换和堆栈间参数的自动复制。临时任务和演示任务配合展示任务切换。实例五的主要实现步骤如下:
(1)实模式下初始化;
(2)切换到保护模式;
(3)设置TR对应临时任务,特权级为0;
(4)直接切换到演示任务,演示任务的特权级为2;
(5)把入口参数压入堆栈,经调用门进入显示信息子程序,显示信息子程序的特权级为0;
(6)从堆栈中取出入口参数并处理;
(7)从显示信息子程序返回特权级为2的演示代码段;
(8)为切换回实模式作部分准备工作;
(9)经任务门切换到特权级为0的临时任务;
(10)准备返回实模式;
(11)切换到实模式;
(12)实模式下的恢复工作。
在任务切换时,把原任务的现场保存到TR所指示的TSS内,然后再把指向目标任务的TSS描述符的选择子装入TR,所以在从临时任务切换到演示任务之前,要把指向临时任务TSS描述符的选择子装入TR。通过把演示任务的TSS初始化成恢复点在特权级2的代码段,使得从临时任务切换到演示任务后,当前特权级CPL=2。
2.源程序组织和清单
实例五由如下部分组成:
(1)全局描述符表GDT。GDT含有演示任务的TSS描述符和LDT段描述符,还含有临时任务TSS描述符和临时任务的代码段描述符,此外,还含有子程序代码段描述符、规范数据段描述符和视频缓冲区段描述符。
(2)演示任务的TSS。以根据演示要求初始化。
(3)演示任务的LDT段。它含有演示任务的0级和2级堆栈段描述符、代码段和数据段描述符、分别以数据段方式描述LDT和临时任务TSS的数据段描述符、以及指向子程序的调用门和指向临时任务的任务门。
(4)演示任务的0级和2级堆栈段。32位段,特权级分别为0和2。
(5)演示任务数据段。32位段,特权级3。
(6)子程序代码段。32位代码段,特权级0。
(7)演示任务代码段。32位代码段,特权级2。
(8)临时任务的TSS段。未初始化。
(9)临时任务代码段。16位代码段,特权级0。
(10)实模式下的数据和代码段。
该实例的逻辑功能是在切换后显示原任务的挂起点(EIP)的值。源程序清单如下:
INCLUDE 386SCD.INC
GDTSeg SEGMENT PARA USE16
GDT LABEL BYTE
DUMMY Desc <>
Normal Desc <0ffffh,,,ATDW,,>
Normal_Sel = Normal-GDT
VideoBuf Desc <0ffffh,8000h,0bh,ATDW+DPL3,,>
Video_Sel = VideoBuf-GDT
EFFGDT LABEL BYTE
DemoLDTab Desc <DemoLDTLen-1,DemoLDTSeg,,ATLDT,,>
DemoLDT_Sel = DemoLDTab-GDT
DemoTSS Desc <DemoTSSLen-1,DemoTSSSeg,,AT386TSS,,>
DemoTSS_Sel = DemoTSS-GDT
TempTSS Desc <TempTSSLen-1,TempTSSSeg,,AT386TSS+DPL2,,>
TempTSS_Sel = TempTSS-GDT
TempCode Desc <0ffffh,TempCodeSeg,,ATCE,,>
TempCode_Sel = TempCode-GDT
SubR Desc <SubRLen-1,SubRSeg,,ATCE,D32,>
SubR_Sel = SubR-GDT
GDNum = ($-EFFGDT)/(SIZE Desc)
GDTLen = $-GDT
GDTSeg ENDS
DemoLDTSeg SEGMENT PARA USE16
DemoLDT LABEL BYTE
DemoStack0 Desc <DemoStack0Len-1,DemoStack0Seg,,ATDW,D32,>
DemoStack0_Sel = DemoStack0-DemoLDT+TIL
DemoStack2 Desc <DemoStack2Len-1,DemoStack2Seg,,ATDW+DPL2,D32,>
DemoStack2_Sel = DemoStack2-DemoLDT+TIL+RPL2
DemoCode Desc <DemoCodeLen-1,DemoCodeSeg,,ATCE+DPL2,D32,>
DemoCode_Sel = DemoCode-DemoLDT+TIL+RPL2
DemoData Desc <DemoDataLen-1,DemoDataSeg,,ATDW+DPL3,D32,>
DemoData_Sel = DemoData-DemoLDT+TIL
ToDLDT Desc <DemoLDTLen-1,DemoLDTSeg,,ATDW+DPL2,,>
ToDLDT_Sel = ToDLDT-DemoLDT+TIL
ToTTSS Desc <TempTSSLen-1,TempTSSSeg,,ATDW+DPL2,,>
ToTTSS_Sel = ToTTSS-DemoLDT+TIL
DemoLDNum = ($-DemoLDT)/(SIZE Desc)
ToSubR Gate <SubRB,SubR_Sel,,AT386CGate+DPL3,>
ToSubR_Sel = ToSubR-DemoLDT+TIL+RPL2
ToTempT Gate <,TempTSS_Sel,,ATTaskGate+DPL3,>
ToTempT_Sel = ToTempT-DemoLDT+TIL
DemoLDTLen = $-DemoLDT
DemoLDTSeg ENDS
DemoTSSSeg SEGMENT PARA USE16
DD 0
DD DemoStack0Len
DW DemoStack0_Sel,0
DD 0
DW 0,0
DD 0
DW 0,0
DD 0
DW DemoBegin,0
DD 0
DD 0
DD 0
DD 0
DD 0
DD DemoStack2Len
DD 0
DD 0
DD 1986
DW Video_Sel,0
DW DemoCode_Sel,0
DW DemoStack2_Sel,0
DW DemoData_Sel,0
DW ToDLDT_Sel,0
DW ToTTSS_Sel,0
DW DemoLDT_Sel,0
DW 0
DW $+2
DB 0ffh
DemoTSSLen = $
DemoTSSSeg ENDS
DemoStack0Seg SEGMENT PARA USE32
DemoStack0Len = 1024
DB DemoStack0Len DUP(0)
DemoStack0Seg ENDS
DemoStack2Seg SEGMENT PARA USE32
DemoStack2Len = 512
DB DemoStack2Len DUP(0)
DemoStack2Seg ENDS
DemoDataSeg SEGMENT PARA USE32
Message DB 'Value=',0
DemoDataLen = $
DemoDataSeg ENDS
SubRSeg SEGMENT PARA USE32
ASSUME CS:SubRSeg
SubRB PROC FAR
push ebp
mov ebp,esp
pushad
mov esi,DWORD PTR [ebp+12]
mov ah,4ah
jmp SHORT SubR2
SubR1: stosw
SubR2: lodsb
or al,al
jnz SubR1
mov ah,4eh
mov edx,DWORD PTR [ebp+16]
mov ecx,8
SubR3: rol edx,4
mov al,dl
call HToASCII
stosw
loop SubR3
popad
pop ebp
ret 8
SubRB ENDP
HToASCII PROC
and al,0fh
add al,90h
daa
adc al,40h
daa
ret
HToASCII ENDP
SubRLen = $
SubRSeg ENDS
DemoCodeSeg SEGMENT PARA USE32
ASSUME CS:DemoCodeSeg,DS:DemoDataSeg
DemoBegin PROC FAR
mov BYTE PTR fs:ToSubR.DCount,2
push DWORD PTR gs:TempTask.TREIP
push OFFSET Message
CALL32 ToSubR_Sel,0
ASSUME DS:TempTSSSeg
push gs
pop ds
mov ax,Normal_Sel
mov WORD PTR TempTask.TRDS,ax
mov WORD PTR TempTask.TRES,ax
mov WORD PTR TempTask.TRFS,ax
mov WORD PTR TempTask.TRGS,ax
mov WORD PTR TempTask.TRSS,ax
JUMP32 ToTempT_Sel,0
jmp DemoBegin
DemoBegin ENDP
DemoCodeLen = $
DemoCodeSeg ENDS
TempTSSSeg SEGMENT PARA USE16
TempTask TSS <>
DB 0ffh
TempTSSLen = $
TempTSSSeg ENDS
TempCodeSeg SEGMENT PARA USE16
ASSUME CS:TempCodeSeg
Virtual PROC FAR
mov ax,TempTSS_Sel
ltr ax
JUMP16 DemoTSS_Sel,0
clts
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,>
SPVar DW ?
SSVar DW ?
RDataSeg ENDS
RCodeSeg SEGMENT PARA USE16
ASSUME CS:RCodeSeg,DS:RDataSeg,ES:RDataSeg
Start PROC
mov ax,RDataSeg
mov ds,ax
cld
call InitGDT
mov ax,DemoLDTSeg
mov fs,ax
mov si,OFFSET DemoLDT
mov cx,DemoLDNum
call InitLDT
mov SSVar,ss
mov SPVar,sp
lgdt QWORD PTR VGDTR
cli
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
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
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
RCodeSeg ENDS
END Start
3.关于实例五的说明
程序中部分片段的背景和实现方法在前面的实例中做过介绍,下面主要就任务切换和通过调用门实现任务内特权级变换时参数的复制等情形做些说明:
(1)从临时任务直接通过TSS切换到演示任务
从实模式切换到保护模式后,就认为进入了临时任务。但TR并没有指向临时任务的TSS。在从临时任务切换到演示任务时,要把临时任务的现场保存到临时任务的TSS,这就要求TR指向临时任务的TSS。所以首先要使用LTR指令把指向临时任务TSS描述符的选择子装入TR。在利用LTR指令显示地装载TR时,并不引用TSS的内容,所以临时任务的TSS几乎没有初始化。理由是这不是真正的任务切换。
临时任务采用段间转移指令JMP,直接指向演示任务的TSS,切换到演示任务。在执行切换到演示任务的段间转移指令JMP时,CPL=0,JMP指令中所含选择子内的RPL=0,演示任务TSS的描述符特权级DPL=0,并且是一个可用的TSS,所以顺利进行从临时任务到演示任务的切换。切换过程包括:把临时任务的执行现场保存到临时任务的TSS中;从演示任务的TSS中恢复演示任务的现场;把演示任务的LDT描述符选择子装载到LDTR等。从源程序可见,初始化后的演示任务的TSS中CS字段存放的选择子是DemoCode_Sel,对应的描述符在演示任务的LDT中,并且DPL=2,它描述了代码段DemoCode;挂起点是DemoBegin,所以在切换到演示任务后从该点开始执行,并且CPL=2。由于使用JMP指令进行任务切换,所以不实施任务链接。
(2)从演示任务通过任务门切换到临时任务
演示任务采用段间转移指令JMP,通过任务门ToTempT切换到临时任务。在执行切换到临时任务的段间转移指令JMP时,CPL=2,JMP指令中所含选择子ToTempT_Sel内的RPL=0,它指示的任务门的描述符特权级DPL=3,所以可以访问该任务门。任务门内的选择子TempTSS_Sel指示临时任务的TSS,并且此时的临时任务TSS是可用的,所以可顺利进行任务切换。演示任务的现场保存到演示任务的TSS;临时任务的现场从临时任务的TSS恢复。
临时任务的挂起点是临时任务代码段的ToRela点,所以恢复后的临时任务从该点开始,CS含临时任务代码段的选择子。但由于在演示任务内“强硬”地改变了临时任务TSS内的SS和DS等字段,所以在恢复到临时任务时,SS和DS等段寄存器内已含有规范数据段的选择子,而非挂起时的原有值。注意,这种做法不被提倡,但在这里却充分地展示了如何从TSS恢复任务。
(3)演示任务内的特权级变换和堆栈传递参数
演示任务采用段间调用指令CALL,通过调用门ToSubR调用子程序SubRB。执行段间调用指令CALL时的CPL=2,指令所含指向调用门的选择子的RPL=2,调用门的DPL=3,所以对调用门的访问是允许的;尽管调用门内的选择子的RPL=3,但由于它所指示的子程序代码段描述符的DPL=0,所以在调用过程中就发生了从特权级2到特权级0的变换,同时堆栈也被切换。
演示代码段通过堆栈传递了两个参数给子程序SubRB。在把参数压入堆栈时,CPL=2,使用的也是对应特权级2 的堆栈。通过调用门进入子程序后,CPL=0,使用0级堆栈。为此,把调用门ToSubR中的DCount字段设置为2,表示在特权级向内层变换时,需从外层堆栈依次复制2各个双字参数到内层堆栈。随着特权级变换,堆栈也跟着变换。这种在堆栈切换的同时复制所需参数的做法,保证了子程序方便地访问堆栈中的参数,而无需考虑是哪个堆栈。
随着从子程序SubRB的返回,CPL=0变换为CPL=2,堆栈也回到2级堆栈。由于再次进入0级堆栈,总是从空开始,所以在返回前不是非要保持内层堆栈平衡不可。但2级堆栈中的2个双字参数需要废除。从源程序可见,这是采用带立即数的段间返回指令实现的,在返回的同时,自动废除外层堆栈中的参数,同时也废除了内层堆栈中的参数。
(4)别名技术的应用
关于别名技术,前文已经作过介绍。实例五也有两处应用了别名技术。
为了把调用门ToSubR中的DCount字段设置成2,使用一个数据段描述符ToDLDT描述调用门所在演示任务的LDT段,该描述符把演示任务的LDT段描述成数据段。
还有一处是把临时任务的TSS视为普通数据段。从演示任务切换到临时任务之前,把指向描述规范数据段的描述符Normal的选择子Normal_Sel填到临时任务TSS中的各数据段寄存器(包括堆栈段寄存器)字段,于是在切换到临时任务时,作为恢复临时任务的现场,该选择子就被装到DS等数据段寄存器,对应的描述符Normal内的信息也就被装入到对应的高速缓冲寄存器中,达到为从临时任务切换到实模式作准备的目的。
|