1. 程式人生 > 其它 >第四章 保護模式入門

第四章 保護模式入門

第四章 保護模式入門

真實模式

什麼是真實模式

真實模式,又叫實地址模式.從80386開始,CPU有三種工作模式:真實模式、保護模式和虛擬8086模式。80286開始的CPU引入保護模式,實際上,真實模式概念是在保護模式推出之後為了區別保護模式之前的8086CPU工作模式才有的,在8086時代CPU工作模式只有一種,自然沒有真實模式之說。

真實模式的“實”體現在程式中用到的地址都是真實的實體地址,“段基址:段內偏移地址”產生的邏輯地址就是實體地址,即程式設計師可見的地址完全是真實的記憶體地址。

真實模式的缺點

不安全

  1. 沒有許可權劃分,作業系統程式和使用者程式屬於同一特權級.
  2. 程式中用到的地址都是真實的實體地址,邏輯地址等於實體地址.
  3. 程式可以自由修改段地址,也就是可以訪問所有記憶體地址.
  4. 段大小受限,段內偏移最大為64K,如果程式超過64k,需要佔用多個段,段暫存器需要切換段基址.

保護模式之暫存器擴充套件

原有暫存器寬度增加

除段暫存器外,通用暫存器、指令指標暫存器、標誌暫存器都由原來的 16 位擴充套件到了 32 位。
經過 extend 後的暫存器,統一在名字前加了 E表示擴充套件.

暫存器中低 16 位的部分是為了相容真實模式,可以單獨使用。高 16 位沒辦法單獨使用,只能在用 32位暫存器時才有機會用到它們。

新增新的暫存器

除了容量的擴充套件,還有新的暫存器新增;

新增新暫存器的目的:解決真實模式的安全問題

安全問題之一:段沒有級別劃分,程式可以隨意訪問任意段,任意記憶體地址;

解決段隨意訪問問題->段新增描述資訊->新增描述資訊,即段描述符->段描述符有多個,要描述多個段,全域性描述符表包括多個段描述符->全域性描述符表很大,不能直接放在暫存器,只能放在記憶體中->為了能找到全域性描述符表,把全域性描述符表的地址放在GDTR暫存器->段暫存器中不再儲存段基址,儲存"選擇子"(selector),是段描述符在全域性描述符表中的索引->獲取段描述符的步驟:1.讀取GDTR暫存器獲取全域性描述符表的地址;2.讀取段暫存器獲取選擇子;3.全域性描述符表地址+選擇子,從記憶體中讀取段描述符;->獲取段描述符耗時長,而且段資訊在執行同一個程式時,一般不會變動,所以可以在獲取段描述符後,把段描述符放到暫存器中做快取,此暫存器式段描述符快取暫存器->在段暫存器的選擇子更新時,就會重新獲取段描述符,更新段描述符快取暫存器;

保護模式之定址擴充套件

真實模式下暫存器的功能比較固定.真實模式下對於記憶體定址來說,其中的基址定址、變址定址、基址變址定址,這三種形式中的基址暫存器只能是bx,bp,變址暫存器只能si、di,也就是說,只能用這個暫存器。其中bx預設的段暫存器是由,它經常用於訪問資料段,bp預設的段暫存器是SS,它經常用於訪問棧。

mov ax, [si] 
mov ax, [di] 
mov ax, [bx] 
mov ax, [bx+si] 
mov ax, [bx+si+Oxl234] 
mov ax, [bx+di] 
mov ax, [bx+di+Oxl234]

在保護模式下,這一切都不同了,同樣是記憶體定址中,基址暫存器不再只是bx,bp,而是所有32位的通用暫存器,變址暫存器也是一樣,不再只是si,di,而是除esp之外的所有32通用暫存器,偏移量由真實模式的16位變成了32位。並且,還可以對變址暫存器乘以一個比例因子

mov eax, [eax+edx*8+0x12345678] 
mov eax, [eax+edx*2+0x8] 
mov eax, [ecx*4+0x1234]

保護模式之執行反轉

