計算機加電後作業系統啟動過程
揭開鋼琴的蓋子:作業系統好比一個架美麗的鋼琴,我們可以用上面的琴鍵彈出優美的旋律。但是我們不能滿足於只會彈奏,如果我們要更深入理解鋼琴,必須開啟鋼琴的蓋子,一探究竟。所以學習作業系統,不能停留上系統API的呼叫,需要能更好更高效的呼叫API,知道API的侷限性與缺點,就必須開啟作業系統的蓋子,探究作業系統API下的底層原理。
從我們按下電源鍵使得計算機通電,計算機的各個部件是怎麼執行起來的呢。我們現在使用的計算都遵循馮諾依曼結構,在我們探討計算機的啟動前,先弄明白我們的計算機的結構。
1. 馮諾依曼結構計算機的工作原理
- 計算機的核心工作部件是CPU,CPU內部從上電開始反覆執行著兩個動作:1)取址;2)執行
- 計算機根據一系列的操作指令來執行不同的動作,這些指令就是計算機程式。
- 計算機執行的程式是以二進位制的方式存在記憶體中,程式中的資料與指令不加區別的都儲存在記憶體上。
2. 計算機的啟動過程:
圖1:從系統加電起所執行的程式- 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盤啟動等)。
圖2:核心在磁碟上的分佈情況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項門描述符。
- 測試系統是否含有資料協處理器,並設定暫存器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模組在記憶體中的詳細映像如下圖所示:
圖3:System記憶體中的映像示意圖6. 總體執行線路
整體上可以分類6個階段,頭2個階段為boostset,中間3個階段為setup,最後一個階段為system的head模組。
圖4:啟動引導的整個過程中,核心在記憶體中的位置以及移動後的位置情況7. 參考資料
[1] 《Linux核心完全剖析基於0.12核心》 趙炯著。
[2] 網易雲課堂,哈爾濱工業大學《作業系統之應用》 李治軍。