1. 程式人生 > >真實模式、保護模式、長模式

真實模式、保護模式、長模式

本文介紹兩個實現真實模式與保護模式切換的例項,通過他們說明如何實現真實模式與保護模式的切換,也說明保護模式下的80386及其程式設計
<一>演示真實模式和保護模式切換的例項(例項一)
     例項一的邏輯功能是,以十六進位制數的形式顯示從記憶體地址110000H開始的256個位元組的值。本例項指定該記憶體區域的目的僅僅是想說明切換到保護模式的必要性,因為在真實模式下不能訪問該指定記憶體區域,只有在保護模式下才能訪問到該指定區域。
     本例項的具體實現步驟是:(1)作切換到保護方式的準備;(2)切換到保護方式;(3)把指定記憶體區域的內容傳送到位於常規記憶體的緩衝區中;(4)切換回真實模式;(5)顯示緩衝區內容。
1.包含檔案
     386保護模式組合語言程式用到的包含檔案如下所示,該包含檔案在後面的程式中還要用到。 
;名稱:386SCD.INC
;功能:符號常量等的定義
;----------------------------------------------------------------------------
;IFNDEF __386SCD_INC
;__386SCD_INC EQU 1
;----------------------------------------------------------------------------
.386P
;----------------------------------------------------------------------------
;開啟A20地址線
;----------------------------------------------------------------------------
EnableA20 MACRO
push ax
in al,92h
or al,00000010b
out 92h,al
pop ax
ENDM
;----------------------------------------------------------------------------
;關閉A20地址線
;----------------------------------------------------------------------------
DisableA20 MACRO
push ax
in al,92h
and al,11111101b
out 92h,al
pop ax
ENDM
;----------------------------------------------------------------------------
;16位偏移的段間直接轉移指令的巨集定義(在16位程式碼段中使用)
;----------------------------------------------------------------------------
JUMP16 MACRO Selector,Offset
DB 0eah ;操作碼
DW Offset ;16位偏移量
DW Selector ;段值或段選擇子
ENDM
;----------------------------------------------------------------------------
;32位偏移的段間直接轉移指令的巨集定義(在32位程式碼段中使用)
;----------------------------------------------------------------------------
COMMENT <JUMP32>
JUMP32 MACRO Selector,Offset
DB 0eah ;操作碼
DD OFFSET
DW Selector ;段值或段選擇子
ENDM
<JUMP32>
;-------------------------------------------------
JUMP32 MACRO Selector,Offset
DB 0eah ;操作碼
DW OFFSET
DW 0
DW Selector ;段值或段選擇子
ENDM
;----------------------------------------------------------------------------
;16位偏移的段間呼叫指令的巨集定義(在16位程式碼段中使用)
;----------------------------------------------------------------------------
CALL16 MACRO Selector,Offset
DB 9ah ;操作碼
DW Offset ;16位偏移量
DW Selector ;段值或段選擇子
ENDM
;----------------------------------------------------------------------------
;32位偏移的段間呼叫指令的巨集定義(在32位程式碼段中使用)
;----------------------------------------------------------------------------
COMMENT <CALL32>
CALL32 MACRO Selector,Offset
DB 9ah ;操作碼
DD Offset
DW Selector ;段值或段選擇子
ENDM
<CALL32>
;-------------------------------------------------
CALL32 MACRO Selector,Offset
DB 9ah ;操作碼
DW Offset
DW 0
DW Selector ;段值或段選擇子
ENDM
;----------------------------------------------------------------------------
;儲存段描述符結構型別定義
;----------------------------------------------------------------------------
Desc STRUC
LimitL DW 0 ;段界限(BIT0-15)
BaseL DW 0 ;段基地址(BIT0-15)
BaseM DB 0 ;段基地址(BIT16-23)
Attributes DB 0 ;段屬性
LimitH DB 0 ;段界限(BIT16-19)(含段屬性的高4位)
BaseH DB 0 ;段基地址(BIT24-31)
Desc ENDS
;----------------------------------------------------------------------------
;門描述符結構型別定義
;----------------------------------------------------------------------------
Gate STRUC
OffsetL DW 0 ;32位偏移的低16位
Selector DW 0 ;選擇子
DCount DB 0 ;雙字計數
GType DB 0 ;型別
OffsetH DW 0 ;32位偏移的高16位
Gate ENDS
;----------------------------------------------------------------------------
;偽描述符結構型別定義(用於裝入全域性或中斷描述符表暫存器)
;----------------------------------------------------------------------------
PDesc STRUC
Limit DW 0 ;16位界限
Base DD 0 ;32位基地址
PDesc ENDS
;----------------------------------------------------------------------------
;任務狀態段結構型別定義
;----------------------------------------------------------------------------
TSS STRUC
TRLink DW 0 ;連結欄位
DW 0 ;不使用,置為0
TRESP0 DD 0 ;0級堆疊指標
TRSS0 DW 0 ;0級堆疊段暫存器
DW 0 ;不使用,置為0
TRESP1 DD 0 ;1級堆疊指標
TRSS1 DW 0 ;1級堆疊段暫存器
DW 0 ;不使用,置為0
TRESP2 DD 0 ;2級堆疊指標
TRSS2 DW 0 ;2級堆疊段暫存器
DW 0 ;不使用,置為0
TRCR3 DD 0 ;CR3
TREIP DD 0 ;EIP
TREFlag DD 0 ;EFLAGS
TREAX DD 0 ;EAX
TRECX DD 0 ;ECX
TREDX DD 0 ;EDX
TREBX DD 0 ;EBX
TRESP DD 0 ;ESP
TREBP DD 0 ;EBP
TRESI DD 0 ;ESI
TREDI DD 0 ;EDI
TRES DW 0 ;ES
DW 0 ;不使用,置為0
TRCS DW 0 ;CS
DW 0 ;不使用,置為0
TRSS DW 0 ;SS
DW 0 ;不使用,置為0
TRDS DW 0 ;DS
DW 0 ;不使用,置為0
TRFS DW 0 ;FS
DW 0 ;不使用,置為0
TRGS DW 0 ;GS
DW 0 ;不使用,置為0
TRLDTR DW 0 ;LDTR
DW 0 ;不使用,置為0
TRTrip DW 0 ;除錯陷阱標誌(只用位0)
TRIOMap DW $+2 ;指向I/O許可點陣圖區的段內偏移
TSS ENDS
;----------------------------------------------------------------------------
;儲存段描述符型別值說明
;----------------------------------------------------------------------------
ATDR EQU 90h ;存在的只讀
資料
段型別值
ATDW EQU 92h ;存在的可讀寫資料段屬性值
ATDWA EQU 93h ;存在的已訪問可讀寫資料段型別值
ATCE EQU 98h ;存在的只執行程式碼段屬性值
ATCER EQU 9ah ;存在的可執行可讀程式碼段屬性值
ATCCO EQU 9ch ;存在的只執行一致程式碼段屬性值
ATCCOR EQU 9eh ;存在的可執行可讀一致程式碼段屬性值
;----------------------------------------------------------------------------
;系統段描述符型別值說明
;----------------------------------------------------------------------------
ATLDT EQU 82h ;區域性描述符表段型別值
ATTaskGate EQU 85h ;任務門型別值
AT386TSS EQU 89h ;可用386任務狀態段型別值
AT386CGate EQU 8ch ;386呼叫門型別值
AT386IGate EQU 8eh ;386中斷門型別值
AT386TGate EQU 8fh ;386陷阱門型別值
;----------------------------------------------------------------------------
;DPL值說明
;----------------------------------------------------------------------------
DPL0 EQU 00h ;DPL=0
DPL1 EQU 20h ;DPL=1
DPL2 EQU 40h ;DPL=2
DPL3 EQU 60h ;DPL=3
;----------------------------------------------------------------------------
;RPL值說明
;----------------------------------------------------------------------------
RPL0 EQU 00h ;RPL=0
RPL1 EQU 01h ;RPL=1
RPL2 EQU 02h ;RPL=2
RPL3 EQU 03h ;RPL=3
;----------------------------------------------------------------------------
;IOPL值說明
;----------------------------------------------------------------------------
IOPL0 EQU 0000h ;IOPL=0
IOPL1 EQU 1000h ;IOPL=1
IOPL2 EQU 2000h ;IOPL=2
IOPL3 EQU 3000h ;IOPL=3
;----------------------------------------------------------------------------
;其它常量值說明
;----------------------------------------------------------------------------
D32 EQU 40h ;32位程式碼段標誌
GL EQU 80h ;段界限以4K為單位標誌
TIL EQU 04h ;TI=1(區域性描述符表標誌)
VMFL EQU 00020000h ;VMF=1
VMFLW EQU 0002h
IFL EQU 00000200h ;IF=1
RFL EQU 00010000h ;RF=1(重啟動標誌,為1表示忽略除錯故障)
RFLW EQU 0001h
NTL EQU 00004000h ;NT=1
;----------------------------------------------------------------------------
;分頁機制使用的常量說明
;----------------------------------------------------------------------------
PL EQU 1 ;頁存在屬性位
RWR EQU 0 ;R/W屬性位值,讀/執行
RWW EQU 2 ;R/W屬性位值,讀/寫/執行
USS EQU 0 ;U/S屬性位值,系統級
USU EQU 4 ;U/S屬性位值,使用者級
;----------------------------------------------------------------------------
;ENDIF