相容真實模式和保護模式,cpu很難區分,需要生成不同的機器碼來做處理;編譯器提供偽指令bits,來區分16位和32位.

[bits 16] 
mov ax, Oxl234 
mov dx, Oxl234 
 
[bits 32] 
mov eax , Oxl234 
mov edx, Oxl234

全域性描述符表

全域性描述符表( Global Descriptor Table, GDT )是保護模式下記憶體段的登記表,這是不同於真實模式的顯著特徵之一。

一個段描述符描述一個段的資訊,一個專門的資料結構儲存著多個段描述符,稱為“全域性描述符表”,其實就是一個儲存著段描述符的陣列。

段描述符

  • 段描述符大小為8個位元組

  • 段界限被分為兩部分,段基址被分為三部分,原因是為了相容80286,80286是第一款有保護模式的cpu,但是隻有16位。

  • 段界限:表示段的大小,共20位。具體邊界值要結合23位的G來看,G=1時,表示段界限的粒度為4KB,最大值為2的(20+12)次方,即4GB,G=0時,表示段界限的粒度為1位元組,最大值為2的20次方,即1MB實際的段界限=(描述符裡的段界限+1)*段界限粒度大小-1。

  • 段基址:共32位。

  • S:代表一個段是系統段還是資料段,在CPU眼裡,凡是硬體使用到的東西稱為系統,凡是軟體使用到的東西稱為資料。所以程式碼段、資料段、棧段等也屬於S中所代表的的資料段。S為0時表示系統段,S為1時表示資料段。

  • Type:共四位,指定段的型別。只有S決定了,Type才有它的意義。下圖是Type在系統段和資料段裡不同的意義。

    • 非系統段:當段為程式碼段時,Type由X、R、C、A組成,分別代表是否可執行、是否可讀、是否一致、是否被訪問過。當段位資料段時,Type由X、W、E、A組成,分別代表是否可執行、是否可寫、擴充套件方向、是否被訪問過。
  • DPL代表段的特權級。
  • P代表記憶體段是否存在,0代表段不存在,1代表段存在。
  • AVL代表可用的位,是對使用者而言,但作業系統可以隨意使用。
  • L代表程式碼段是64位還是32位。
  • D/B:有效地址和運算元的大小。對於程式碼段來說此位是D,為0時,有效地址和運算元的大小為16位,使用ip暫存器;為1時,有效地址和運算元的大小為32位,使用eip暫存器。對於棧段來說此位是B,為0時,運算元為16位,使用sp暫存器;為1時,運算元為32位,使用esp暫存器。

全域性描述符表

全域性描述符表示全域性的,共用的,多個程式都可以在這個表定義自己的段描述符。我們進入保護模式的其中一個步驟之一就是載入全域性描述符表,讓CPU知道全域性描述符表的位置,在操作記憶體的時候,CPU就會根據描述符的資訊檢查這操作是否有效。

全域性描述符表位於記憶體中,由GDTR暫存器指向全域性描述符表的記憶體地址。GDTR暫存器大小為48位,初始化GDTR的指令為lgdt 48位數值

這48位數值的後32位表示GDT的起始地址,前16位表示GDT的大小,單位為位元組,即2的16次方,大小為65536位元組;每個描述符大小為8位元組,所以GDT最多容納65536/8=8192個段。

選擇子

段暫存器中存入的是選擇子(selector),段暫存器是16位,所以選擇子也是16位。

![image-20210221212035412](/Users/liufq/Documents/文件/作業系統真相還原/第四章 保護模式入門/第四章 保護模式入門.assets/image-20210221212035412.png)

  • RPL:特權級,有0、1、2、3,四種。
  • TI:為0,描述符索引值為GDT中索引值;為1,描述符索引值為LDT中索引值。

保護模式下,使用選擇子獲取端描述符,從段描述符中取出段基址。

LDT:區域性描述符表,很少使用。

進入保護模式

A20地址線

