天天看點

linux啟動從bios到bootleader

bios(basic input / output system)啟動,首先系統會進行自檢,檢測cpu,記憶體,顯示卡,i/o是否正常,如發現異常這個階段會報錯。

上一個階段成功後會讀取mbr(master boot record)硬碟的0柱面,0磁盤,1扇區稱為主引導扇區。它由三部分組成,主引導程式(bootleader),硬碟分區表dpt(disk partition table)和硬碟有效标志(55aa),如下圖所示

linux啟動從bios到bootleader

使用grub2啟動系統時,啟動順序如下圖

linux啟動從bios到bootleader

grub2第一個需要安裝的是boot.img,它由boot.s編譯而成,一共512個位元組,正是安裝到啟動盤的第一個扇區,這個扇區通常被稱作mbr(master boot record / 主引導記錄扇區)

bios完成任務後,會将boot.img從硬碟加載到記憶體的0x7c00中,為什麼是0x7c00呢,0x7c00這個位址來自intel的第一代個人電腦晶片8088,以後的cpu為了保持相容一直使用這個位址。1981年8月,ibm公司最早的個人電腦ibm pc 5150上市,就用了這個晶片。當時搭配的作業系統是86-dos, 這個作業系統需要的記憶體最少是32kb,我們知道,記憶體位址從0x0000開始編号,32kb的記憶體就是0x0000-0x7fff可以使用。8088晶片本身需要占用0x0000-0x03ff,用來儲存各種中斷處理程式的存儲位置(主引導記錄本身就是中斷信号 int 19h的處理程式),是以記憶體隻剩下0x0400-0x7fff可以使用,為了把盡量多的連續記憶體留給作業系統,主引導記錄就被放到了記憶體位址的尾部,由于一個扇區是512個位元組,主引導記錄本身也會産生資料,需要另外留出512位元組儲存。是以他的預留位置就變成了0x7fff-512-512+1=0x7c00。

由于512個位元組很有限,boot.imp做不了太多的事情,它能坐的最重要的一個事情就是加載grub2的另一個鏡像core.img,它是由lzma_decompress.img、diskboot.img、kernel.img和一系列子產品組成,功能比較豐富,能做很多事情。

boot.imp先加載的是core.img的第一個扇區。如果從硬碟啟動的話,這個扇區裡面是diskboot.img,對應的代碼是diskboot.s

boot.img将控制權交給diskboot.img後,diskboot.img的任務就是将core.img的其他部分加載進來,顯示解壓縮程式lzma_decompress.img,再往下是kernel.img,最後是各個子產品module對應的映像。這裡需要注意,它不是linux的核心,而是grub的核心。

lzma_decompress.img對應的代碼是startup_raw.s,本來kernel.img是壓縮過的,現在執行的時候,需要解壓縮。

在這之前,我們所有遇到過的程式都非常非常小,完全可以在實模式下運作,但是随着我們加載的東西越來越大,實模式這1m的位址空間實在放不下了,是以再真正的解壓縮之前,lzma_decopress.img做了一個重要的決定,就是調用real_to_prot,切換到保護模式,這樣就能在更大的尋址空間裡面,加載更多的東西。

從實模式切換到保護模式,第一項是啟用分段,就是再記憶體裡面履歷段描述符表,将寄存器裡面的段寄存器程式設計段選擇子,指向某個段描述符,這樣就能實作不同程序的切換了。第二項是啟動分頁。能夠管理的記憶體變大了,就需要将記憶體分成相等大小的塊。

保護模式需要做一項工作,那就是打開gate a20,也就是第21根位址線的控制線。在實模式8086下面,一共就20個位址線,可通路1m的位址空間。如果超過了這個限度,在保護模式下,第21根要起作用了,于是我們就需要打開gate a20.切換保護模式的函數data32 call real_to_prot 會打開gate a20,也就是第21根位址線的控制線。有了空間之後,接下來我們要對壓縮過的kernel.img進行解壓縮,然後跳轉到kernel.img開始運作。

kernel.img對應的代碼是startup.s以及一堆c檔案,再startup.s中會調用grub_main,這是grub kernel的主函數。在這個函數裡面,grub_load_config()開始解析,我們上面寫的那個grub.conf檔案裡的配置資訊。如果是正常啟動,grub_main最後會調用grub_command_execute("normal",0,0),最終會調用grub_normal_execute()函數。在這個函數裡面,grub_show_menu()會顯示出讓你選擇那個作業系統的清單。

一定你選擇了啟動某個作業系統,就要開始調用grub_menu_execute_entry(),開始解析并執行你選擇的那一項。

例如裡面的linux16指令,表示裝載指定的核心檔案,并傳遞核心啟動參數。于是grub_cmd_linux()函數會被調用,它會首先讀取linux核心鏡像頭部的一些資料結構,放到記憶體中的資料結構來,進行檢查。如果檢查通過,則會讀取整個linux核心鏡像到記憶體,如果配置檔案裡面還有initrd指令,用于為即将啟動的核心傳遞init tamdisk路徑。于是grub_cmd_initrd()函數會被調用,将initramfs加載到記憶體中來。當這些事情做完之後,grub_command_execute("boot",0,0)才開始真正地啟動核心。

繼續閱讀