歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> 關於Linux >> 一個操作系統的實現(3)-保護模式進階

一個操作系統的實現(3)-保護模式進階

日期:2017/3/1 11:51:44   编辑:關於Linux

上節內容是從實模式進入到保護模式,只是進入保護模式打印了一個字母P。但是沒有體現出保護模式的優勢,也沒有從保護模式中返回。這節就是要體驗保護模式下讀寫大地址內存的能力和從保護模式返回到實模式。

這節要做的內容如下:首先在屏幕的第11行輸出In Protect Mode now. ^-^。然後在屏幕第12行輸出內存中起始地址為5MB的連續的8個字節。然後向這個以5MB開始的內存中寫入ABCDEFGH。再次在第13行輸出這8個字節。結果演示如下:

\

源代碼300多行,很長,分段講述,主要講新增的部分

源代碼解釋

首先是GDT段

GDT段

[SECTION .gdt]
; GDT
;                            段基址,        段界限 , 屬性
LABEL_GDT:         Descriptor    0,              0, 0         ; 空描述符
LABEL_DESC_NORMAL: Descriptor    0,         0ffffh, DA_DRW    ; Normal 描述符
LABEL_DESC_CODE32: Descriptor    0, SegCode32Len-1, DA_C+DA_32; 非一致代碼段, 32
LABEL_DESC_CODE16: Descriptor    0,         0ffffh, DA_C      ; 非一致代碼段, 16
LABEL_DESC_DATA:   Descriptor    0,      DataLen-1, DA_DRW    ; Data
LABEL_DESC_STACK:  Descriptor    0,     TopOfStack, DA_DRWA+DA_32; Stack, 32 位
LABEL_DESC_TEST:   Descriptor 0500000h,     0ffffh, DA_DRW
LABEL_DESC_VIDEO:  Descriptor  0B8000h,     0ffffh, DA_DRW    ; 顯存首地址
; GDT 結束

GdtLen        equ    $ - LABEL_GDT    ; GDT長度
GdtPtr        dw    GdtLen - 1    ; GDT界限
        dd    0        ; GDT基地址

; GDT 選擇子
SelectorNormal        equ    LABEL_DESC_NORMAL    - LABEL_GDT
SelectorCode32        equ    LABEL_DESC_CODE32    - LABEL_GDT
SelectorCode16        equ    LABEL_DESC_CODE16    - LABEL_GDT
SelectorData        equ    LABEL_DESC_DATA        - LABEL_GDT
SelectorStack        equ    LABEL_DESC_STACK    - LABEL_GDT
SelectorTest        equ    LABEL_DESC_TEST        - LABEL_GDT
SelectorVideo        equ    LABEL_DESC_VIDEO    - LABEL_GDT
; END of [SECTION .gdt]

相比於上一節,這裡新增了下面幾個描述符表項:
LABEL_DESC_NORMAL:這個描述符用在從保護模式返回實模式的過程,為了讓對應的段描述符告訴緩沖寄存器中含有合適的界限和屬性。這兒有點不明白

LABEL_DESC_CODE16:在保護模式返回到實模式的過程中用到。用來將SeletorNormal選擇子賦給ds、es、fs、gs、ss,跟上面LABEL_DESC_NORMAL合作實現返回實模式的准備工作。在書上43頁

LABEL_DESC_DATA:數據段描述符

LABEL_DESC_STACK:棧段描述符

LABEL_DESC_TEST:用來測試的大地址內存(5MB起始的內存)

自然地,需要增加了相應的選擇子。

接下來新增了數據段。

數據段

[SECTION .data1]     ; 數據段
ALIGN    32
[BITS    32]
LABEL_DATA:
SPValueInRealMode    dw    0
; 字符串
PMMessage:        db    "In Protect Mode now. ^-^", 0    ; 在保護模式中顯示
OffsetPMMessage        equ    PMMessage - $$
StrTest:        db    "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 0
OffsetStrTest        equ    StrTest - $$
DataLen            equ    $ - LABEL_DATA
; END of [SECTION .data1]