在真實模式下,A20地址線是預設禁用的,原因是還未進入保護模式之前,地址匯流排還是要模擬20位的效果,即只保留20位以內的地址,如果地址超過20位,地址就會迴繞到0,將地址20位(從0開始算)捨棄,所以要將A20地址線給禁用掉。但進入保護模式後,我們需要恢復地址匯流排的原貌,即使地址超過20位,地址也不應該回繞到0,所以此時將A20地址線開啟,我們就能訪問超過20位的地址了。因此,開啟A20地址線,是進入保護模式的步驟之一。

CR0的PE位

  進入保護模式的最後一個步驟是,開啟CR0的PE位,CR0是控制暫存器。控制暫存器是CPU的視窗,它既可以展示CPU的內部狀態,也可以控制CPU的執行機制。CR0的第0位,PE位,就是保護模式的開關,我們開啟PE位,就是告訴CPU接下來我們要進入保護模式。

進入保護模式

  由上面可以知道,進入保護模式的步驟如下:

  ① 開啟A20地址線

  ② 載入GDT

  ③ 將CR0的PE位置為1

總共三個檔案:include/boot.inc,mbr.S,loader.S

boot.inc

增加段描述符的常量

;-------------	 loader和kernel   ----------

LOADER_BASE_ADDR equ 0x900 
LOADER_START_SECTOR equ 0x2

;--------------   gdt描述符屬性  -------------
DESC_G_4K   equ	  1_00000000000000000000000b   
DESC_D_32   equ	   1_0000000000000000000000b
DESC_L	    equ	    0_000000000000000000000b	;  64位程式碼標記,此處標記為0便可。
DESC_AVL    equ	     0_00000000000000000000b	;  cpu不用此位,暫置為0  
DESC_LIMIT_CODE2  equ 1111_0000000000000000b
DESC_LIMIT_DATA2  equ DESC_LIMIT_CODE2
DESC_LIMIT_VIDEO2  equ 0000_000000000000000b
DESC_P	    equ		  1_000000000000000b
DESC_DPL_0  equ		   00_0000000000000b
DESC_DPL_1  equ		   01_0000000000000b
DESC_DPL_2  equ		   10_0000000000000b
DESC_DPL_3  equ		   11_0000000000000b
DESC_S_CODE equ		     1_000000000000b
DESC_S_DATA equ	  DESC_S_CODE
DESC_S_sys  equ		     0_000000000000b
DESC_TYPE_CODE  equ	      1000_00000000b	;x=1,c=0,r=0,a=0 程式碼段是可執行的,非依從的,不可讀的,已訪問位a清0.  
DESC_TYPE_DATA  equ	      0010_00000000b	;x=0,e=0,w=1,a=0 資料段是不可執行的,向上擴充套件的,可寫的,已訪問位a清0.

DESC_CODE_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 + DESC_L + DESC_AVL + DESC_LIMIT_CODE2 + DESC_P + DESC_DPL_0 + DESC_S_CODE + DESC_TYPE_CODE + 0x00
DESC_DATA_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 + DESC_L + DESC_AVL + DESC_LIMIT_DATA2 + DESC_P + DESC_DPL_0 + DESC_S_DATA + DESC_TYPE_DATA + 0x00
DESC_VIDEO_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 + DESC_L + DESC_AVL + DESC_LIMIT_VIDEO2 + DESC_P + DESC_DPL_0 + DESC_S_DATA + DESC_TYPE_DATA + 0x0b

;--------------   選擇子屬性  ---------------
RPL0  equ   00b
RPL1  equ   01b
RPL2  equ   10b
RPL3  equ   11b
TI_GDT	 equ   000b
TI_LDT	 equ   100b

mbr.S

此前只讀入了一個扇區,由於 loader.bin 超過了 512 位元組,52行增加讀入的扇區為4

;主載入程式 
;------------------------------------------------------------
%include "boot.inc"
SECTION MBR vstart=0x7c00         
   mov ax,cs      
   mov ds,ax
   mov es,ax
   mov ss,ax
   mov fs,ax
   mov sp,0x7c00
   mov ax,0xb800
   mov gs,ax

