1.作業系統的啟動過程
在系統加電(我們按下電源開關)後,開始初始化他的暫存器,主要是cs和eip(基於x86架構),然後在ROM中找到一個叫BIOS(Basic Input Output System),載入到RAM中然後開始執行他,他在進行完裝置的自檢和初始化之後,就根據他自己內部的“我該去哪個裝置啟動載入程式”表,將其中第一個裝置的主引導扇區載入到記憶體中來,也就是將系統的控制權轉交給了這個程式,然後他在正確的引用了bootloader,也就是我們常說的“載入程式”,這個載入程式就會在記憶體的I/O區域中讀入硬碟(os的位置)扇區的資訊,得到作業系統核心的ELF檔案,載入到記憶體中來,然後將控制權轉交給OS,就大功告成了。
ROM-BIOS:意思是隻讀儲存器基本輸入輸出系統。是一組固化到計算機內主機板上一個ROM晶片上的程式,它儲存著計算機最重要的基本輸入輸出的程式、系統設定資訊、開機上電自檢程式和系統啟動自舉程式。 形象地說,BIOS應該是連線軟體程式與硬體裝置的一座"橋樑",負責解決硬體的即時要求。一塊主機板效能優越與否,很大程度上就取決於BIOS程式的管理功能是否合理、先進。主機板上的BIOS晶片或許是主機板上唯一貼有標籤的晶片,一般它是一塊32針的雙列直插式的積體電路,上面印有"BIOS"字樣。586以前的BIOS多為可重寫EPROM晶片,上面的標籤起著保護BIOS內容的作用(紫外線照射會使EPROM內容丟失),不能隨便撕下。586以後的ROM BIOS多采用EEPROM(電可擦寫只讀ROM),通過跳線開關和系統配帶的驅動程式盤,可以對EEPROM進行重寫,方便地實現BIOS升級。常見的BIOS晶片有Award、AMI、Phoenix、MR等,在晶片上都能見到廠商的標記。
BIOS的主要作用有以下幾方面: 首先是自檢及初始化程式:計算機電源接通後,系統將有一個對內部各個裝置進行檢查的過程,這是由一個通常稱之為POST(Power On Self Test/上電自檢)的程式來完成,這也是BIOS程式的一個功能。 完整的自檢包括了對CPU、640K基本記憶體、1M以上的擴充套件記憶體、ROM、主機板、CMOS存貯器、串並口、顯示卡、軟硬碟子系統及鍵盤的測試。 在自檢過程中若發現問題,系統將給出提示資訊或鳴笛警告。如果沒有任何問題,完成自檢後BIOS將按照系統CMOS設定中的啟動順序搜尋軟、硬碟驅動器及CDROM、網路伺服器等有效的啟動驅動器 , 讀入作業系統引導記錄,然後將系統控制權交給引導記錄,由引導記錄完成系統的啟動,你就可以放心地使用你的寶貝了。其次是硬體中斷處理:計算機開機的時候,BIOS會告訴CPU等硬體裝置的中斷號,當你操作時輸入了使用某個硬體的命令後,它就會根據中斷號使用相應的硬體來完成命令的工作,最後根據其中斷號跳會原來的狀態。再有就是程式服務請求:從BIOS的定義可以知道它總是和計算機的輸入輸出裝置打交道,它通過最特定的資料埠發出指令,傳送或接收各類外部裝置的資料,從而實現軟體應用程式對硬體的操作。
記憶體(主存):它是可以隨時讀寫,而且速度很快隨機儲存器。作為作業系統或其他正在執行中的程式的臨時資料儲存媒介。
在bootloader接手BIOS的工作後,當前的PC系統處於真實模式(16位模式)執行狀態,在這種狀態下軟體可訪問的實體記憶體空間不能超過1MB(0X00000~0XFFFFF),且無法發揮Intel 80386以上級別的32位CPU的4GB記憶體管理能力。
1.真實模式:
將整個實體記憶體看成分段的區域,程式程式碼和資料位於不同區域,作業系統和使用者程式並沒有區別對待,而且每一個指標都是指向實際的實體地址。這 樣,使用者程式的一個指標如果指向了作業系統區域或其他使用者程式區域,並修改了內容,那麼其後果就很可能是災難性的。通過修改A20地址線可以完成從真實模式 到保護模式的轉換。
開機時系統會以真實模式進入,此時可訪問的記憶體只有1M大小,這時的記憶體分配情況如下所示(此時由bios主導這一M記憶體的使用情況):
0x 0 0 0 0 0
|
| 10x64K=640K; 基本記憶體
|
0x 9 F F F F
0x A 0 0 0 0
|
| 2x64K=128K; 作為視訊記憶體使用
| 0xa0000-0xb0000 EGA/VGA/XGA/XVGA圖形視訊緩衝區
| 0xb0000-0xb8000 Mono text video buffer
| 0xb8000-0xc0000 CGA/EGA+ chroma text video buffer
|
0x B F F F F
0x C 0 0 0 0
|
| 4x64K=256K; 由bios使用,地址如何利用由其自己決定
|
0x F F F F F
---------------------------------------------------------
而通常情況下,bios對屬於自己的地址空間的劃分方式如下:
0x C 0 0 0 0
|
| 0.5X64k=32k; 顯示卡bios使用
|
0x C 7 F F F
0x C 8 0 0 0
|
| 0.25x64K=16K IDE控制器bios使用
|
0x C B F F F
.
.
0x F 0 0 0 0
|
| 1x64K=64K; 系統bios使用
|
0x F F F F F
也就是說:C0000H~FFFFFH則被保留給BIOS使用,其中系統BIOS一般佔用了最後的64KB或更多一點的空間,顯示卡BIOS一般在C0000H~C7FFFH處,IDE控制器的BIOS在C8000H~CBFFFH處。
----------------------------------------------------------
基本記憶體的分配方式如下(由bios分配):
0x 0 0 0 0 0
|
| 1K 中斷向量表 每一項佔領4位元組 共256項
|
0x 0 0 3 F F
0x 0 0 4 0 0
|
| 256位元組 bios資料區
|
0x 0 0 4 F F
0x 0 0 5 0 0
|
| 自由記憶體區 但0x07C00-0x07DFF (512位元組)為載入程式載入區
|
0x 9 F F F F
2.bootloader模式切換:
要將真實模式轉換到保護模式下,首先要做的就是開啟A20Gate,實現真實模式向保護模式的轉換過程:
啟用A20地址線的流程為:
1.關閉中斷;
2.等待8042 Input buffer為空;
3.傳送禁止鍵盤操作命令;
4.等待8042 Input buffer為空;
5.傳送讀取8042 Output Port命令;
6.等待8042 Output buffer有資料;
7.讀取8042 Output buffer,並儲存得到的位元組;
8.等待8042 Input buffer為空;
9.傳送Write 8042 Output Port命令到8042 Input buffer;
10.等待8042 Input buffer為空;
11.將從8042 Output Port得到的位元組的第2位置1(或清0),然後寫入8042 Input buffer;
12.等待,直到8042 Input buffer為空為止;
13.傳送允許鍵盤操作命令到8042 Input buffer;
14. 開啟中斷。
3.保護模式:
只有在保護模式下,80386的全部32根地址線有效,可定址高達4G位元組的線性地址空間和實體地址空間,可訪問64TB(有2^14個段,每個段 最大空間為2^32位元組)的邏輯地址空間,可採用分段儲存管理機制和分頁儲存管理機制。這不僅為儲存共享和保護提供了硬體支援,而且為實現虛擬儲存提供了 硬體支援。通過提供4個特權級和完善的特權檢查機制,既能實現資源共享又能保證程式碼資料的安全及任務的隔離。
3.1 分段儲存管理機制
只有在保護模式下才能使用分段儲存管理機制。分段機制將記憶體劃分成以起始地址和長度限制這兩個二維引數表示的記憶體塊,這些記憶體塊就稱之為段 (Segment)。分段機涉及4個關鍵內容:邏輯地址、段描述符(描述段的屬性)、段描述符表(包含多個段描述符的“陣列”)、段選擇子(段暫存器,用於定位段描述符 表中表項的索引)。轉換邏輯地址(Logical Address,應用程式設計師看到的地址)到實體地址(Physical Address, 實際的實體記憶體地址)分以下兩步:
(1)分段地址轉換:CPU把邏輯地址(由段選擇子selector和段偏移offset組成)中的段選擇子的內容作為段描述符表的索引,找到表中對應的段描述 符,然後把段描述符中儲存的段基址加上段偏移值,形成線性地址(Linear Address)。如果不啟動分頁儲存管理機制,則線性地址等於實體地址。
(2)分頁地址轉換,這一步中把線性地址轉換為實體地址。
線性地址空間由一維的線性地址構成,線性地址空間和實體地址空間對等。線性地址32位長,線性地址空間容量為4G位元組。分段地址轉換的基本過程如下圖:
3.2 段描述符
在分段儲存管理機制的保護模式下,每個段由如下三個引數進行定義:段基地址(Base Address)、段界限(Limit)和段屬性(Attributes)。在ucore中的kern/mm/mmu.h中的struct segdesc 資料結構中有具體的定義。
- 段基地址:規定線性地址空間中段的起始地址。在80386保護模式下,段基地址長32位。因為基地址長度與定址地址的長度相同,所以任何一個段都可以從32位線性地址空間中的任何一個位元組開始,而不象實方式下規定的邊界必須被16整除。
- 段界限:規定段的大小。在80386保護模式下,段界限用20位表示,而且段界限可以是以位元組為單位或以4K位元組為單位。
- 段屬性:確定段的各種性質。
- 段屬性中的粒度位(Granularity),用符號G標記。G=0表示段界限以位元組位位單位,20位的界限可表示的範圍是1位元組至1M位元組,增量為1位元組;G=1表示段界限以4K位元組為單位,於是20位的界限可表示的範圍是4K
- 段屬性中的粒度位(Granularity),用符號G標記。G=0表示段界限以位元組位位單位,20位的界限可表示的範圍是1位元組至1M位元組,增量為1位元組;G=1表示段界限以4K位元組為單位,於是20位的界限可表示的範圍是4K位元組至4G位元組,增量為4K位元組。
- 型別(TYPE):用於區別不同型別的描述符。可表示所描述的段是程式碼段還是資料段,所描述的段是否可讀/寫/執行,段的擴充套件方向等。
- 描述符特權級(Descriptor Privilege Level)(DPL):用來實現保護機制。
- 段存在位(Segment-Present bit):如果這一位為0,則此描述符為非法的,不能被用來實現地址轉換。如果一個非法描述符被載入進一個段暫存器,處理器會立即產生異常。圖5-4顯示 了當存在位為0時,描述符的格式。作業系統可以任意的使用被標識為可用(AVAILABLE)的位。
- 已訪問位(Accessed bit):當處理器訪問該段(當一個指向該段描述符的選擇子被載入進一個段暫存器)時,將自動設定訪問位。作業系統可清除該位。
上述引數通過段描述符來表示,段描述符的結構如下圖所示:
全域性描述符表
全域性描述符表的是一個儲存多個段描述符的“陣列”,其起始地址儲存在全域性描述符表暫存器GDTR中。GDTR長48位,其中高32位為基地址,低16位為 段界限。由於GDT 不能有GDT本身之內的描述符進行描述定義,所以處理器採用GDTR為GDT這一特殊的系統段。注意,全部描述符表中第一個段描述符設定為空段描述符。 GDTR中的段界限以位元組為單位。對於含有N個描述符的描述符表的段界限通常可設為8*N-1。在ucore中的boot/bootasm.S中的gdt 地址處和kern/mm/pmm.c中的全域性變數陣列gdt[]分別有基於組合語言和C語言的全域性描述符表的具體實現。
選擇子
線性地址部分的選擇子是用來選擇哪個描述符表和在該表中索引一個描述符的。選擇子可以做為指標變數的一部分,從而對應用程式設計師是可見的,但是一般是由連線載入器來設定的。選擇子的格式如下圖所示:
圖3 段選擇子結構
- 索引(Index):在描述符表中從8192個描述符中選擇一個描述符。處理器自動將這個索引值乘以8(描述符的長度),再加上描述符表的基址來索引描述符表,從而選出一個合適的描述符。
- 表指示位(Table Indicator,TI):選擇應該訪問哪一個描述符表。0代表應該訪問全域性描述符表(GDT),1代表應該訪問區域性描述符表(LDT)。
- 請求特權級(Requested Privilege Level,RPL):保護機制,在後續試驗中會進一步講解。
全域性描述符表的第一項是不能被CPU使用,所以當一個段選擇子的索引(Index)部分和表指示位(Table Indicator)都為0的時(即段選擇子指向全域性描述符表的第一項時),可以當做一個空的選擇子(見mmu.h中的SEG_NULL)。當一個段寄存 器被載入一個空選擇子時,處理器並不會產生一個異常。但是,當用一個空選擇子去訪問記憶體時,則會產生異常。
3.3 保護模式下的特權級
在保護模式下,特權級總共有4個,編號從0(最高特權)到3(最低特權)。有3種主要的資源受到保護:記憶體,I/O埠以及執行特殊機器指令的能 力。在任一時刻,x86 CPU都是在一個特定的特權級下執行的,從而決定了程式碼可以做什麼,不可以做什麼。這些特權級經常被稱為為保護環(protection ring),最內的環(ring 0)對應於最高特權0,最外面的環(ring 3)一般給應用程式使用,對應最低特權3。在ucore中,CPU只用到其中的2個特權級:0(核心態)和3(使用者態)。
有大約15條機器指令被CPU限制只能在核心態執行,這些機器指令如果被使用者模式的程式所使用,就會顛覆保護模式的保護機制並引起混亂,所以它們被 保留給作業系統核心使用。如果企圖在ring 0以外執行這些指令,就會導致一個一般保護異常(general-protection exception)。對記憶體和I/O埠的訪問也受類似的特權級限制。
資料段選擇子的整個內容可由程式直接載入到各個段暫存器(如SS或DS等)當中。這些內容裡包含了請求特權級(Requested Privilege Level,簡稱RPL)欄位。然而,程式碼段暫存器(CS)的內容不能由裝載指令(如MOV)直接設定,而只能被那些會改變程式執行順序的指令(如 JMP、INT、CALL)間接地設定。而且CS擁有一個由CPU維護的當前特權級欄位(Current Privilege Level,簡稱CPL)。二者結構如下圖所示:
程式碼段暫存器中的CPL欄位(2位)的值總是等於CPU的當前特權級,所以只要看一眼CS中的CPL,你就可以知道此刻的特權級了。
CPU會在兩個關鍵點上保護記憶體:當一個段選擇符被載入時,以及,當通過線性地址訪問一個記憶體頁時。因此,保護也反映在記憶體地址轉換的過程之中,既包括分段又包括分頁。當一個數據段選擇符被載入時,就會發生下述的檢測過程:
因為越高的數值代表越低的特權,上圖中的MAX()用於選擇CPL和RPL中特權最低的一個,並與描述符特權級(Descriptor Privilege Level,簡稱DPL)比較。如果DPL的值大於等於它,那麼這個訪問可正常進行了。RPL背後的設計思想是:允許核心程式碼載入特權較低的段。比如,你 可以使用RPL=3的段描述符來確保給定的操作所使用的段可以在使用者模式中訪問。但堆疊段暫存器是個例外,它要求CPL,RPL和DPL這3個值必須完全 一致,才可以被載入。CPL、RPL和DPL描述:
- CPL:當前特權級(Current Privilege Level) 儲存在CS段暫存器(選擇子)的最低兩位,CPL就是當前活動程式碼段的特權級,並且它定義了當前所執行程式的特權級別)
- DPL:描述符特權(Descriptor Privilege Level) 儲存在段描述符中的許可權位,用於描述對應段所屬的特權等級,也就是段本身真正的特權級。
- RPL:請求特權級RPL(Request Privilege Level) RPL儲存在選擇子的最低兩位。RPL說明的是程序對段訪問的請求許可權,意思是當前程序想要的請求許可權。RPL的值由程式設計師自己來自由的設定,並不一定 RPL>=CPL,但是當RPL<CPL時,實際起作用的就是CPL了,因為訪問時的特權檢查是判斷:max(RPL,CPL)& lt;=DPL是否成立,所以RPL可以看成是每次訪問時的附加限制,RPL=0時附加限制最小,RPL=3時附加限制最大。
參考文獻: