《操做系統真象還原》保護模式入門

  如下是第四章的收穫:數組

保護模式

  什麼是保護模式?直接定義保護模式彷佛是件很抽象的事情,咱們不妨先看看爲何要有保護模式,且保護模式能爲咱們作些什麼?緩存

  保護模式是相對於實模式而言的,且是爲了解決實模式的一些問題而提出來的。實模式是8086CPU下的尋址模式、指令用法、寄存器大小等。安全

  那麼實模式有什麼問題呢?爲何須要保護模式呢?數據結構

爲何須要保護模式

  ①實模式下,用戶進程和系統進程屬於同一特權級別,分庭抗禮,系統進程能調用什麼,用戶進程就能調用什麼。spa

  ②實模式下,進程訪問的地址直接就是物理地址,想修改內存裏面的內容十分容易,沒有一點限制。操作系統

  ③實模式下,能夠隨意修改段基址,訪問全部內存。code

  以上三點均爲安全問題,也是主要問題。咱們能夠從保護模式的「保護」中看出,保護模式下運行的程序會更加安全可靠,更具「安全感」,不會被另外一個程序隨意修改。blog

  ④訪問內存,須要不斷變換段基址,來訪問咱們想要的內存地址,由於一個段的大小隻有64KB。進程

  ⑤內存大小隻有1M,不夠用。內存

  ⑥每次只能運行一個程序,浪費CPU的資源。

  ④到⑥點是使用上的問題,想一想若是咱們如今的電腦還用着使用着實模式下的CPU的的話,程序的效率會有多麼低下,訪問內存還得自縛手腳,運行一個程序還要等上一個程序運行完,顯然是現代操做系統裏不可思議的。

  下面就來想如何解決這些問題,第①點的話,咱們賦予每一個進程不同的特權等級就好,操做系統顯然是最高級別的,用戶進程就要低一些,具體內容會在以後章節看到。

  第②點,保護模式下采用了分頁來訪問內存,此時進程訪問的地址並非最終的物理地址,而是虛擬地址。虛擬地址還須要經過分頁模式下的一些信息才能轉換爲最終的物理地址。這也會在後面章節說到。

  對於②③點,最直接的作法是對段的讀寫作必定的限制,CPU及操做系統經過段描述符來達到這一目的。段描述符,就是描述段的結構,其信息包括:段基址、段界限、段類型、段是否可寫可讀、段的方向(如棧是往低地址延伸的,其它的往高地址延伸)等等。具體的細節後面會說到。

  對於④⑤點,在CPU發展到32位後,地址總線和數據總線拓展到32位,通用寄存器大小也拓展到32位,這樣子能訪問的內存地址空間一會兒就變成4G,更方便的是,單純靠一個通用寄存器也能訪問全部內存地址了,甚至無需段基址了,也無需左移4位。固然,爲了兼容,CPU在保護模式下仍是採用段基址+段內偏移地址來訪問最終的物理地址,但咱們能夠作一點小小的操做,將段基址設爲0,單靠段內偏移地址就能訪問全部的物理內存,於是這也被稱爲「平坦模式」。除此以外,CPU還拓展了指令用法,例如基址尋址再也不限制只能用bx,sp做爲基址寄存器,而是全部經過寄存器都是做爲基址寄存器;變址尋址也再也不限制於只用si,di做爲變址寄存器,而是除了sp之外的全部通用寄存器都能用做變址寄存器。