; 清屏
;利用0x06號功能,上卷全部行,則可清屏。
; -----------------------------------------------------------
;INT 0x10   功能號:0x06	   功能描述:上卷視窗
;------------------------------------------------------
;輸入:
;AH 功能號= 0x06
;AL = 上卷的行數(如果為0,表示全部)
;BH = 上卷行屬性
;(CL,CH) = 視窗左上角的(X,Y)位置
;(DL,DH) = 視窗右下角的(X,Y)位置
;無返回值:
   mov     ax, 0600h
   mov     bx, 0700h
   mov     cx, 0                   ; 左上角: (0, 0)
   mov     dx, 184fh		   ; 右下角: (80,25),
				   ; 因為VGA文字模式中,一行只能容納80個字元,共25行。
				   ; 下標從0開始,所以0x18=24,0x4f=79
   int     10h                     ; int 10h

   ; 輸出字串:MBR
   mov byte [gs:0x00],'1'
   mov byte [gs:0x01],0xA4

   mov byte [gs:0x02],' '
   mov byte [gs:0x03],0xA4

   mov byte [gs:0x04],'M'
   mov byte [gs:0x05],0xA4	   ;A表示綠色背景閃爍,4表示前景色為紅色

   mov byte [gs:0x06],'B'
   mov byte [gs:0x07],0xA4

   mov byte [gs:0x08],'R'
   mov byte [gs:0x09],0xA4
	 
   mov eax,LOADER_START_SECTOR	 ; 起始扇區lba地址
   mov bx,LOADER_BASE_ADDR       ; 寫入的地址
   mov cx,4			 ; 待讀入的扇區數
   call rd_disk_m_16		 ; 以下讀取程式的起始部分(一個扇區)
  
   jmp LOADER_BASE_ADDR
       
;-------------------------------------------------------------------------------
;功能:讀取硬碟n個扇區
rd_disk_m_16:	   
;-------------------------------------------------------------------------------
				       ; eax=LBA扇區號
				       ; ebx=將資料寫入的記憶體地址
				       ; ecx=讀入的扇區數
      mov esi,eax	  ;備份eax
      mov di,cx		  ;備份cx
;讀寫硬碟:
;第1步:設定要讀取的扇區數
      mov dx,0x1f2
      mov al,cl
      out dx,al            ;讀取的扇區數

      mov eax,esi	   ;恢復ax

;第2步:將LBA地址存入0x1f3 ~ 0x1f6

      ;LBA地址7~0位寫入埠0x1f3
      mov dx,0x1f3                       
      out dx,al                          

      ;LBA地址15~8位寫入埠0x1f4
      mov cl,8
      shr eax,cl
      mov dx,0x1f4
      out dx,al

      ;LBA地址23~16位寫入埠0x1f5
      shr eax,cl
      mov dx,0x1f5
      out dx,al

      shr eax,cl
      and al,0x0f	   ;lba第24~27位
      or al,0xe0	   ; 設定7~4位為1110,表示lba模式
      mov dx,0x1f6
      out dx,al

;第3步:向0x1f7埠寫入讀命令,0x20 
      mov dx,0x1f7
      mov al,0x20                        
      out dx,al

;第4步:檢測硬碟狀態
  .not_ready:
      ;同一埠,寫時表示寫入命令字,讀時表示讀入硬碟狀態
      nop
      in al,dx
      and al,0x88	   ;第4位為1表示硬碟控制器已準備好資料傳輸,第7位為1表示硬碟忙
      cmp al,0x08
      jnz .not_ready	   ;若未準備好,繼續等。

;第5步:從0x1f0埠讀資料
      mov ax, di
      mov dx, 256
      mul dx
      mov cx, ax	   ; di為要讀取的扇區數,一個扇區有512位元組,每次讀入一個字,
			   ; 共需di*512/2次,所以di*256
      mov dx, 0x1f0
  .go_on_read:
      in ax,dx
      mov [bx],ax
      add bx,2		  
      loop .go_on_read
      ret

   times 510-($-$$) db 0
   db 0x55,0xaa

