80x86保護模式系列教程(4)實模式與保護模式切換實例

轉自:http://www.chinaitpower.com/A/2001-12-10/7366.html

本文介紹兩個實現實模式與保護模式切換的實例,通過他們說明如何實現實模式與保護模式的切換, 也說明保護模式下的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位代碼段。


 

參考資料 書        名 出  版  社 作    者
《保護方式下的80386及其編程》 清華大學出版社 周明德主編
《80X86彙編語言程序設計教程》 清華大學出版社 揚季文主編




 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章