2.例項源程式
     例項一的源程式如下所示:
;名稱:ASM1.ASM
;功能:演示實方式和保護方式切換(切換到16位程式碼段)
;----------------------------------------------------------------------------
INCLUDE 386SCD.INC
;----------------------------------------------------------------------------
;字元顯示巨集指令的定義
;----------------------------------------------------------------------------
EchoCh MACRO ascii
mov ah,2
mov dl,ascii
int 21h
ENDM
;----------------------------------------------------------------------------
DSEG SEGMENT USE16 ;16位
資料

;----------------------------------------------------------------------------
GDT LABEL BYTE ;全域性描述符表
DUMMY Desc <> ;空描述符
Code Desc <0ffffh,,,ATCE,,> ;程式碼段描述符
DataS Desc <0ffffh,0,11h,ATDW,,> ;源資料段描述符
DataD Desc <0ffffh,,,ATDW,,> ;目標資料段描述符
;----------------------------------------------------------------------------
GDTLen = $-GDT ;全域性描述符表長度
VGDTR PDesc <GDTLen-1,> ;偽描述符
;----------------------------------------------------------------------------
Code_Sel = Code-GDT ;程式碼段選擇子
DataS_Sel = Datas-GDT ;源
資料
段選擇子
DataD_Sel = DataD-GDT ;目標資料段選擇子
;----------------------------------------------------------------------------
BufLen = 256 ;緩衝區位元組長度
Buffer DB BufLen DUP(0) ;緩衝區
;----------------------------------------------------------------------------
DSEG ENDS ;資料段定義結束
;----------------------------------------------------------------------------
CSEG SEGMENT USE16 ;16位程式碼段
ASSUME CS:CSEG,DS:DSEG
;----------------------------------------------------------------------------
Start PROC
mov ax,DSEG
mov ds,ax
;準備要載入到GDTR的偽描述符
mov bx,16
mul bx
add ax,OFFSET GDT ;計算並設定基地址
adc dx,0 ;界限已在定義時設定好
mov WORD PTR VGDTR.Base,ax
mov WORD PTR VGDTR.Base+2,dx
;設定程式碼段描述符
mov ax,cs
mul bx
mov WORD PTR Code.BaseL,ax ;程式碼段開始偏移為0
mov BYTE PTR Code.BaseM,dl ;程式碼段界限已在定義時設定好
mov BYTE PTR Code.BaseH,dh
;設定目標資料段描述符
mov ax,ds
mul bx ;計算並設定目標資料段基址
add ax,OFFSET Buffer
adc dx,0
mov WORD PTR DataD.BaseL,ax
mov BYTE PTR DataD.BaseM,dl
mov BYTE PTR DataD.BaseH,dh
;載入GDTR
lgdt QWORD PTR VGDTR
cli ;關中斷
EnableA20 ;開啟地址線A20
;切換到保護方式
mov eax,cr0
or eax,1
mov cr0,eax
;清指令預取佇列,並真正進入保護方式
JUMP16 Code_Sel,<OFFSET Virtual>
Virtual: ;現在開始在保護方式下執行
mov ax,DataS_Sel
mov ds,ax ;載入源資料段描述符
mov ax,DataD_Sel
mov es,ax ;載入目標資料段描述符
cld
xor si,si
xor di,di ;設定指標初值
mov cx,BufLen/4 ;設定4位元組為單位的緩衝區長度
repz movsd ;傳送
;切換回真實模式
mov eax,cr0
and al,11111110b
mov cr0,eax
;清指令預取佇列,進入實方式
JUMP16 <SEG Real>,<OFFSET Real>
Real: ;現在又回到實方式
DisableA20
sti
mov ax,DSEG
mov ds,ax
mov si,OFFSET Buffer
cld
mov bp,BufLen/16
NextLine: mov cx,16
NextCh: lodsb
push ax
shr al,1
call ToASCII
EchoCh al
pop ax
call ToASCII
EchoCh al
EchoCh ' '
loop NextCh
EchoCh 0dh
EchoCh 0ah
dec bp
jnz NextLine
mov ax,4c00h
int 21h
Start ENDP
;----------------------------------------------------------------------------
ToASCII PROC
and al,0fh
add al,90h
daa
adc al,40h
daa
ret
ToASCII ENDP
;----------------------------------------------------------------------------
CSEG ENDS ;程式碼段定義結束
;----------------------------------------------------------------------------
END Start