loader.S

   %include "boot.inc"
   section loader vstart=LOADER_BASE_ADDR
   LOADER_STACK_TOP equ LOADER_BASE_ADDR
   jmp loader_start					; 此處的實體地址是:
   
;構建gdt及其內部的描述符
   GDT_BASE:   dd    0x00000000 
	       dd    0x00000000

   CODE_DESC:  dd    0x0000FFFF 
	       dd    DESC_CODE_HIGH4

   DATA_STACK_DESC:  dd    0x0000FFFF
		     dd    DESC_DATA_HIGH4

   VIDEO_DESC: dd    0x80000007	       ;limit=(0xbffff-0xb8000)/4k=0x7
	       dd    DESC_VIDEO_HIGH4  ; 此時dpl已改為0

   GDT_SIZE   equ   $ - GDT_BASE
   GDT_LIMIT   equ   GDT_SIZE -	1 
   times 60 dq 0					 ; 此處預留60個描述符的slot
   SELECTOR_CODE equ (0x0001<<3) + TI_GDT + RPL0         ; 相當於(CODE_DESC - GDT_BASE)/8 + TI_GDT + RPL0
   SELECTOR_DATA equ (0x0002<<3) + TI_GDT + RPL0	 ; 同上
   SELECTOR_VIDEO equ (0x0003<<3) + TI_GDT + RPL0	 ; 同上 

   ;以下是定義gdt的指標,前2位元組是gdt界限,後4位元組是gdt起始地址

   gdt_ptr  dw  GDT_LIMIT 
	    dd  GDT_BASE
   loadermsg db '2 loader in real.'

   loader_start:

;------------------------------------------------------------
;INT 0x10    功能號:0x13    功能描述:列印字串
;------------------------------------------------------------
;輸入:
;AH 子功能號=13H
;BH = 頁碼
;BL = 屬性(若AL=00H或01H)
;CX=字串長度
;(DH、DL)=座標(行、列)
;ES:BP=字串地址 
;AL=顯示輸出方式
;   0——字串中只含顯示字元,其顯示屬性在BL中。顯示後,游標位置不變
;   1——字串中只含顯示字元,其顯示屬性在BL中。顯示後,游標位置改變
;   2——字串中含顯示字元和顯示屬性。顯示後,游標位置不變
;   3——字串中含顯示字元和顯示屬性。顯示後,游標位置改變
;無返回值
   mov	 sp, LOADER_BASE_ADDR
   mov	 bp, loadermsg           ; ES:BP = 字串地址
   mov	 cx, 17			 ; CX = 字串長度
   mov	 ax, 0x1301		 ; AH = 13,  AL = 01h
   mov	 bx, 0x001f		 ; 頁號為0(BH = 0) 藍底粉紅字(BL = 1fh)
   mov	 dx, 0x1800		 ;
   int	 0x10                    ; 10h 號中斷

;----------------------------------------   準備進入保護模式   ------------------------------------------
									;1 開啟A20
									;2 載入gdt
									;3 將cr0的pe位置1


   ;-----------------  開啟A20  ----------------
   in al,0x92
   or al,0000_0010B
   out 0x92,al

   ;-----------------  載入GDT  ----------------
   lgdt [gdt_ptr]


   ;-----------------  cr0第0位置1  ----------------
   mov eax, cr0
   or eax, 0x00000001
   mov cr0, eax

   ;jmp dword SELECTOR_CODE:p_mode_start	     ; 重新整理流水線,避免分支預測的影響,這種cpu優化策略,最怕jmp跳轉,
   jmp  SELECTOR_CODE:p_mode_start	     ; 重新整理流水線,避免分支預測的影響,這種cpu優化策略,最怕jmp跳轉,
					     ; 這將導致之前做的預測失效,從而起到了重新整理的作用。

[bits 32]
p_mode_start:
   mov ax, SELECTOR_DATA
   mov ds, ax
   mov es, ax
   mov ss, ax
   mov esp,LOADER_STACK_TOP
   mov ax, SELECTOR_VIDEO
   mov gs, ax

   mov byte [gs:160], 'P'

   jmp $