對於上面的代碼

SPValueInRealMode:實模式中棧頂指針的值會保存在這裡。干什麼用?

ALIGN 32:這是個偽指令,告訴編譯器本偽指令下面的內存變量必須從下一個能被32整除的地址開始分配。

接下來你會發現定義的兩個數據塊PMMessage和StrTest下面都定義了另外一個符號Offset_PMMessage和OffsetStrTest,用來表示對應的上一個字符串相對於本節開始處(也就是LABEL_DATA處)的偏移。定義這兩個符號是因為在保護模式下需要用到這個偏移來定位字符串的位置。而不再需要實模式下的地址。

你看到GDT中關於數據段的段基址並沒有初始化,所以需要對數據段描述符進行初始化,在[SECTION .s16]中,初始化代碼如下:

        ; 初始化數據段描述符
        xor     eax, eax 
        mov     ax, ds
        shl     eax, 4
        add     eax, LABEL_DATA
        mov     word [LABEL_DESC_DATA + 2], ax
        shr     eax, 16
        mov     byte [LABEL_DESC_DATA + 4], al
        mov     byte [LABEL_DESC_DATA + 7], ah

回顧一下,保護模式下段值仍然是用16位的寄存器來存儲。段值×16+偏移地址在此時它僅僅變成了一個索引,這個索引指向的就是GDT的一個表項,這個表項裡面詳細定義了段的起始地址、界限、屬性等內容。在書的31頁有詳細介紹。

接下來定義了全局堆棧段,因為保護模式下用到了堆棧,這些都是需要我們自己定義的。因此需要在實模式下新建堆棧段。

堆棧段

定義堆棧段的源代碼如下:

; 全局堆棧段
[SECTION .gs]
ALIGN    32
[BITS    32]
LABEL_STACK:
    times 512 db 0

TopOfStack    equ    $ - LABEL_STACK - 1

; END of [SECTION .gs]

這裡定義的棧的大小是512字節,棧頂是TopOfStack。

但計算棧頂的時候減一是為什麼呢?這兒我也沒搞清楚。棧為空時,ss:esp指向的棧的最底部單元下面的單元,這個單元的偏移地址應該為棧最底部的雙字(因為是32位的堆棧段)單元的偏移地址+4。但是這裡我無論怎麼計算都不是在所說的位置,好像棧空間小於512字節了,難道是為了避免訪問越界,減1更有保障?

在[SECTION .s16]有對應的堆棧段描述符如下:

        ; 初始化堆棧段描述符
        xor     eax, eax 
        mov     ax, ds
        shl     eax, 4
        add     eax, LABEL_STACK
        mov     word [LABEL_DESC_STACK + 2], ax
        shr     eax, 16
        mov     byte [LABEL_DESC_STACK + 4], al
        mov     byte [LABEL_DESC_STACK + 7], ah

再看看GDT中的關於堆棧的表項,屬性是DA_DRWA+DA_32。DA_DRWA表示存在的已訪問可讀寫數據段類型值。DA_32表示他是一個32位的堆棧段。

在[SECTION .s32]有對應的堆棧初始化代碼如下:

        mov     ax, SelectorStack
        mov     ss, ax                  ; 堆棧段選擇子

        mov     esp, TopOfStack

關於數據段、棧段定義位置,描述符定義的位置,相應的寄存器初始化位置

從上面可以看到:

數據段,棧段,代碼段的定義是獨立的;

數據段,棧段,32位代碼段描述符的初始化是在16位代碼段中定義的;

數據段,棧段相應寄存器的初始化要看它門在哪個代碼段中使用,比如說這裡的棧段是在32位代碼段中使用的,所以ss,esp寄存器的初始化過程實在32位代碼段中進行的。

16位代碼段(運行在實模式)

這段用來對GDT進行初始化並進入保護模式,詳細的介紹參考上一節文章。

[SECTION .s16]
[BITS    16]
LABEL_BEGIN:
    mov    ax, cs
    mov    ds, ax
    mov    es, ax
    mov    ss, ax
    mov    sp, 0100h

    mov    [LABEL_GO_BACK_TO_REAL+3], ax
    mov    [SPValueInRealMode], sp

    ; 初始化 16 位代碼段描述符
    mov    ax, cs
    movzx    eax, ax
    shl    eax, 4
    add    eax, LABEL_SEG_CODE16
    mov    word [LABEL_DESC_CODE16 + 2], ax
    shr    eax, 16
    mov    byte [LABEL_DESC_CODE16 + 4], al
    mov    byte [LABEL_DESC_CODE16 + 7], ah

    ; 初始化 32 位代碼段描述符
    xor    eax, eax
    mov    ax, cs
    shl    eax, 4
    add    eax, LABEL_SEG_CODE32
    mov    word [LABEL_DESC_CODE32 + 2], ax
    shr    eax, 16
    mov    byte [LABEL_DESC_CODE32 + 4], al
    mov    byte [LABEL_DESC_CODE32 + 7], ah

    ; 初始化數據段描述符
    xor    eax, eax
    mov    ax, ds
    shl    eax, 4
    add    eax, LABEL_DATA
    mov    word [LABEL_DESC_DATA + 2], ax
    shr    eax, 16
    mov    byte [LABEL_DESC_DATA + 4], al
    mov    byte [LABEL_DESC_DATA + 7], ah

    ; 初始化堆棧段描述符
    xor    eax, eax
    mov    ax, ds
    shl    eax, 4
    add    eax, LABEL_STACK
    mov    word [LABEL_DESC_STACK + 2], ax
    shr    eax, 16
    mov    byte [LABEL_DESC_STACK + 4], al
    mov    byte [LABEL_DESC_STACK + 7], ah

    ; 為加載 GDTR 作准備
    xor    eax, eax
    mov    ax, ds
    shl    eax, 4
    add    eax, LABEL_GDT        ; eax <- gdt 基地址
    mov    dword [GdtPtr + 2], eax    ; [GdtPtr + 2] <- gdt 基地址

    ; 加載 GDTR
    lgdt    [GdtPtr]

    ; 關中斷
    cli

    ; 打開地址線A20
    in    al, 92h
    or    al, 00000010b
    out    92h, al

    ; 准備切換到保護模式
    mov    eax, cr0
    or    eax, 1
    mov    cr0, eax

    ; 真正進入保護模式
    jmp    dword SelectorCode32:0    
    ; 執行這一句會把 SelectorCode32 裝入 cs, 並跳轉到 Code32Selector:0  處

mov [LABEL_GO_BACK_TO_REAL+3], ax、mov [SPValueInRealMode], sp:這條指令是干什麼的呢?先告訴你它是為了後面從保護模式返回到實模式用,具體為什麼是這樣在下面有詳細的介紹

程序剛加載就會執行這個代碼段,之後跳到32位代碼段中運行,32位代碼段如下。

32位代碼段(運行在保護模式)

相應的代碼如下。雖然很長,但很簡單,

首先,

是初始化相應段的寄存器。包括數據段、測試段、視頻段(屏幕)、堆棧段

然後,

顯示字符串In Protect Mode now. ^-^

然後,

顯示從內存地址5MB起始的8個字節,然後向內存5MB起始的地址中寫入數據段中的字符串,然後再顯示從內存5MB起始的8個字節。每次顯示完成後都執行換行操作。

最後通過跳轉指令jmp SelectorCode16:0返回到實模式,到這裡32位代碼段就結束了。又回到了實模式。下面的一小節介紹跳轉到的目的地做了什麼工作。

先附上32位代碼段,裡面有詳細的注釋供參考

[SECTION .s32]; 32 位代碼段. 由實模式跳入.
[BITS    32]

