保護模式的尋址方式不在使用寄存器分段的方式直接尋址方式了。而采用的是使用GDT(全局分段描述表)來尋址。進而使用更多的記憶體位址
從實模式到保護模式,是計算法技術跨時代的發展。大家想想笨拙的Dos界面,黑底白字的那種冷漠界面到win95各種色彩斑斓的視窗,兩者之間的差別其實就是實模式和保護模式的天壤之别
作業系統核心真正要發揮功能的話,必須進入保護模式,進而實作32位的尋址。之前我們一直是處于實模式,實模式尋址隻有16位,最多就尋址到1M左右的記憶體
從實模式到保護模式,是計算法技術跨時代的發展。從笨拙的Dos界面,黑底白字的那種冷漠界面到win95各種色彩斑斓的視窗,兩者之間的差別其實就是實模式和保護模式的天壤之别。
保護模式中,最重要的一個概念莫過于”保護”二字,有了“保護”功能後,CPU為軟體提供了很多的功能,當然也有了更多的限制
注意,實模式和保護模式是記憶體的兩種模式
核心态與使用者态是作業系統的兩種運作級别,跟intel cpu沒有必然的聯系
進入保護模式後,還有一個巨大的好處是,我們可以引入C語言來開發核心
實模式下的尋址方式
段寄存器*16+偏移(16bit)保護模式下分段機制的記憶體尋址
保護模式下 分段機制是利用一個稱作段選擇符的偏移量,進而到描述符表找到需要的段描述符,而這個段描述符中就存放着真正的段的實體首位址,再加上偏移量保護模式的實作
我們要了解什麼叫保護模式,先看保護模式的兩個顯著特點:
①尋址空間從實模式的1M增強到4G
②不同的代碼擁有不同的優先級,優先級高的能夠執行特殊指令,優先級低的,某些重要指令就無法執行。
于是進入保護模式,我們需要解決兩個問題,一是如何擷取超過1M以上的記憶體位址,第二是如何設定不同代碼所具有的優先級。
我們先看看尋址能力的變化,在實模式下,cpu是16位的,寄存器16位,資料總線16位,位址總線20位,于是尋找的範圍必然受限于20位的位址總線,是以尋找範圍無法超過1M(2^20). 要想實作4GB的尋址,我們必須使用32位來表示位址,intel是這麼解決這個問題的,他們用連續的8個位元組組成的結構體來解決一系列問題:
byte0
byte1
…..
byte7
其中,位元組2,3,4以及位元組7,這四個位元組合在一起總共有32位,這就形成了一個32位的位址(234是低位,7是高位)。同時把位元組0,位元組1,以及将位元組6的拆成兩部分,各4個bits,前4個bits跟位元組0,位元組1合在一起,形成一個20個bit的資料(1M大小),用來表示要通路的記憶體長度,也就是段長度。這樣,我們就解決了記憶體尋址的問題。
byte5是用來标記屬性的
由于保護模式是一個非常複雜的邏輯,為了掌握它,我們一次隻吃透一點,這樣才好掌握,這裡我們首先了解如何進行32位的尋址
pm.inc
彙編語言的頭檔案
%macro Descriptor 3 dw %2 & 0FFFFh ;byte1和byte0 dw %1 & 0FFFFh ;byte3和byte2 db (%1>>16) & 0FFh dw ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh) db (%1 >> 24) & 0FFh %endmacro DA_32 EQU 4000h ; 32 位段 DA_C EQU 98h ; 存在的隻執行代碼段屬性值 DA_DRW EQU 92h ; 存在的可讀寫資料段屬性值
pm.inc裡面的宏定義就是我們說的7位元組資料結構,
%macro Descriptor 3
表示要初始化該資料結構,需要傳入3個參數,%1表示引用第一個參數,%2表示引用第二個參數。初始化該結構時,輸入的一個參數是記憶體的位址。
大家看語句:
dw %1 & 0FFFFh
db (%1>>16) & 0FFh
這兩句就是把記憶體位址的頭三個位元組放入到byte2,byte3,byte4
db (%1 >> 24) & 0FFh
就是講位址的第4個位元組放入到byte7. 初始化資料結構的第二個參數表示的是要通路的記憶體的長度
dw %2 & 0FFFFh
就是把記憶體長度的頭兩個位元組寫入byte0,byte1
dw ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh)
中的((%2 >> 8) & 0F00h)就是把記憶體長度的第16-19bit寫入到byte6的前4個bit.
由此要通路的記憶體和記憶體的長度就都設定好了
這個GDT.java和pm.inc是對應的,都是描述GDT全局描述符表,隻不過用java寫起來更直覺好看一點
kernel.asm中的descriptor的資料結構就類似下面的程式
但我們今天實作的時候并沒有用到GDT.java這段代碼,隻是展示一下descriptor的結構
/*|BYTE7|BYTE6I|BYTE5|BYTE4|BYTE3|BYTE2|BYTE1|BYTE0 */ public class GDT{ byte[] segmentLength_low = new byte[2];//BYTE1|BYTE0 */ byte[] baseAddressLow = new byte[3]; //BYTE2|BYTE3|BYTE4 byte[] attribute = new byte[2]; //BYTE5|BYTE6 //BYTE6的頭4個bit,用于segment length,于是段長度的字長為20bit byte addressHigh;//BYTE7 //實模式下的尋址方式 //段态存器*16+偏移(16bit) }
kernel.asm
kernel.asm生成kernel.bin
其中會引用pm.inc
%include "pm.inc" ;定義了一個資料結構 org 0x9000 jmp LABEL_BEGIN [SECTION .gdt] ;全局描述符表,用來描述一段記憶體資料的屬性,加載到CPU之後,CPU就會通過它去通路資料 ;這裡面用的Descriptor資料結構是在pm.inc裡面定義的 ; 段基址 段界限 屬性 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 ;目前的位址-開頭 = 24, 一個descriptor八位元組,三個共24位元組 GdtPtr dw GdtLen - 1 dd 0 SelectorCode32 equ LABEL_DESC_CODE32 - LABEL_GDT SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT [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 ;byte2 shr eax, 16 mov byte [LABEL_DESC_CODE32 + 4], al ;byte4 mov byte [LABEL_DESC_CODE32 + 7], ah ;byte7 ;上面這一段就将用于保護模式的32位位址的描述位址相關資訊給初始化完成了 xor eax, eax ;清零 mov ax, ds shl eax, 4 add eax, LABEL_GDT mov dword [GdtPtr + 2], eax lgdt [GdtPtr] ;lgdt是BIOS調用,加載中斷描述符到CPU cli ;關中斷——防止下面代碼被打斷 ;------------這一段就是從實模式進入保護模式---------------------- in al, 92h or al, 00000010b out 92h, al mov eax, cr0 ;cr0也是一個寄存器,有專門的用途的,用來控制硬體的,不能像ax這樣的用于計算 or eax , 1 mov cr0, eax ;---------------------------------- jmp dword SelectorCode32: 0 [SECTION .s32] [BITS 32] LABEL_SEG_CODE32: mov ax, SelectorVideo ; SelectorVideo的段基址是0B8000H mov gs, ax mov si, msg mov ebx, 10 mov ecx, 2 showChar: mov edi, (80*11) add edi, ebx mov eax, edi mul ecx mov edi, eax mov ah, 0ch ;ah存放字元的顔色 mov al, [si] cmp al, 0 je end add ebx,1 add si, 1 mov [gs:edi], ax jmp showChar end: jmp $ msg: DB "Protect Mode", 0 SegCode32Len equ $ - LABEL_SEG_CODE32
從LABEL_SEG_CODE32:這一部分開始,代碼就執行在保護模式下。
這段代碼的作用就是顯示一串字元。
在保護模式下,寄存器不再存儲具體位址的數值,而是全局描述符表的偏移
gs是計算機的一個寄存器,它跟eax,ebx這些寄存器差不多,但作用更為單一,主要用來指向顯存
當我們将資訊寫入gs指向的記憶體後,資訊會顯示到螢幕上。記憶體位址從0XB800h開始,從該位址開始,每兩個位元組用來在螢幕上顯示一個字元,這兩個位元組中,第一個位元組的資訊用來表示字元的顔色,第二個位元組用來存儲要顯示的字元的ASCII值,螢幕一行能顯示80個字元
0B8000h表示的是顯存的位址。我們如果要在螢幕上顯示字元的話,需要把顯示的資訊寫入到0B8000h開始的地方,寫入0B8000h的資料會直接顯示在螢幕上
代碼中有語句
mov edi, (80*11)
這表明我們要從第11行開始顯示字元。因為整個螢幕可以顯示80個字元,80*11表示我們要顯示的字元從第11行開始
add edi, ebx
其中,ebx的值是11,這表明我們要從第11行的第10列開始顯示字元串
mov eax, edi
mul ecx
ecx的值是2,這個2就是我們前面說過的顯示一個字元需要兩個位元組,上面幾句彙編語句的作用是:
eax = ((80*11) + 10) * 2
這樣eax就指向了第11行第11列所在的顯存位置,接下來語句:
mov ah, 0ch
它的作用是在用來顯示字元的兩位元組中,對第一個位元組放入數值0ch,也就是設定字元的顔色,接下來的語句:
mov al, [si]
将寄存器si指向的字元的ascii值寫入到第二個位元組,這樣,字元就顯示到螢幕上了。大家注意寄存器si的用法:[si]. si相當于C語言中的一個指針,指向記憶體某個位址,[si]就是讀取si指向的記憶體位址的資訊,等同于 c語言中的*(si)
這裡的boot.asm還是用的上一節的,boot.asm生成boot.bin
boot.asm就是loader的代碼,是程式啟動引導的
kernel.bin是作業系統核心
計算機運作時要先由boot.bin引導計算機去讀取存放在硬碟的kernel.bin
boot.asm
寫完“Protect Mode"之後整個核心跳入到死循環org 0x7c00; LOAD_ADDR EQU 0X9000 entry: mov ax, 0 mov ss, ax mov ds, ax mov es, ax mov si, ax readFloppy: mov CH, 1 ;CH 用來存儲柱面号 mov DH, 0 ;DH 用來存儲磁頭号 mov CL, 2 ;CL 用來存儲扇區号 mov BX, LOAD_ADDR ; ES:BX 資料存儲緩沖區 mov AH, 0x02 ; AH = 02 表示要做的是讀盤操作 mov AL, 1 ; AL 表示要練習讀取幾個扇區 mov DL, 0 ;驅動器編号,一般我們隻有一個軟碟驅動器,是以寫死 ;為0 INT 0x13 ;調用BIOS中斷實作磁盤讀取功能 JC fin jmp LOAD_ADDR fin: HLT jmp fin
參考:
https://blog.csdn.net/tyler_download/article/details/52021120