3.關於例項步驟的註釋
     在源程式的開頭首先包含了檔案“386SCD.INC”,在此包含檔案中定義了保護模式程式設計要用到的一些結構、巨集及常量。下面對各實現步驟作些說明。
     (1)切換到保護方式的準備工作
     在從真實模式切換到保護模式之前,必須作必要的準備。準備工作的內容根據實際而定。最起碼的準備工作是建立合適的全域性描述符表,並使用GDTR指向該GDT。因為在切換到保護方式時,至少要把程式碼段的選擇子裝載到CS,所以GDT中至少含有程式碼段的描述符。
     從本例項源程式可見,全域性描述符表GDT僅有四個描述符:第一個是空描述符;第二個是程式碼段描述符;第三個和第四個分別為源資料段及目標資料段描述符。本例項各描述符中的段界限是在定義時設定的,並且除偽描述符VGDTR中的界限按GDT的實際長度設定外,各使用的儲存段描述符的界限都規定為0FFFFH。另外,描述符中的段屬性也根據所描述段的型別被預置,各屬性的定義在包含檔案386SCD.INC中均有說明。從屬性值可知,這三個段都是16位段。
     由於在切換到保護方式後就要引用GDT,所以在切換到保護方式前必須裝載GDTR。例項中使用如下指令裝載GDTR:
     LGDT QWORD PTR VGDTR
     該指令的功能是把儲存器中的偽描述符VGDTR裝入到全域性描述符表暫存器GDTR中。偽描述符VGDTR的結構如前所述結構型別PDESC所示,低字是以位元組位單位的全域性描述符表段的界限,高雙字為描述符表段的線性基地址(本例項不啟用分頁機制,所以線性地址等同於實體地址)。本例項中未涉及到區域性描述符表及中斷描述符表,後面的文章將作詳細說明。
     (2)由真實模式切換到保護模式
     在做好準備後,從真實模式切換到保護模式並不難。原則上只要把控制暫存器CR0中的PE位置1即可。本例項採用如下三條指令設定PE位: 
     mov eax,cr0
     or eax,1
     mov cr0,eax
     實際情況要比這複雜些。執行上面的三條指令後,處理器轉入保護模式,但CS中的內容還是真實模式下程式碼段的段值,而不是保護模式下程式碼段的選擇子,所以在取指令之前得把程式碼段的選擇子裝入CS。為此,緊接著這三條指令,安排一條如下所示的段間轉移指令:
     JUMP16 Code_Sel ,< OFFSET Virtual >
     這條段間轉移指令 在真實模式下被預取並在保護方式下被執行 。利用這條段間轉移指令可把保護模式下程式碼段的選擇子裝入CS,同時也重新整理指令預取佇列。從此真正進入保護模式。
     (3)由保護模式切換到真實模式 
     在80386上,從保護模式切換到真實模式的過程類似於從真實模式切換到保護模式。原則上只要把控制暫存器CR0中的PE位清0即可。實際上,在此之後也要安排一條段間轉移指令,一方面清指令預取佇列,另一方面把真實模式下程式碼段的段值送CS。 這條段間轉移指令在保護方式下被預取並在真實模式下被執行 。
     (4)保護模式下的資料傳送 
     首先,把源資料段和目標資料段的選擇子裝入DS和ES暫存器,這兩個描述符已在真實模式下設定好,把選擇子裝入段暫存器就意味著把包括基地址在內的段資訊裝入到了段描述符高速緩衝暫存器。然後設定指標暫存器SI和DI的初值,也設定計數器CX的初值。根據預置的段屬性,在保護方式下,程式碼段也僅是16位段,串操作指令只使用16位的SI、DI和CX等暫存器。最後利用串操作指令實施傳送。
     (5)顯示緩衝區中的內容
     由於緩衝區在常規記憶體中,所以在真實模式下根據要求按十六進位制顯示其內容是很容易理解的,這裡就不再多說。
     4.記憶體映象 
     在源程式中沒有把GDT作為一個單獨的段對待,但在進入保護方式後,它是一個獨立的段。從對程式碼段和源資料段描述符所賦的基地址和段界限值可見,程式碼段和資料段有部分覆蓋。儘管這樣做不利於程式碼和資料的安全,但如果需要,這樣做是可行的。本例項執行時的記憶體映象如下圖所示。
                             
      5.特別說明
      作為第一個真實模式和保護模式切換的例子,本例項作了大量的簡化處理。 
      通常,由真實模式切換到保護模式的準備工作還應包含建立中斷描述符表。但本例項沒有建立中斷描述符表。為此,要求整個過程在關中斷的情況下進行;要求不使用軟中斷指令;假設不發生任何異常。否則會導致系統崩潰。 
      本例項未使用區域性描述符表,所以在進入保護模式後沒有設定區域性描述符表暫存器LDTR。為此,在保護模式下使用的段選擇子都指定GDT中的描述符。 
      本例項未定義保護模式下的堆疊段,GDT中沒有堆疊段描述符,在保護模式下沒有設定SS,所以在保護方式下沒有涉及堆疊操作的指令。 
      本例項各描述符特權級DPL和各選擇子的請求特權級RPL均為0,在保護方式下執行時的當前特權級CPL也是0。 
      本例項沒有采用分頁管理機制,也即CR0中的PG位為0,線性地址就是儲存單元的實體地址。 
      6.開啟和關閉地址線A20
      PC及其相容機的第21根地址線(A20)較特殊,計算機系統中一般安排一個 “門”控制該地址線是否有效。為了訪問地址在1M以上的儲存單元,應先開啟控制地址線A20的“門”。這種設定與真實模式下只使用最低端的1M位元組儲存空間有關,與處理器是否工作在真實模式或保護方式無關,即使在關閉地址線A20時,也可進入保護模式。 
      如何開啟和關閉地址線A20與計算機系統的具體設定有關。在本文中介紹的包含檔案386SCD.INC中定義了兩個巨集,開啟地址線A20的巨集EnableA20和關閉地址線A20的巨集DisableA20,此兩個巨集指令在一般的PC相容機上都是可行的。