LABEL_SEG_CODE32:
    mov    ax, SelectorData
    mov    ds, ax                    ; 數據段選擇子
    mov    ax, SelectorTest
    mov    es, ax                    ; 測試段選擇子
    mov    ax, SelectorVideo
    mov    gs, ax                    ; 視頻段選擇子

    mov    ax, SelectorStack
    mov    ss, ax                    ; 堆棧段選擇子

    mov    esp, TopOfStack


    ; 下面顯示一個字符串
    mov    ah, 0Ch                    ; 0000: 黑底    1100: 紅字
    xor    esi, esi
    xor    edi, edi
    mov    esi, OffsetPMMessage    ; 源數據偏移
    mov    edi, (80 * 10 + 0) * 2    ; 目的數據偏移。屏幕第 10 行, 第 0 列。
    cld                         ; 置標志位DF的置為零
.1:
    lodsb                       ; al=((ds)*16+(esi))、(esi)=(esi)+1
    test    al, al              ; al^al,結果不保存,只影響標志位
    jz    .2                      ; 如果ZF標志位為1,轉移到.2
    mov    [gs:edi], ax            ; 否則,輸出到屏幕
    add    edi, 2                  ; 屏幕偏移寄存器加2
    jmp    .1                      ; 跳轉到.1
.2:                                ; 顯示完畢

    call    DispReturn          ; 換行

    call    TestRead            ; 讀出內存5MB起始的連續8字節內容
    call    TestWrite           ; 向5MB起始的內存中寫入字符串
    call    TestRead            ; 再一次讀出內存5MB起始的連續8字節內容
                                ; 如果正確,讀出的內存與寫入的內容相同

    ; 到此停止
    jmp    SelectorCode16:0        ; 跳轉到准備工作(返回實模式的准備工作代碼)

; ------------------------------------------------------------------------
TestRead:                       ;輸出到屏幕函數
    xor    esi, esi                ;esi置0
    mov    ecx, 8                  ; 向屏幕讀出8個字節
.loop:
    mov    al, [es:esi]            ; 將內存字節內容傳送到al中
    call    DispAL              ; 以16進制的形式顯示出來
    inc    esi                     ; 內存地址增加1
    loop    .loop               ; 繼續循環讀出到屏幕

    call    DispReturn          ; 調用換行函數

    ret                         ; 函數返回
; TestRead 結束-----------------------------------------------------------


; ------------------------------------------------------------------------
TestWrite:                      ; 寫入內存函數
    push    esi                 ; 保存寄存器esi內容
    push    edi                 ; 保存寄存器edi內容
    xor    esi, esi                ; esi置0
    xor    edi, edi                ; edi置0
    mov    esi, OffsetStrTest        ; 源數據偏移
    cld                         ; DF標志位置0
.1:                             ; 因此內存-內存沒有直接通路,所以下面需要al中轉
    lodsb                       ; al=((ds)*16+(esi))、(esi)=(esi)+1
    test    al, al              ; al^al,結果不保存,只影響標志位
    jz    .2                      ; 如果ZF標志位為0,跳轉到.2
    mov    [es:edi], al            ; 將字符寫入內存
    inc    edi                     ; 內存偏移到下一位
    jmp    .1                      ; 循環到寫入的字符串為0時結束
.2:

    pop    edi                     ; 還原子函數用到的寄存器edi、esi,注意順序
    pop    esi

    ret
; TestWrite 結束----------------------------------------------------------


; ------------------------------------------------------------------------
; 以16進制顯示 AL 中的數字
; 默認地:
;    數字已經存在 AL 中
;    edi 始終指向要顯示的下一個字符的位置
; 被改變的寄存器:
;    ax, edi
; ------------------------------------------------------------------------
DispAL:
    push    ecx                 ; 保存子程序中用到的寄存器ecx、edx
    push    edx

    mov    ah, 0Ch                    ; 0000: 黑底    1100: 紅字
    mov    dl, al                  ; al高4位,低4位分開處理
    shr    al, 4                   ; 先處理高四位
    mov    ecx, 2                  ; 循環兩次,第一次高四位,第二次低四位
.begin:
    and    al, 01111b
    cmp    al, 9                   ; 如果大於9,要顯示A~F的ASCii碼
    ja    .1
    add    al, '0'                 ; 如果小於或等於9,顯示0~9的ASCii碼
    jmp    .2
.1:
    sub    al, 0Ah
    add    al, 'A'
.2:
    mov    [gs:edi], ax            ; 輸出到屏幕上
    add    edi, 2                  ; 屏幕偏移指針+2

    mov    al, dl                  ; 處理低4位
    loop    .begin
    add    edi, 2                  ; edi要時刻指向要顯示下一個字符的位置

    pop    edx                     ; 還原子函數用到的寄存器edx、ecx,注意順序
    pop    ecx

    ret                         ; 子程序返回
; DispAL 結束-------------------------------------------------------------


; ------------------------------------------------------------------------
DispReturn:                     ; 實現屏幕輸出的換行
    push    eax                 ; 保存用到的eax、ebx
    push    ebx

    mov    eax, edi                ;
    mov    bl, 160                 ;
    div    bl                      ; 執行結束後,ax存放的是當前行的行號碼
    and    eax, 0FFh               ; 執行結束後,eax存放的是當前行的行號碼
    inc    eax                     ; 讓eax存放下一行的行號碼
    mov    bl, 160                 ;
    mul    bl                      ; 執行結束後,eax存放的是下一行的行首偏移值
    mov    edi, eax                ; 將下一行的行首偏移置傳送到edi中

    pop    ebx                     ; 還原子函數用到的寄存器ebx、eax,注意順序
    pop    eax

    ret                         ; 子程序返回
; DispReturn 結束---------------------------------------------------------

SegCode32Len    equ    $ - LABEL_SEG_CODE32
; END of [SECTION .s32]

從保護模式跳轉到實模式的16位代碼段

從保護模式返回到實模式有點復雜。因為在准備結束保護模式回到實模式之前,需要加載一個合適的描述符選擇子到有關的寄存器,以使對應段描述符高速緩沖寄存器中含有合適的段界限和屬性。而且,我們不能從32位的代碼段返回實模式,只能從16位的代碼段中返回。這是因為無法實現從32位代碼段返回時cs告訴緩沖寄存器中的屬性符合實模式的要求(實模式不能改變屬性)。

所以,在這裡,我們新增一個Normal描述符。在返回實模式之前把對應選擇子SelectorNormal加載到ds、es和ss,就是上面所說的這個原因

下面就來看一下保護模式返回到實模式前用到的16位代碼段:

; 16 位代碼段. 由 32 位代碼段跳入, 跳出後到實模式
[SECTION .s16code]
ALIGN    32
[BITS    16]
LABEL_SEG_CODE16:
    ; 跳回實模式:
    mov    ax, SelectorNormal
    mov    ds, ax
    mov    es, ax
    mov    fs, ax
    mov    gs, ax
    mov    ss, ax

    mov    eax, cr0
    and    al, 11111110b
    mov    cr0, eax

LABEL_GO_BACK_TO_REAL:
    jmp    0:LABEL_REAL_ENTRY    ; 段地址會在程序開始處被設置成正確的值

Code16Len    equ    $ - LABEL_SEG_CODE16

; END of [SECTION .s16code]

這段代碼前面6條mov指令就是上面所說的目的:為了使對應段描述符告訴緩存寄存器中含有合適的段界限和屬性,需要加載一個合適的描述符選擇子到有關寄存器中。這邊我現在也不太了解,先記住吧

接下來的3條指令實現將cr0的PE位(第0位)置零,代表運行(將要運行)在保護模式下。

還記得上面提到的mov [LABEL_GO_BACK_TO_REAL+3], ax指令嗎?上面只是說它要用在從保護模式返回到實模式中,這裡就詳細說一下它為什麼能夠實現這樣的功能:

你看這裡的jmp指令的段地址是0,但是這是程序剛加載到內存中的時候,隨著運行到mov [LABEL_GO_BACK_TO_REAL+3], ax會發生什麼呢?

首先看一下jmp 0:LABEL_REAL_ENTRY的機器碼:

   BYTE1      BYTE2     BYTE3      BYTE4     BYTE5
0EAhoffsetSegment

