揭開鋼琴的蓋子:操作系統好比一個架美麗的鋼琴,我們可以用上面的琴鍵彈出優美的旋律。但是我們不能滿足於只會彈奏,如果我們要更深入理解鋼琴,必須打開鋼琴的蓋子,一探究竟。所以學習操作系統,不能停留上系統API的調用,需要能更好更高效的調用API,知道API的局限性與缺點,就必須打開操作系統的蓋子,探究操作系統API下的底層原理。
從我們按下電源鍵使得計算機通電,計算機的各個部件是怎么運行起來的呢。我們現在使用的計算都遵循馮諾依曼結構,在我們探討計算機的啟動前,先弄明白我們的計算機的結構。
1. 馮諾依曼結構計算機的工作原理
- 計算機的核心工作部件是CPU,CPU內部從上電開始反復執行着兩個動作:1)取址;2)執行
- 計算機根據一系列的操作指令來執行不同的動作,這些指令就是計算機程序。
- 計算機運行的程序是以二進制的方式存在內存中,程序中的數據與指令不加區別的都存儲在內存上。
2. 計算機的啟動過程:

- X86 PC歲開機時,CPU處於實模式,這時候內存的計算方式是
段基址 << 4 + 段內偏移
- CPU的第一條指令是通過
CS:IP
來取得,而此時CS=0xFFFF,IP=0x0000。這是硬件設定好的。 - 所以最開始執行的指令地址就是0xFFFF0,這個內存地址映射在主板的BIOS ROM(只讀存儲區)中。
- ROM中的程序會檢測RAM、鍵盤、顯示器、軟硬磁盤是否正確工作。同時會從地址0開始設置BIOS的中斷向量表。
- ROM中的程序繼續執行,將啟動設備磁盤0磁道0扇區,一個512字節的扇區讀到內存0x07c00處。0x07c00應試是一個歷史遺留的問題,后續把system模塊拷貝到地址開始處時,預留的空間將不夠,所以bootset需要把0x07c00這一塊操作系統引導與設置模塊拷貝走。這算是一個歷史包袱。
- 設置cs=0x07c0,ip=0x0000。
- ROM中的程序執行結束,轉到0x07c00處開始執行。
啟動設備是可以通過BIOS程序來設置的,信息寫在CMOS中。CMOS(64B-128B)中存的還有實時鍾,硬件配置信息等。(開始時按住Del鍵可以進入啟動設置的配置界面,可以設置光盤啟動或U盤啟動等)。

3. Bootsect.s做了哪些事
- 把0x7c00開始的512個字節,拷貝到0x90000處。(0x90000 - 0x90020)
- 設置棧ss = 0x9000,sp = 0xff00,這里把sp設置的夠大,防止棧的區域把下面的操作系統代碼覆蓋了。
- 調用BIOS ox13中斷,將第2-5個扇區拷貝到0x90020開始的內存處。如果出錯,就反復讀取。
- 獲取磁盤的參數:磁道數等
- 打印字符串信息:system is loading
- 讀入system部分(幾百個扇區),讀入到內存為0x10000處。(在0x90000的下面)
- 轉到地址為0x90020的地址處執行,也就是開始執行setup部分的代碼了。
4. Steup模塊做了什么事
主要工作是完成操作系統啟動前的設置工作。
- 讀取光標的位置信息放在09000的頭2個字節處。因為這時候bootsect模塊的代碼已經沒有用了,可以覆蓋了。
- 讀出擴展內存的大小,放在接着的2個字節處。
- 獲取顯卡參數,硬盤參數等等。
- 將system模塊的內容從0x10000處開始移到0x00000處,即內存的起始位置。之所以Load進來的時候為什么不一次性放在0x00000處,是因為0x00000處開始放的bios中斷。現在bios中斷已經不需要了,所以可以覆蓋了。
- 這時候開始,BIOS的中斷向量表已經被覆蓋了,后面就不再需要BIOS的中斷了。
- 設置中斷向量表與全局描述符表的一部分內容。
- 把cr0的最后一位設置為1,也就是說從實模型進入保護模式。
jmpi 0, 8
。 cs = 8,取到的段基址其實是0x0000,那么這句話就是跳轉到地址為0x00000的地方開始執行,也就是system模塊的開始部分。
保護模式下地址翻譯與中斷處理的改變:
cp:ip的翻譯過程是:從cs的前12位取出GDT的偏移量(這里是1),從gtd的對應表項中取得基地址,再和ip合並為一個完整的地址。
int n: n指明了IDT表中的序號。從IDT表中獲取中斷處理函數的入口地址。
5. system-head
System的第一部分就是head.s部分的代碼,這部分代碼實際處於絕對地址0處開始的地方。該部分的代碼是在保護模式下執行的,所使用的是AT&T格式的匯編指令與之前使用的as86匯編指令不同。這部分的代碼主要完成了下面幾件事情。
- 初步始中斷描述符中的256項門描述符。
- 檢查A20地址線是否打開。關於A20地址線的解釋
- 測試系統是否含有數據協處理器,並設置寄存器CR0對應的位。
- 初始化內存頁目錄表,為內存分頁管理作好准備工作。頁目錄表放在了絕對物理地址為0開始處,也就是head.s程序物理內存位置,程序會被覆蓋掉。80286當時24根地址線,尋址16M,所以頁表要能尋址16MB。如果內存頁大小為4k,那頁表就有4K個表項,一個表項按4個字節算,那頁表就需要16K個字節(4頁)。這里只用到了1級頁表,在后續的發展中出現了二級頁表,3級頁表。
- 最后跳轉到system模塊中的初始化程序init/main.c中繼續執行。
head.s程序執行結束后,已經正式完成了內存頁目錄頁表的設置,並重新設置了內核實際使用的中斷描述符表idt和全局描述符表gtd。另外還為軟盤驅動開辟了1kb的緩沖區。此時system模塊在內存中的詳細映像如下圖所示:

6. 總體執行線路
整體上可以分類6個階段,頭2個階段為boostset,中間3個階段為setup,最后一個階段為system的head模塊。

7. 參考資料
[1] 《Linux內核完全剖析基於0.12內核》 趙炯著。
[2] 網易雲課堂,哈爾濱工業大學《操作系統之應用》 李治軍。