<二>演示32位程式碼段和16位程式碼段切換的例項(例項二) 
     例項二的邏輯功能是,以十六進位制數和ASCII字元兩種形式顯示從記憶體地址100000H開始的16個位元組的內容。
     從功能上看,本例項類似於例項一,但在實現方法上卻有了改變,它更能反映出真實模式和保護模式切換的情況。具體實現步驟是:(1)作切換到保護方式的準備;(2)切換到保護方式的一個32位程式碼段;(3)把指定記憶體區域的內容以位元組為單位,轉換成對應的十六進位制數的ASCII碼,並直接填入顯示緩衝區實現顯示;(4)再變換到保護方式下的一個16位程式碼段;(5)把指定記憶體區域的內容直接作為ASCII碼填入顯示緩衝區中實現顯示;(6)切換回真實模式。
     1.例項二源程式
     例項二的源程式如下所示: 
;名稱:ASM2.ASM
;功能:演示實方式和保護方式切換(切換到32位程式碼段)
;----------------------------------------------------------------------------
INCLUDE 386SCD.INC
;----------------------------------------------------------------------------
DSEG SEGMENT USE16 ;16位資料
;----------------------------------------------------------------------------
GDT LABEL BYTE ;全域性描述符表
DUMMY Desc <> ;空描述符
Normal Desc <0ffffh,,,ATDW,,> ;規範段描述符
Code32 Desc <C32Len-1,,,ATCE,D32,> ;32位程式碼段描述符
Code16 Desc <0ffffh,,,ATCE,,> ;16位程式碼段描述符
DataS Desc <DataLen-1,0,10h,ATDR,,> ;源資料段描述符
DataD Desc <3999,8000h,0bh,ATDW,,> ;顯示緩衝區描述符
Stacks Desc <StackLen-1,,,ATDW,,> ;堆疊段描述符
;----------------------------------------------------------------------------
GDTLen = $-GDT ;全域性描述符表長度
VGDTR PDesc <GDTLen-1,> ;偽描述符
;----------------------------------------------------------------------------
SaveSP DW ? ;用於儲存SP暫存器
SaveSS DW ? ;用於儲存SS暫存器
;----------------------------------------------------------------------------
Normal_Sel = Normal-GDT ;規範段描述符選擇子
Code32_Sel = Code32-GDT ;32位程式碼段選擇子
Code16_Sel = Code16-GDT ;16位程式碼段選擇子
DataS_Sel = Datas-GDT ;源資料段選擇子
DataD_Sel = DataD-GDT ;目標資料段選擇子
Stacks_Sel = Stacks-GDT ;堆疊段描述符選擇子
;----------------------------------------------------------------------------
DataLen = 16
;----------------------------------------------------------------------------
DSEG ENDS ;資料段定義結束
;----------------------------------------------------------------------------
StackSeg SEGMENT PARA STACK USE16
StackLen = 256
DB StackLen DUP(0)
StackSeg ENDS
;----------------------------------------------------------------------------
CSEG1 SEGMENT USE16 'REAL' ;16位程式碼段
ASSUME CS:CSEG1,DS:DSEG
;----------------------------------------------------------------------------
Start PROC
mov ax,DSEG
mov ds,ax
;準備要載入到GDTR的偽描述符
mov bx,16
mul bx
add ax,OFFSET GDT ;計算並設定基地址
adc dx,0 ;界限已在定義時設定好
mov WORD PTR VGDTR.Base,ax
mov WORD PTR VGDTR.Base+2,dx
;設定32位程式碼段描述符
mov ax,CSEG2
mul bx
mov WORD PTR Code32.BaseL,ax
mov BYTE PTR Code32.BaseM,dl
mov BYTE PTR Code32.BaseH,dh
;設定16位程式碼段描述符
mov ax,CSEG3
mul bx
mov WORD PTR Code16.BaseL,ax ;程式碼段開始偏移為0
mov BYTE PTR Code16.BaseM,dl ;程式碼段界限已在定義時設定好
mov BYTE PTR Code16.BaseH,dh
;設定堆疊段描述符
mov ax,ss
mov WORD PTR SaveSS,ax
mov WORD PTR SaveSP,sp
mov ax,StackSeg
mul bx
mov WORD PTR Stacks.BaseL,ax
mov BYTE PTR Stacks.BaseM,dl
mov BYTE PTR Stacks.BaseH,dh
;載入GDTR
lgdt QWORD PTR VGDTR
cli ;關中斷
EnableA20 ;開啟地址線A20
;切換到保護方式
mov eax,cr0
or al,1
mov cr0,eax
;清指令預取佇列,並真正進入保護方式
JUMP16 Code32_Sel,<OFFSET SPM32>
ToReal: ;現在又回到實方式
mov ax,DSEG
mov ds,ax
mov sp,SaveSP
mov ss,SaveSS
DisableA20
sti
mov ax,4c00h
int 21h
Start ENDP
;----------------------------------------------------------------------------
CSEG1 ENDS ;程式碼段定義結束
;----------------------------------------------------------------------------
CSEG2 SEGMENT USE32 'PM32'
ASSUME CS:CSEG2
;----------------------------------------------------------------------------
SPM32 PROC
mov ax,Stacks_Sel
mov ss,ax
mov esp,StackLen
mov ax,DataS_Sel
mov ds,ax
mov ax,DataD_Sel
mov es,ax
xor esi,esi
xor edi,edi
mov ecx,DataLen
cld
Next: lodsb
push ax
CALL ToASCII
mov ah,7
shl eax,16
pop ax
shr al,4
CALL ToASCII
mov ah,7
stosd
mov al,20h
stosw
loop Next
JUMP32 Code16_Sel,<OFFSET SPM16>
SPM32 ENDP
;----------------------------------------------------------------------------
ToASCII PROC
and al,00001111b
add al,30h
cmp al,39h
jbe Isdig
add al,7
IsDig: ret
ToASCII ENDP
;----------------------------------------------------------------------------
C32Len = $
;----------------------------------------------------------------------------
CSEG2 ENDS
;----------------------------------------------------------------------------
CSEG3 SEGMENT USE16 'PM16'
ASSUME CS:CSEG3
;----------------------------------------------------------------------------
SPM16 PROC
xor si,si
mov di,DataLen*3*2
mov ah,7
mov cx,DataLen
AGain: lodsb
stosw
loop AGain
mov ax,Normal_sel
mov ds,ax
mov es,ax
mov ss,ax
mov eax,cr0
and al,11111110b
mov cr0,eax
jmp FAR PTR ToReal
SPM16 ENDP
;----------------------------------------------------------------------------
CSEG3 ENDS
;----------------------------------------------------------------------------
END Start

2.關於實現步驟的註釋
    (1)切換到保護模式的準備工作 
     建立全域性描述符表,這裡的全域性描述符表含有兩個16位資料段的描述符、一個16位程式碼段的描述符和一個16位的堆疊段描述符。此外,GDT中還有一個32位的程式碼段描述符,描述32位程式碼段,該描述符的屬性欄位中的D位為1。 
    (2)由真實模式切換到保護模式
     由真實模式切換到保護模式32位程式碼段的方法與切換到16位程式碼段的方法相同。由保護模式16位程式碼段切換回真實模式的方法與例項一相似。
     在保護模式下,通過如下直接段間轉移指令從32位程式碼段切換到16位程式碼段:
     JUMP32 Code16_Sel ,< OFFSET SPM16 > 
     從該巨集指令的定義可知,該轉移指令含48位指標,其高16位是16位程式碼段的選擇子,低32位是16位程式碼段的入口偏移。 該指令在32位方式下預取並執行 。由於在32位方式下執行,所以要使用48位指標。
    (3)顯示指定記憶體區域的內容
     在本例項中,採用直接寫顯示緩衝區的方法實現顯示。假設顯示緩衝區的開始實體地址是0B8000H, 3號文字顯示模式,在螢幕的第一行進行顯示。
     3.特別說明
     本例項在保護方式下使用了涉及堆疊操作的指令,因此建立了一個16位的保護模式下的堆疊段。
     本例項仍作了大量的簡化處理。如:沒有建立IDT和LDT等,各特權級均是0。也沒有采用分頁管理機制。 
     從本例項的GDT中可見,兩個資料段的界限都是根據實際大小而設定的。從源程式程式碼段CSEG3可見,在切換到真實模式之前,把一個指向似乎沒有用的資料段的描述符Normal的選擇子裝載到DS和ES。這是為什麼呢? 
實模
式下
段描
述符
高速
緩衝
寄存
器的
內容
段暫存器 段基地址 段界限(固定) 段屬性(固定)
存在性 特權級 已存取 粒度 擴充套件方向 可讀性 可寫性 可執行 堆疊大小 一致特權
CS 當前CS*16 0000FFFFH Y 0 Y B U Y Y Y - N
SS 當前SS*16 0000FFFFH Y 0 Y B U Y Y N W -
DS 當前DS*16 0000FFFFH Y 0 Y B U Y Y N - -
ES 當前ES*16 0000FFFFH Y 0 Y B U Y Y N - -
FS 當前FS*16 0000FFFFH Y 0 Y B U Y Y N - -
GS 當前GS*16 0000FFFFH Y 0 Y B U Y Y N - -
     在分段管理機制一文中已介紹過,每個段暫存器都配有段描述符高速緩衝暫存器,這些高速緩衝暫存器在實方式下仍發揮作用,只是內容上與保護模式下有所不同。如上表所示,其中“Y”表示“是”; “N”表示“否”;“B”表示位元組;“U”表示向上擴充套件,“W”表示以字方式操作堆疊。段基地址仍是 32位,其值是相應段暫存器值(段值)乘以16,在把段值裝載到段暫存器時重新整理。由於其值是16位段值乘上16,所以在真實模式下基地址實際上有效位只有20位。每個段的32位段界限都固定為0FFFFH,段屬性的許多位也是固定的。所謂固定是指在實方式下不可設定這些屬性值,只能繼續沿用保護方式下所設定的值。因此,在準備結束保護模式回到真實模式之前,要通過載入一個合適的描述符選擇子到有關段暫存器,以使得對應段描述符高速緩衝暫存器中含有合適的段界限和屬性。本例項GDT中的描述符Normal就是這樣一個描述符,在返回真實模式之前把對應選擇子Normal_Sel載入到DS和ES就是此目的。由於SS段描述符中的內容已符合真實模式的需要,所以儘管也改變了SS,但不需要重新載入SS(本例項中重新載入了SS,這除了稍增加執行時間外,並沒有什麼壞處)。16位程式碼段描述符中的內容也符合真實模式的需要,所以在通過16位程式碼段返回真實模式時,CS段描述符中的內容也符合真實模式的要求。需要注意的是,不能從32位程式碼段返回真實模式,這是因為無法實現從32位程式碼段返回時CS高速緩衝暫存器中的屬性符合真實模式的要求(真實模式不能改變段屬性)。順便說以下,例項一中的描述符都是符合真實模式要求的。段描述符高速緩衝暫存器中含有合適的段界限
4.關於32位程式碼段程式設計的說明
     在32位程式碼段中,預設的運算元大小是32位,預設的儲存單元地址大小是32位。由於串操作指令使用的指標暫存器是ESI和EDI,LOOP指令使用的計數器是ECX,所以,在程式碼段CSEG2中,為了使用串操作指令,對ESI和EDI等暫存器賦初值。請比較程式碼段CSEG3中的相關片段和例項一中的相關片段,它們是16位程式碼段。