天天看點

MyOS(五):由實模式進入保護模式(32位)

保護模式的尋址方式不在使用寄存器分段的方式直接尋址方式了。而采用的是使用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

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

           
MyOS(五):由實模式進入保護模式(32位)
寫完“Protect Mode"之後整個核心跳入到死循環

參考:

https://blog.csdn.net/tyler_download/article/details/52021120