全局描述符表

  一個段描述符描述一個段的信息,一個專門的數據結構保存着多個段描述符,稱爲「全局描述符表」,其實就是一個保存着段描述符的數組。寄存器顯然不可能保存着這個全局描述符表,那麼只能保存在大一點的內存裏面。但內存相對於寄存器和L一、L2緩存等仍是慢了很多,因此CPU專門有一個叫作段描述符緩衝寄存器來提升獲取段描述符的效率,經過將用過的段描述符存進寄存器裏,再用時再從寄存器中取出來,減小對內存的訪問,從而加快內存尋址。

  下面來描述一下段描述符結構:

 

  低32位0~15位和高32位的16~19位表明段界限,描述段能達到的邊界。具體邊界值要結合23位的G來看,G=1時,表示段界限的粒度爲4KB,G=0時,表示段界限的粒度爲1Byte,實際的段界限=(描述符裏的段界限+1)*段界限粒度大小-1。

      低32位的16~31位和高32位的0~7位及24~31位共同描述段基址的32位,爲何會分散在三個地方呢?答案很簡單,兼容問題,爲了將段基址拓展到32位,段界限拓展到20位,也只能接着在後面添加了,因此纔會分散在不一樣的地方。

  S表明一個段是系統段仍是數據段,在CPU眼裏,凡是硬件使用到的東西稱爲系統,凡是軟件使用到的東西稱爲數據。因此代碼段、數據段、棧段等也屬於S中所表明的的數據段。

  Type指定段的類型,一共四位。只有S決定了,Type纔有它的意義。下圖是Type在系統段和數據段裏不一樣的意義。

  咱們主要看一下數據段下Type的意義。當段爲代碼段時,Type由X、R、C、A組成,分別表明是否可執行、是否可讀、是否一致、是否被訪問過。當段位數據段時,Type由X、W、E、A組成,分別表明是否可執行、是否可寫、擴展方向、是否被訪問過。

  DPL表明段屬於哪個特權級別。

  P表明內存段是否存在,0表明段不存在,1表明段存在。

  AVL表明可用的位,操做系統能夠隨意使用,沒有特殊含義。

  L表明代碼段是64位仍是32位。

  D/B。對於代碼段來講此位是D,用來給代碼段指定是使用16位仍是32位有效地址和操做數的。對於棧段來講此位是B,用來給棧段指定使用的是sp寄存器仍是esp寄存器,sp寄存器的最大尋址範圍是0xFFFF,esp寄存器的最大尋址範圍是0xFFFFFFFF。

  G表明段界限的粒度,是4KB仍是1B。

  全局描述符表示共用的,多個程序均可以在這個表定義本身的段描述符。咱們進入保護模式的其中一個步驟之一就是加載全局描述符表,讓CPU知道全局描述符表的位置,在操做內存的時候,CPU就會根據描述符的信息檢查這操做是否有效。

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

  下面咱們開始編寫代碼:

  首先是boot.inc文件的改動,主要定義了描述符的各個位,方便設置整個段描述符。

LOADER_BASE_ADDR equ 0x900 LOADER_START_SECTOR equ 0x2 DESC_G_4K equ 1_000_000_000_000_000_000_000_00b DESC_D_32 equ 1_000_000_000_000_000_000_000_0b DESC_L equ 0_000_000_000_000_000_000_000b DESC_AVL equ 0_000_000_000_000_000_000_00b DESC_LIMIT_CODE2 equ 1111_0000_0000_0000_0000b DESC_LIMIT_DATA2 equ DESC_LIMIT_CODE2 DESC_LIMIT_VIDEO2 equ 0000_000_000_000_000_000b DESC_P equ 1_000_000_000_000_000b DESC_DPL_0 equ 00_000_000_000_000_0b DESC_DPL_1 equ 01_000_000_000_000_0b DESC_DPL_2 equ 10_000_000_000_000_0b DESC_DPL_3 equ 11_000_000_000_000_0b DESC_S_CODE equ 1_000_000_000_000b DESC_S_DATA equ DESC_S_CODE DESC_S_sys equ 0_000_000_000_000b DESC_TYPE_CODE equ 1000_0000_0000b DESC_TYPE_DATA equ 0010_0000_0000b 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

  下面是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 0x0000_0000 dd 0x0000_0000 ;代碼段描述符 CODE_DESC: dd 0x0000FFFF dd DESC_CODE_HIGH4 ;數據段、棧段描述符 DATA_STACK_DESC: dd 0x0000FFFF dd DESC_DATA_HIGH4 ;顯存段 VIDEO_DESC: dd 0x8000_0007 dd DESC_VIDEO_HIGH4 GDT_SIZE equ $ - GDT_BASE GDT_LIMIT equ GDT_SIZE - 1 ;預留60個描述符 times 60 dq 0 ;設置各個選擇子 SELECTOR_CODE equ (0x0001 << 3) + TI_GDT + RPL0 SELECTOR_DATA equ (0x0002 << 3) + TI_GDT + RPL0 SELECTOR_VIDEO equ (0x0003 << 3) + TI_GDT + RPL0 ;用於設置GDTR寄存器的值 gdt_ptr dw GDT_LIMIT dd GDT_BASE loadermsg db '2 loader in real.' loader_start: mov sp,LOADER_BASE_ADDR mov bp, loadermsg mov cx, 17 mov ax, 0x1301 mov bx, 0x001f mov dx, 0x1800
int 0x10

in al,0x92 or al,0000_0010b out 0x92,al ;進入保護模式的三個步驟 lgdt [gdt_ptr] mov eax, cr0 or eax, 0x0000_0001 mov cr0, eax ;刷新流水線 jmp dword SELECTOR_CODE:p_mode_start [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 $

  像以前一節那樣編譯及加載程序到硬盤後,執行bochs,第二行會顯示字符P,最後一行會顯示「2 loader in real",結果以下:

 

   另外一個值得注意的指令是:jmp dword SELECTOR_CODE:p_mode_start,這個指令是用來刷新流水線的,由於在進入保護模式以前,p_mode_start後面的指令也會被放上流水線,指令會按照16位譯碼,其實原本應該按照32位譯碼才能正常執行,因此咱們須要清除流水線上的這些指令,保證這些指令按32位譯碼,這樣才能正常地運行下去。

相關文章
相關標籤/搜索