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)的值。源程序清單如下:
;名稱:ASM5.ASM
;功能:演示任務切換和任務內有特權級變換的轉移
;編譯:TASM ASM5.ASM
;鏈接:TLINK /32 ASM5.OBJ
;----------------------------------------------------------------------------
INCLUDE 386SCD.INC
;----------------------------------------------------------------------------
GDTSeg SEGMENT PARA USE16 ;全局描述符表數據段(16位)
;----------------------------------------------------------------------------
;全局描述符表
GDT LABEL BYTE
;空描述符
DUMMY Desc <>
;規范段描述符及選擇子
Normal Desc <0ffffh,,,ATDW,,>
Normal_Sel = Normal-GDT
;視頻緩沖區段描述符(DPL=3)及選擇子
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 ;局部描述符表數據段(16位)
;----------------------------------------------------------------------------
DemoLDT LABEL BYTE ;局部描述符表
;0級堆棧段描述符(32位段)及選擇子
DemoStack0 Desc <DemoStack0Len-1,DemoStack0Seg,,ATDW,D32,>
DemoStack0_Sel = DemoStack0-DemoLDT+TIL
;2級堆棧段描述符(32位段)及選擇子
DemoStack2 Desc <DemoStack2Len-1,DemoStack2Seg,,ATDW+DPL2,D32,>
DemoStack2_Sel = DemoStack2-DemoLDT+TIL+RPL2
;演示任務代碼段描述符(32位段,DPL=2)及選擇子
DemoCode Desc <DemoCodeLen-1,DemoCodeSeg,,ATCE+DPL2,D32,>
DemoCode_Sel = DemoCode-DemoLDT+TIL+RPL2
;演示任務數據段描述符(32位段,DPL=3)及選擇子
DemoData Desc <DemoDataLen-1,DemoDataSeg,,ATDW+DPL3,D32,>
DemoData_Sel = DemoData-DemoLDT+TIL
;把LDT作為普通數據段描述的描述符(DPL=2)及選擇子
ToDLDT Desc <DemoLDTLen-1,DemoLDTSeg,,ATDW+DPL2,,>
ToDLDT_Sel = ToDLDT-DemoLDT+TIL
;把TSS作為普通數據段描述的描述符(DPL=2)及選擇子
ToTTSS Desc <TempTSSLen-1,TempTSSSeg,,ATDW+DPL2,,>
ToTTSS_Sel = ToTTSS-DemoLDT+TIL
;----------------------------------------------------------------------------
DemoLDNum = ($-DemoLDT)/(SIZE Desc) ;需處理基地址的LDT描述符數
;----------------------------------------------------------------------------
;指向子程序SubRB代碼段的調用門(DPL=3)及選擇子
ToSubR Gate <SubRB,SubR_Sel,,AT386CGate+DPL3,>
ToSubR_Sel = ToSubR-DemoLDT+TIL+RPL2
;指向臨時任務Temp的任務門(DPL=3)及選擇子
ToTempT Gate <,TempTSS_Sel,,ATTaskGate+DPL3,>
ToTempT_Sel = ToTempT-DemoLDT+TIL
;----------------------------------------------------------------------------
DemoLDTLen = $-DemoLDT
;----------------------------------------------------------------------------
DemoLDTSeg ENDS ;局部描述符表段定義結束
;----------------------------------------------------------------------------
DemoTSSSeg SEGMENT PARA USE16 ;任務狀態段TSS
;----------------------------------------------------------------------------
DD 0 ;鏈接字
DD DemoStack0Len ;0級堆棧指針
DW DemoStack0_Sel,0 ;0級堆棧選擇子
DD 0 ;1級堆棧指針(實例不使用)
DW 0,0 ;1級堆棧選擇子(實例不使用)
DD 0 ;2級堆棧指針
DW 0,0 ;2級堆棧選擇子
DD 0 ;CR3
DW DemoBegin,0 ;EIP
DD 0 ;EFLAGS
DD 0 ;EAX
DD 0 ;ECX
DD 0 ;EDX
DD 0 ;EBX
DD DemoStack2Len ;ESP
DD 0 ;EBP
DD 0 ;ESI
DD 1986 ;EDI
DW Video_Sel,0 ;ES
DW DemoCode_Sel,0 ;CS
DW DemoStack2_Sel,0 ;SS
DW DemoData_Sel,0 ;DS
DW ToDLDT_Sel,0 ;FS
DW ToTTSS_Sel,0 ;GS
DW DemoLDT_Sel,0 ;LDTR
DW 0 ;調試陷阱標志
DW $+2 ;指向I/O許可位圖
DB 0ffh ;I/O許可位圖結束標志
DemoTSSLen = $
;----------------------------------------------------------------------------
DemoTSSSeg ENDS ;任務狀態段TSS結束
;----------------------------------------------------------------------------
DemoStack0Seg SEGMENT PARA USE32 ;演示任務0級堆棧段(32位段)
DemoStack0Len = 1024
DB DemoStack0Len DUP(0)
DemoStack0Seg ENDS ;演示任務0級堆棧段結束
;----------------------------------------------------------------------------
DemoStack2Seg SEGMENT PARA USE32 ;演示任務2級堆棧段(32位段)
DemoStack2Len = 512
DB DemoStack2Len DUP(0)
DemoStack2Seg ENDS ;演示任務2級堆棧段結束
;----------------------------------------------------------------------------
DemoDataSeg SEGMENT PARA USE32 ;演示任務數據段(32位段)
Message DB 'Value=',0
DemoDataLen = $
DemoDataSeg ENDS ;演示任務數據段結束
;----------------------------------------------------------------------------
SubRSeg SEGMENT PARA USE32 ;子程序代碼段(32位)
ASSUME CS:SubRSeg
;----------------------------------------------------------------------------
SubRB PROC FAR
push ebp
mov ebp,esp
pushad ;保護現場
mov esi,DWORD PTR [ebp+12] ;從0級棧中取出顯示串偏移
mov ah,4ah ;設置顯示屬性
jmp SHORT SubR2
SubR1: stosw
SubR2: lodsb
or al,al
jnz SubR1
mov ah,4eh ;設置顯示屬性
mov edx,DWORD PTR [ebp+16] ;從0級棧中取出顯示值
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 ;演示任務的32位代碼段
ASSUME CS:DemoCodeSeg,DS:DemoDataSeg
;----------------------------------------------------------------------------
DemoBegin PROC FAR
;把要復制的參數個數置入調用門
mov BYTE PTR fs:ToSubR.DCount,2
;向2級堆棧中壓入參數
push DWORD PTR gs:TempTask.TREIP
push OFFSET Message
;通過調用門調用SubRB
CALL32 ToSubR_Sel,0
;把指向規范數據段描述符的選擇子填入臨時任務TSS
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 ;演示任務的32位代碼段結束
;----------------------------------------------------------------------------
TempTSSSeg SEGMENT PARA USE16 ;臨時任務的任務狀態段TSS
TempTask TSS <>
DB 0ffh ;I/O許可位圖結束標志
TempTSSLen = $
TempTSSSeg ENDS
;----------------------------------------------------------------------------
TempCodeSeg SEGMENT PARA USE16 ;臨時任務的代碼段
ASSUME CS:TempCodeSeg
;----------------------------------------------------------------------------
Virtual PROC FAR
mov ax,TempTSS_Sel ;裝載TR
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,> ;GDT偽描述符
SPVar DW ? ;用於保存實方式下的SP
SSVar DW ? ;用於保存實方式下的SS
RDataSeg ENDS
;----------------------------------------------------------------------------
RCodeSeg SEGMENT PARA USE16
ASSUME CS:RCodeSeg,DS:RDataSeg,ES:RDataSeg
;----------------------------------------------------------------------------
Start PROC
mov ax,RDataSeg
mov ds,ax
cld
call InitGDT ;初始化全局描述符表GDT
mov ax,DemoLDTSeg
mov fs,ax
mov si,OFFSET DemoLDT
mov cx,DemoLDNum
call InitLDT ;初始化局部描述符表LDT
mov SSVar,ss
mov SPVar,sp
lgdt QWORD PTR VGDTR ;裝載GDTR並切換到保護方式
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
;----------------------------------------------------------------------------
;入口參數: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
;----------------------------------------------------------------------------
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內的信息也就被裝入到對應的高速緩沖寄存器中,達到為從臨時任務切換到實模式作准備的目的。