由上圖可以看出,LABEL_GO_BACK_TO_REAL+3恰好就是Segment的地址,而執行mov [LABEL_GO_BACK_TO_REAL+3], ax之前ax的值已經是實模式下的cs(假設記為cs_real_mode)了,所以它這條mov指令將把cs保存到segment的位置,等到jmp指令執行時,它已經不再是:

jmp    0:LABEL_REAL_ENTRY

而是:

jmp    cs_real_mode:LABEL_REAL_ENTRY

這條指令將會跳轉到標號LABEL_REAL_ENTRY處。現在已經跳回到實模式了,接下來就是要重新設置各個寄存器的值,並回復sp的值,然後關閉A20,打開中斷,重新回到原來的樣子。LABEL_REAL_ENTRY的代碼如下:

LABEL_REAL_ENTRY:        ; 從保護模式跳回到實模式就到了這裡
    mov    ax, cs
    mov    ds, ax
    mov    es, ax
    mov    ss, ax

    mov    sp, [SPValueInRealMode]

    in    al, 92h        ; `.
    and    al, 11111101b    ;  | 關閉 A20 地址線
    out    92h, al        ; /

    sti            ; 開中斷

    mov    ax, 4c00h    ; `.
    int    21h        ; /  回到 DOS
; END of [SECTION .s16]

這裡我們又看到了SPValueInRealMode,還記得上面沒有詳細說的指令mov [SPValueInRealMode], sp嗎?從這而很容易可以看出,它保存實模式下sp的值,也是為了現在回到實模式回復sp的值。

關閉A20地址先,開中斷之後,通過int 21h中斷返回到DOS。

這樣整個程序的運行過程就結束了哈哈。

編譯運行

通過nasm編譯成.com文件,這裡面還有如何突破引導扇區512字節的限制。弄明白了再詳細記錄。

完整源代碼

下面是主程序的完整源代碼


; ==========================================
; pmtest2.asm
; 編譯方法:nasm pmtest2.asm -o pmtest2.com
; ==========================================

%include    "pm.inc"    ; 常量, 宏, 以及一些說明

org    0100h
    jmp    LABEL_BEGIN

[SECTION .gdt]
; GDT
;                            段基址,        段界限 , 屬性
LABEL_GDT:         Descriptor    0,              0, 0         ; 空描述符
LABEL_DESC_NORMAL: Descriptor    0,         0ffffh, DA_DRW    ; Normal 描述符
LABEL_DESC_CODE32: Descriptor    0, SegCode32Len-1, DA_C+DA_32; 非一致代碼段, 32
LABEL_DESC_CODE16: Descriptor    0,         0ffffh, DA_C      ; 非一致代碼段, 16
LABEL_DESC_DATA:   Descriptor    0,      DataLen-1, DA_DRW    ; Data
LABEL_DESC_STACK:  Descriptor    0,     TopOfStack, DA_DRWA+DA_32; Stack, 32 位
LABEL_DESC_TEST:   Descriptor 0500000h,     0ffffh, DA_DRW
LABEL_DESC_VIDEO:  Descriptor  0B8000h,     0ffffh, DA_DRW    ; 顯存首地址
; GDT 結束

GdtLen        equ    $ - LABEL_GDT    ; GDT長度
GdtPtr        dw    GdtLen - 1    ; GDT界限
        dd    0        ; GDT基地址

; GDT 選擇子
SelectorNormal        equ    LABEL_DESC_NORMAL    - LABEL_GDT
SelectorCode32        equ    LABEL_DESC_CODE32    - LABEL_GDT
SelectorCode16        equ    LABEL_DESC_CODE16    - LABEL_GDT
SelectorData        equ    LABEL_DESC_DATA        - LABEL_GDT
SelectorStack        equ    LABEL_DESC_STACK    - LABEL_GDT
SelectorTest        equ    LABEL_DESC_TEST        - LABEL_GDT
SelectorVideo        equ    LABEL_DESC_VIDEO    - LABEL_GDT
; END of [SECTION .gdt]

