自制作業系統(4)進入保護模式
阿新 • • 發佈:2020-11-12
進入保護模式
進入保護模式後才能進行32位定址,進而才有可能寫出GUI介面
整個保護模式非常複雜,這裡一步一步來慢慢說
首先來看一下全域性描述表的一個數據結構:
GDT Description
[BYTE7] [BYTE6(1)] [BYTE6(2)] [BYTE5] [BYTE4 BYTE3 BYTE2] [BYTE1 BYTE0]
這個資料結構用匯編表示如下:
pm.inc
%macro Descriptor 3 dw %2 & 0FFFFh ;段界限1 dw %1 & 0FFFFh ;段基址1 db (%1>>16) & 0FFh ;段基址2 dw ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh) ;屬性1+段界限2+屬性2 db (%1 >> 24) & 0FFh ;段基址3 %endmacro DA_32 EQU 4000h ;32位段 DA_C EQU 98h ;存在的只執行程式碼段屬性值 DA_DRW EQU 92h ;存在的可讀寫資料段屬性值
段界限1如果是0&FFFF=0,如果是ABCD,那麼ABCD&FFFF=ABCD
所以說這裡的&的作用其實並沒有讓這個值發生變化
段基址也是這樣,段基址1沒問題
段基址2這裡00 0B 80 00 00>>16=00 00 00 0B 80
再&FF等於80,正確拿到了段基址2
段界限總共是20位,段界限1是16位,所以段界限2這裡是4位
假如段界限是12345,12345>>8=00123,00123&0F00=00100,成功取到了最高位
屬性這裡也就好理解了,&F0FF說明0這裡做一個清零的操作,因為這裡是段界限2
核心程式碼如下:
kernel.asm
%include "pm.inc" ;引入一個數據結構 org 0x9000 ;從0x9000往後載入 jmp LABEL_BEGIN ;Descriptor是GDT的資料結構 ;CODE32中段界限是計算出的段長 ;VIDEO中顯示卡在記憶體中的對映是0B8000H,段界限是0ffff,屬性表示可讀可寫 [SECTION .gdt] ; 段基址 段界限 屬性 LABEL_GDT: Descriptor 0, 0, 0 LABEL_DESC_CODE32: Descriptor 0, SegCode32Len - 1, DA_C + DA_32 LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW GdtLen equ $ - LABEL_GDT ;當前的地址減去LABEL_GDT,所以GdtLen就是24(3個8位) GdtPtr dw GdtLen - 1 ;表示三個描述符的總長度 dd 0 ;dd是32位4位元組,表示描述符表的起始地址(具體如何計算出在下面的彙編程式碼) SelectorCode32 equ LABEL_DESC_CODE32 - LABEL_GDT ;CODE32相對於GDT的地址偏移,也就是8位元組 SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT ;VIDEO相對於GDT的地址偏移,也就是16位元組 [SECTION .s16] [BITS 16] LABEL_BEGIN: ;初始化一堆暫存器 mov ax, cs mov ds, ax mov es, ax mov ss, ax mov sp, 0100h ;這一段完成了GDT中關於基址地址的初始化 ;真實模式下的定址方式:段暫存器*16+偏移(16位) ;EAX [12 34 AB CD] ;EX [AB CD] ;AL [CD] ;AH [AB] xor eax, eax ;把eax暫存器清零 mov ax, cs ;cs暫存器中儲存的是程式碼段的段基址(ax是eax的低16位) shl eax, 4 ;向左移4位,實現段暫存器*16,也就是ABCD->ABCD0 add eax, LABEL_SEG_CODE32 ;段暫存器*16+偏移得到地址 mov word [LABEL_DESC_CODE32 + 2], ax ;eax的低16位存入LABEL_DESC_CODE32偏移兩位元組 ;也就是寫入了LABEL_DESC_CODE32中的段基址1 shr eax, 16 ;這時候eax的高16位轉移到ax,也就是1234ABCD->00001234 mov byte [LABEL_DESC_CODE32 + 4], al ;把ax的低8位放入LABEL_DESC_CODE32中的段基址2 mov byte [LABEL_DESC_CODE32 + 7], ah ;把ax的高8位放入LABEL_DESC_CODE32中的段基址3 ;到這裡就完整的初始化了一個Descriptor資料結構 xor eax, eax mov ax, ds shl eax, 4 add eax, LABEL_GDT mov dword [GdtPtr + 2], eax ;GdtPtr+2就是上面的dd 0 ;eax就是LABEL_GDT的地址 ;三個資料結構起始的地址放入了dd中 ;由這三個資料結構組成的列表就是全域性描述符表,也就是GDT lgdt [GdtPtr] ;BIOS中斷呼叫,將全域性描述符表載入到CPU中 cli ;關中斷(下面的這些程式碼不能被滑鼠和鍵盤的操作影響) in al, 92h ;得到92h埠的資料 or al, 00000010b ;把第1位置為1 out 92h, al ;然後把資料寫回92h埠 mov eax, cr0 ;從特殊暫存器中讀取值 or eax , 1 ;把最低位的值置1 mov cr0, eax ;然後把資料寫回cr0暫存器 jmp dword SelectorCode32: 0 ;得到Descriptor資料結構並取2347位元組組成完整的地址並JMP ;這裡也是一個偏移定址,dword是4位元組32位 [SECTION .s32] [BITS 32] LABEL_SEG_CODE32: ;上面的JMP跳到了這裡,從這裡開始就是所謂的保護模式 mov ax, SelectorVideo ;把Descriptor資料結構的地址放入ax mov gs, ax ;真實模式的定址方式發生了變化,這裡是拿到Video的Descriptor資料結構地址 ;然後找到該資料結構中由2347位元組構成的基地址(這裡代表視訊記憶體基地址) ;從該地址開始,每2位元組用來顯示一個字元,第1個位元組表示顏色,第2個位元組表示ASCII值 mov si, msg mov ebx, 10 ;從第10列開始顯示字元 mov ecx, 2 ;顯示一個字元需要兩個位元組 showChar: mov edi, (80*11) ;從第11行開始顯示字元 ;1行有80個字元 add edi, ebx ;80*11+10 mov eax, edi mul ecx ;每個字元需要2個位元組,所以最終eax結果是(80*11+10)*2 mov edi, eax ;最終計算得出第11行10列所在的視訊記憶體位置 mov ah, 0ch ;第1個位元組設定顏色 mov al, [si] ;取si暫存器地址指向的值,也就是輸出字串 cmp al, 0 ;如果al是0,那麼進入死迴圈 je end add ebx,1 ;列標號+1 add si, 1 ;指向字元的指標+1(也就是讀取字串的下一個字元) mov [gs:edi], ax ;al和ah一共2位元組,正好放入視訊記憶體地址指向的值 jmp showChar ;迴圈 end: jmp $ msg: DB "Protect Mode", 0 SegCode32Len equ $ - LABEL_SEG_CODE32 ;當前的地址-LABEL_SEG_CODE32程式碼段地址
編譯二進位制檔案再生成system.img成功執行
下面做一個小改變,嘗試申請5M的記憶體,然後在記憶體裡面寫入msg並讀出:
boot_read5M.asm
%include "pm.inc" org 0x7c00 jmp LABEL_BEGIN [SECTION .gdt] ; 段基址 段界限 屬性 LABEL_GDT: Descriptor 0, 0, 0 LABEL_DESC_CODE32: Descriptor 0, SegCode32Len - 1, DA_C + DA_32 LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW LABEL_DESC_5M: Descriptor 0500000h, 0ffffh, DA_DRW ;加入這一行 GdtLen equ $ - LABEL_GDT GdtPtr dw GdtLen - 1 dd 0 SelectorCode32 equ LABEL_DESC_CODE32 - LABEL_GDT SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT Selector5M equ LABEL_DESC_5M - LABEL_GDT ;加入這一行,得到距離GDT的偏移,也就是LABEL_DESC_5M的起始地址 [SECTION .s16] [BITS 16] LABEL_BEGIN: mov ax, cs mov ds, ax mov es, ax mov ss, ax mov sp, 0100h 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_GDT mov dword [GdtPtr + 2], eax lgdt [GdtPtr] cli in al, 92h or al, 00000010b out 92h, al mov eax, cr0 or eax , 1 mov cr0, eax jmp dword SelectorCode32: 0 [SECTION .s32] [BITS 32] LABEL_SEG_CODE32: mov ax, SelectorVideo mov gs, ax mov si, msg mov ax, Selector5M ;用es指向5M記憶體描述符 mov es, ax mov edi, 0 write_msg_to_5M: ;將si指向的字元寫到5M記憶體處 cmp byte [si], 0 ;是否讀完 je prepare_to_show_char ;if(si[i]==0){prepare_to_show_char();} mov al, [si] mov [es:edi], al ;es暫存器是Selector5M的偏移值,進而得到LABEL_DESC_5M的基地址 ;edi=0,所以意思是把al暫存器的值寫入LABEL_DESC_5M的基地址偏移0的地址記憶體中 add edi, 1 ;偏移+1 add si, 1 ;相當於for迴圈的i++ jmp write_msg_to_5M ;迴圈方式 prepare_to_show_char: mov ebx, 10 mov ecx, 2 mov si, 0 showChar: mov edi, (80*11) add edi, ebx mov eax, edi mul ecx mov edi, eax mov ah, 0ch mov al, [es:si] ;由於es指向描述符LABEL_DESC_5M,所以es:si表示的地址是從5M開始的記憶體,si表示從5M開始後的偏移 ;所以這句話可以理解為字串從第一個開始遍歷放入al cmp al, 0 je end ;if(al==0){end();} add ebx,1 add si, 1 mov [gs:edi], ax ;組合ah和al然後放入視訊記憶體中 jmp showChar end: jmp $ msg: DB "This string is writeen to 5M memory", 0 SegCode32Len equ $ - LABEL_SEG_CODE32
nasm kernel.asm -o kernel.bat
nasm boot_read5M.asm -o boot.bat
編譯二進位制檔案再生成system.img成功執行