1. 程式人生 > >06.真實模式進入保護模式

06.真實模式進入保護模式

簡介

上一節我們實現了從核心載入器中載入其它扇區程式碼並執行,但始終工作在真實模式狀態下。記憶體定址方式和8086相同,由16位段暫存器的內容乘以16(10H)當做段基地址,加上16位偏移地址形成20位的實體地址,最大定址空間1MB,最大分段64KB。

保護模式與真實模式相比地址轉換方式差異較大:

真實模式下的地址轉換方式,假設我們在ES中存入0x1000,DI中存入0xFFFF,那麼ES:DI=0x1000*0x10+0xFFFF=0x1FFFF。
保護模式下ES:DI=全域性描述符表中第0x200項描述符(一個描述符8個位元組)給出的段基址+0xFFFF

1、描述符
保護模式下引入描述符來描述各種資料段,所有的描述符均為8個位元組(0-7),由第5個位元組說明描述符的型別,型別不同,描述符的結構也有所不同。若干個描述符集中在一起組成描述符表,而描述符表本身也是一種資料段,也使用描述符進行描述。“地址轉換”由描述符表來完成,描述符表是一張地址轉換函式表。

2、選擇子
選擇子是一個2位元組的數,共16位,最低2位表示RPL,第3位表示查表是利用GDT(全域性描述符表)還是LDT(區域性描述符表)進行,最高13位給出了所需的描述符在描述符表中的地址。(注:13位正好足夠定址8K項)有了以上三個概念之後可以進一步工作了,現在程式的執行與真實模式下完全一樣!各段暫存器仍然給出一個“段值”,只是這個“假段值”到真正的段地址的轉換不再是“左移4位”,而是利用描述符表來完成。

3、80x86系列中引入了兩個新暫存器GDTR和LDTR,其中GDTR用於表示GDT在記憶體中的段地址和段限(就是表的最大偏移上限),因此GDTR是一個48位的暫存器,其中32位表示段地址,16位表示段限。

目標

實現真實模式到保護模式的切換需要使用匯編語言實現,boot.s檔案修改如下

;能用於操作記憶體的暫存器只能是bx、bp、si、di
;0x7c00--0x7dff 這512位元組用於啟動區
;對記憶體的訪問都必須指定段暫存器,沒有顯示指定時將使用ds作為段暫存器


        org 0x7c00
    
        LOAD_ADDR EQU 0x9000   ;核心載入偏移地址
    
        mov ax,0
        mov ss,ax
        mov ds,ax
        mov es,ax 
    
        mov bx,LOAD_ADDR
       
        mov ch,1        ;柱面號
        mov dh,0        ;磁頭號
        mov cl,2        ;扇區號
        mov ah,0x02     ;0x02表示讀盤操作
        mov al,1        ;表示連續讀取扇區數
        mov dl,0        ;驅動器號,早期有多個軟碟機,一般只有一個寫死0
        int 0x13        ;呼叫BIOS實現磁碟讀取
        jc error        ;讀盤操作失敗,flag標誌暫存器cf 標誌位被置1 
        jmp bx          ;跳轉到載入的記憶體地址開始執行程式碼
    
    
    fin:
        hlt
        jmp fin
    
    
    putloop:
        mov al,[si]
        inc si
        cmp al,0
        je fin
        mov ah,0x0e     ;中斷呼叫引數
        mov bx,15       ;字元顏色
        int 0x10        ;中斷呼叫號
        jmp putloop
    
    
    error:
        mov si,errMsg
        jmp putloop
       
    
    errMsg:
        db 'error'
        db 0

kernel.s檔案如下

	;全域性描述符結構 8位元組
    ; byte7 byte6 byte5 byte4 byte3 byte2 byte1 byte0
    ; byte6低四位和 byte1 byte0 表示段偏移上限
    ; byte7 byte4 byte3 byte2 表示段基址
    


    ;定義全域性描述符資料結構
    ;3 表示有3個引數分別用 %1、%2、%3引用引數
    ;%1:段基址     %2:段偏移上限  %3:段屬性
    %macro GDescriptor  3
        dw %2 & 0xffff
        dw %1 & 0xffff
        db (%1>>16) & 0xff 
        dw ((%2>>8) & 0x0f00) | (%3 & 0xf0ff)
        db (%1>>24) & 0xff 
    %endmacro


    DA_32       EQU 0x4000   ; 32 位段
    DA_CODE     EQU 0x98     ; 只執行程式碼段屬性值
    DA_RW       EQU 0x92     ; 可讀寫資料段屬性值


    org 0x9000 
    jmp entry
    
    [SECTION .gdt]
    ;定義全域性描述符                            段基址           段偏移上限       段屬性
    LABEL_GDT:           GDescriptor         0,             0,             0
    LABEL_DESC_CODE:     GDescriptor         0,             SegCodeLen-1,  DA_CODE+DA_32 
    LABEL_DESC_VIDEO:    GDescriptor         0xb8000,       0xffff,        DA_RW


    ;gdt 表大小
    GdtLen  equ     $-LABEL_GDT

    ;gdt表偏移上限和基地址
    GdtPtr  dw      GdtLen-1
            dd      0


    ;cpu開機進入真實模式時使用的段暫存器 cs,ds,es,ss 和偏移地址組成記憶體地址,記憶體地址=段暫存器 * 16 + 偏移地址 
    ;保護模式中段暫存器儲存的是gdt 描述表中各個描述符的偏移,也叫選擇子
    

    SelectorCode32  EQU     LABEL_DESC_CODE-LABEL_GDT
    SelectorVideo   EQU     LABEL_DESC_VIDEO-LABEL_GDT

    [SECTION .s16]
    [BITS 16]
entry:
    mov ax,cs 
    mov ds,ax
    mov es,ax
    mov ss,ax
    mov sp,0x100 

    ;設定LABEL_DESC_CODE描述符段基址
    mov eax,0 
    mov ax,cs 
    shl eax,4
    add eax,SEG_CODE32
    mov word [LABEL_DESC_CODE+2],ax
    shr eax,16
    mov [LABEL_DESC_CODE+4],al
    mov [LABEL_DESC_CODE+7],ah

    mov eax,0
    mov ax,ds
    shl eax,4 
    add eax,LABEL_GDT
    mov dword [GdtPtr+2],eax

    ;設定GDTR 暫存器
    lgdt [GdtPtr]

    cli     ;關閉可可遮蔽中斷,如鍵盤中斷

    in al,0x92 
    or al,0x02
    out 0x92,al 

    mov eax,cr0
    or eax,1 
    mov cr0,eax

    jmp dword SelectorCode32:0


    [SECTION .s32]
    [BITS 32]
SEG_CODE32:
    mov ax,SelectorVideo 
     

    ;gs 暫存器是80386新增的輔助段暫存器
    mov gs,ax

    ;在螢幕中間顯示字串,螢幕為每行80個字元,共25行。低位元組為ascii字元編碼,高位元組為字元顯示屬性
    ;可參考 《組合語言》 王爽,螢幕顯示相關章節

    ;在螢幕11行20列開始顯示字元
    mov ax,(80*11+20)
    mov ecx,2
    mul ecx
    mov edi,eax
    mov ah,00000010b 

    mov si,msg 
    
putloop:
    mov al,[esi]
    cmp al,0
    je  fin
    mov [gs:edi],ax
    add edi,2
    inc esi 
    jmp putloop

fin:
    hlt
    jmp fin

msg:
    db 'protected mode',0


SEG_CODE32_END: nop 



;32為模式程式碼長度
SegCodeLen  EQU SEG_CODE32_END-SEG_CODE32

使用nasm 分別編譯boot.s 、kernel.s 生成boot.bat和kernel.bat

使用C語言軟盤功能模組製作虛擬軟盤,main.c

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

#include "floppy.h"


int main(int argc,char **argv){
    FILE *fp = initFloppy("floppy.img");
    if(fp == NULL) {
        printf("初始化磁碟失敗");
        exit(0);
    }
    
    FILE *src = fopen("boot.bat", "r");
    if(src == NULL) {
        printf("檔案開啟失敗");
        exit(0);
    }
    char buf[512];
    memset(buf, 0, 512);
    fread(buf, 512, 1, src);
    buf[510] = 0x55;
    buf[511] = 0xaa;
    writeFloppy(0, 0, 1, fp, buf);
    fclose(src);
    
    memset(buf, 0, 512);
    src = fopen("kernel.bat", "r");
    if(src == NULL) {
        printf("檔案開啟失敗");
        exit(0);
    }
    fread(buf, 512, 1, src);
    writeFloppy(1, 0, 2, fp, buf);
    fclose(src);
    
    fclose(fp);
    return 1;
}

使用C語言檔案操作boot.bat、kernel.bat,生成floppy.img 虛擬軟盤檔案,VirtualBox虛擬機器載入該軟盤檔案效果如下:
在這裡插入圖片描述

我們成功從真實模式進入保護模式,並在保護模式下實現字元顯示!

補充

在記憶體地址空間中,B8000H~BFFFFH共32KB的空間,為8025彩色字元模式的顯示緩衝區,向這個地址空間寫資料,寫入的內容將立即出現在顯示器上.
在80
25彩色字元模式下,顯示器可以顯示25行,每行80個字元,每個字元可以有256種屬性(背景色,閃爍,高亮等組合資訊).
這樣一個字元在顯示緩衝區中就要佔兩個位元組,分別存放字元的ASCII碼和屬性,80*25模式下,一屛內容在顯示緩衝區中佔4000個位元組.
顯示緩衝區分為8頁,每頁4KB,顯示器可以任意顯示任意一頁的內容,一般情況下,顯示第0頁的內容,也就是B8000~B8F9FH中的4000個位元組.

顏色屬性位元組格式:
7 6 5 4 3 2 1 0
BL R G B I R G B
閃爍 背景(rgb) 高亮 前景(rgb)