[SECTION .data1]     ; 數據段
ALIGN    32
[BITS    32]
LABEL_DATA:
SPValueInRealMode    dw    0
; 字符串
PMMessage:        db    "In Protect Mode now. ^-^", 0    ; 在保護模式中顯示
OffsetPMMessage        equ    PMMessage - $$
StrTest:        db    "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 0
OffsetStrTest        equ    StrTest - $$
DataLen            equ    $ - LABEL_DATA
; END of [SECTION .data1]


; 全局堆棧段
[SECTION .gs]
ALIGN    32
[BITS    32]
LABEL_STACK:
    times 512 db 0

TopOfStack    equ    $ - LABEL_STACK - 1

; END of [SECTION .gs]


[SECTION .s16]
[BITS    16]
LABEL_BEGIN:
    mov    ax, cs
    mov    ds, ax
    mov    es, ax
    mov    ss, ax
    mov    sp, 0100h

    mov    [LABEL_GO_BACK_TO_REAL+3], ax
    mov    [SPValueInRealMode], sp

    ; 初始化 16 位代碼段描述符
    mov    ax, cs
    movzx    eax, ax
    shl    eax, 4
    add    eax, LABEL_SEG_CODE16
    mov    word [LABEL_DESC_CODE16 + 2], ax
    shr    eax, 16
    mov    byte [LABEL_DESC_CODE16 + 4], al
    mov    byte [LABEL_DESC_CODE16 + 7], ah

    ; 初始化 32 位代碼段描述符
    xor    eax, eax
    mov    ax, cs
    shl    eax, 4
    add    eax, LABEL_SEG_CODE32
    mov    word [LABEL_DESC_CODE32 + 2], ax
    shr    eax, 16
    mov    byte [LABEL_DESC_CODE32 + 4], al
    mov    byte [LABEL_DESC_CODE32 + 7], ah

    ; 初始化數據段描述符
    xor    eax, eax
    mov    ax, ds
    shl    eax, 4
    add    eax, LABEL_DATA
    mov    word [LABEL_DESC_DATA + 2], ax
    shr    eax, 16
    mov    byte [LABEL_DESC_DATA + 4], al
    mov    byte [LABEL_DESC_DATA + 7], ah

    ; 初始化堆棧段描述符
    xor    eax, eax
    mov    ax, ds
    shl    eax, 4
    add    eax, LABEL_STACK
    mov    word [LABEL_DESC_STACK + 2], ax
    shr    eax, 16
    mov    byte [LABEL_DESC_STACK + 4], al
    mov    byte [LABEL_DESC_STACK + 7], ah

    ; 為加載 GDTR 作准備
    xor    eax, eax
    mov    ax, ds
    shl    eax, 4
    add    eax, LABEL_GDT        ; eax <- gdt 基地址
    mov    dword [GdtPtr + 2], eax    ; [GdtPtr + 2] <- gdt 基地址

    ; 加載 GDTR
    lgdt    [GdtPtr]

    ; 關中斷
    cli

    ; 打開地址線A20
    in    al, 92h
    or    al, 00000010b
    out    92h, al

    ; 准備切換到保護模式
    mov    eax, cr0
    or    eax, 1
    mov    cr0, eax

    ; 真正進入保護模式
    jmp    dword SelectorCode32:0    
    ; 執行這一句會把 SelectorCode32 裝入 cs, 並跳轉到 Code32Selector:0  處

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

LABEL_REAL_ENTRY:        ; 從保護模式跳回到實模式就到了這裡
    mov    ax, cs
    mov    ds, ax
    mov    es, ax
    mov    ss, ax

    mov    sp, [SPValueInRealMode]

    in    al, 92h        ; `.
    and    al, 11111101b    ;  | 關閉 A20 地址線
    out    92h, al        ; /

    sti            ; 開中斷

    mov    ax, 4c00h    ; `.
    int    21h        ; /  回到 DOS
; END of [SECTION .s16]


[SECTION .s32]; 32 位代碼段. 由實模式跳入.
[BITS    32]

LABEL_SEG_CODE32:
    mov    ax, SelectorData
    mov    ds, ax            ; 數據段選擇子
    mov    ax, SelectorTest
    mov    es, ax            ; 測試段選擇子
    mov    ax, SelectorVideo
    mov    gs, ax            ; 視頻段選擇子

    mov    ax, SelectorStack
    mov    ss, ax            ; 堆棧段選擇子

    mov    esp, TopOfStack


    ; 下面顯示一個字符串
    mov    ah, 0Ch            ; 0000: 黑底    1100: 紅字
    xor    esi, esi
    xor    edi, edi
    mov    esi, OffsetPMMessage    ; 源數據偏移
    mov    edi, (80 * 10 + 0) * 2    ; 目的數據偏移。屏幕第 10 行, 第 0 列。
    cld
.1:
    lodsb
    test    al, al
    jz    .2
    mov    [gs:edi], ax
    add    edi, 2
    jmp    .1
.2:    ; 顯示完畢

    call    DispReturn

    call    TestRead
    call    TestWrite
    call    TestRead

    ; 到此停止
    jmp    SelectorCode16:0

; ------------------------------------------------------------------------
TestRead:
    xor    esi, esi
    mov    ecx, 8
.loop:
    mov    al, [es:esi]
    call    DispAL
    inc    esi
    loop    .loop

    call    DispReturn

    ret
; TestRead 結束-----------------------------------------------------------


; ------------------------------------------------------------------------
TestWrite:
    push    esi
    push    edi
    xor    esi, esi
    xor    edi, edi
    mov    esi, OffsetStrTest    ; 源數據偏移
    cld
.1:
    lodsb
    test    al, al
    jz    .2
    mov    [es:edi], al
    inc    edi
    jmp    .1
.2:

    pop    edi
    pop    esi

    ret
; TestWrite 結束----------------------------------------------------------


; ------------------------------------------------------------------------
; 顯示 AL 中的數字
; 默認地:
;    數字已經存在 AL 中
;    edi 始終指向要顯示的下一個字符的位置
; 被改變的寄存器:
;    ax, edi
; ------------------------------------------------------------------------
DispAL:
    push    ecx
    push    edx

    mov    ah, 0Ch            ; 0000: 黑底    1100: 紅字
    mov    dl, al
    shr    al, 4
    mov    ecx, 2
.begin:
    and    al, 01111b
    cmp    al, 9
    ja    .1
    add    al, '0'
    jmp    .2
.1:
    sub    al, 0Ah
    add    al, 'A'
.2:
    mov    [gs:edi], ax
    add    edi, 2

    mov    al, dl
    loop    .begin
    add    edi, 2

    pop    edx
    pop    ecx

    ret
; DispAL 結束-------------------------------------------------------------


; ------------------------------------------------------------------------
DispReturn:
    push    eax
    push    ebx
    mov    eax, edi
    mov    bl, 160
    div    bl
    and    eax, 0FFh
    inc    eax
    mov    bl, 160
    mul    bl
    mov    edi, eax
    pop    ebx
    pop    eax

    ret
; DispReturn 結束---------------------------------------------------------

SegCode32Len    equ    $ - LABEL_SEG_CODE32
; END of [SECTION .s32]


; 16 位代碼段. 由 32 位代碼段跳入, 跳出後到實模式
[SECTION .s16code]
ALIGN    32
[BITS    16]
LABEL_SEG_CODE16:
    ; 跳回實模式:
    mov    ax, SelectorNormal
    mov    ds, ax
    mov    es, ax
    mov    fs, ax
    mov    gs, ax
    mov    ss, ax

    mov    eax, cr0
    and    al, 11111110b
    mov    cr0, eax

LABEL_GO_BACK_TO_REAL:
    jmp    0:LABEL_REAL_ENTRY    ; 段地址會在程序開始處被設置成正確的值

Code16Len    equ    $ - LABEL_SEG_CODE16

; END of [SECTION .s16code]
Copyright © Linux教程網 All Rights Reserved