Linux啓動過程簡述

BIOS加載bootloader階段

計算機接電之後第一個啟動的是BIOS,因為BIOS是存儲在主板上的一個小程序,空間有限代碼量少因此功能受限。要經過一步步的控制權轉移,最後轉移給功能更加強大的操作系統。

BIOS對系統做一些簡單的初始化工作,然後把控制權交給MBR。約定MBR(Main Boot Record)存在於整個硬盤最開始的扇區。每個扇區都是512字節,MBR引導扇區的內容是:

Linux啟動過程簡述

  • 446字節的引導程序及參數

  • 64字節的分區表(每個分區表項16字節,因此只能有4個主分區)

  • 2字節的結束標誌0x55和0xaa

0x55和0xaa是MBR結束標誌,0x7c00是MBR被加載在內存中的位置。

BIOS結束自己的工作以後,需要把控制權交給MBR,過程是:BIOS找到MBR並且把MBR加載在內存中,跳轉到該位置。

為了方便BIOS尋找,約定好0盤0道1扇區(第一個扇區)用來存儲MBR。MBR內容佔510個字節,剩餘兩個字節用作結束標誌。BIOS不用遍歷這個扇區,只要檢測到有這個標誌就認為這個扇區有可以運行的MBR存在,否則BIOS會報錯。這兩個結束標誌是0x55和0xaa。

關於0x7c00,這是一個歷史遺留問題。MBR的名稱是主引導記錄,MBR主要工作是:建立分區表、引導其他更復雜的程序。8086cpu要求0~30k位置用來存儲中斷向量表,DOS1.0要求的最小內存是32k,因此MBR必須存儲在錢32k中,中斷向量表長度不一定,MBR希望自己有足夠的空間運行(MBR也是程序,程序運行需要用到棧,MBR預估自己需要用到512字節的自身存儲和512字節的堆棧空間),同時儘可能多的留給向量表足夠的空間。綜上,MBR存儲在32k的最後1k空間,也就是從0x7c00開始。

MBR的主要工作是什麼呢?

MBR加載bootloader

執行完了自己的工作,需要把控制權交出去。MBR遍歷分區表中的4個分區,查找操作系統加載器,找到後把CPU交給加載器。MBR怎麼知道加載器存在哪個分區呢?分區表項到 第一個字節就是活動分區標誌,如果該分區存儲了加載器,該標誌被置為0x80,否則是0.MBR只需要遍歷分區表就能找到下一棒選手應該是誰。這裡為了方便MBR找到活動分區上的(因為程序跳轉是需要指明跳轉地址的),約定好加載器就存儲在各分區的開始扇區,這個扇區被稱為操作系統引導扇區也稱為,這個OBR就是我們常見的x86平臺上的grub或者arm平臺上的uboot,這個程序負責的主要工作是加載kernel image 並解壓縮。

bootloader加載kernel

CRUB會根據需求顯示一個可用的內核列表(定義在/etc/grub.con,以及/etc/grub/menu.lst和/etc/grub.conf的軟連接)。你可以選中一個內核,並且可以用附加的內核參數改進它。另外,你還能通過shell終端命令行的方式手動控制整個啟動過程。

Bootloader完成CPU、RAM、網卡等信息的初始化,建立內存的映射關係。在外存上找到kernel image,啟動IO讀入內存。一般kernel image使用zlib壓縮為zImage或者bzImage格式,讀取了image以後,在內核鏡像的頭部,有一個小型的routine,做一些簡單的硬件設置,解壓縮至高端內存(輸出解壓縮提示信息:Decompressing Linux...),加載initrd(注意:這個在後面會作為臨時根文件系統存在)進內存,執行kernel通過

./arch/i386/boot/head

kernel初始化

Bootloader在把控制權交給kernel時候,會傳遞一些參數。這個參數可以是用戶通過bootloader設置的(例如:grub的配置文件grub.conf),或者是bootloader自己檢測到的硬件信息(例如:根設備標識、頁面大小、內核需要的命令信息)。

內核先是設置系統的狀態(此部分通過彙編程序head.S完成):查看CPU類型檢查kernel是否支持此種類型,查找體系類型,初始化寄存器,創建頁表,開啟MMU,建立IDT、GDT、LDT,跳轉到入口函數start_kernel(main.c中,是第一個C函數)處。

在main開始運行的時,PC處於一個實模式,CPU執行一些必要操作後跳轉到保護模式。這裡,有兩個很重要的問題:中斷和內存。在實模式下,處理器的中斷向量表始終位於存儲地址0(最開始的位置),而在保護模式下,中斷向量表的位置存儲在稱為IDTR的CPU寄存器中。同是邏輯地址到線性地址轉換實模式和保護模式是不一樣的,保護模式需要一個稱為GDTR的寄存器來加載內存的全局描述符表的地址。因此,go_to_protected_mode調用setup_idtsetup_gdt來安裝臨時中斷描述符表和全局描述符表。

現在我們準備好了跳轉到保護模式了,調用protected_mode_jump,這個函數設置CR0寄存器的PE位,打開A19地址線等等操作跳轉到保護模式。注意此時分頁還是被禁用的,因為暫時還不需要分頁。現在,內存終於可以尋址到4GB,調用32位內核入口點startup_32

startup_32調用decompress_kernel解壓縮真正的內核,並在顯示器上打印信息Decompress Linux...。解壓縮過的kernel此時會覆蓋解壓縮之前的內核鏡像。解壓縮結束以後,清楚BSS段設置最終的GDT和IDT,構建頁表,打開分頁初始化堆棧,最後跳轉到start_kernel。 上面的過程如下所示:

Linux啟動過程簡述

start_kernel

執行各種初始化函數:初始化IRQ、時鐘、CPU、內存、VFS、中斷向量表、內核子系統等等。最後調用

rest_init

,這幾乎是start_kernel的全部工作,

rest_init

創建一個內核線程,執行

kernel_init

rest_init

調用

schedule

啟動任務調度,調用

cpu_idle

cpu_idle

永遠運行,只有當進程就緒列表中有就緒進程時候,cpu_idle才會被從CPU上拉下來,當沒有進程時候,這個空閒進程又被拉起來執行,這就是進程0。進程0漫長的工作終於結束了,從BIOS、MBR、Bootloader最後到kernel。雖然進程0從啟動的舞臺上退出了,這並沒有意味著kernel啟動完成了。

進程0剛剛創建了一個內核線程執行

kernel_init

,還記得嗎?我們現在看看這個線程的運行。

kernel_init

負責初始化啟動剩下的CPU,從啟動分析到現在,我們只有一個CPU在運行注意到了嗎?這個CPU我們成為引導CPU,剩下的應用CPU初始化依然需要從實模式開始。最後

kernel_init

調用

init_post

,這個函數嘗試執行一下用戶進程:

/sbin/init

/etc/init

/bin/init

/bin/sh

,如果全部失敗,產生kernel panic。如果上述程序存在,創建進程1運行(這個進程是所有用戶進程的父進程)。過程如下圖所示:

Linux啟動過程簡述

init讀取的第一個文件是

/etc/inittab

,從這裡init決定了我們的Linux操作系統的運行級別。Linux系統有7個運行級別(runlevel):

level 0:系統停機狀態

level 1:單用戶工作狀態

level 2:多用戶狀態

level 3:完全多用戶狀態

level 4:保留

level 5:X11

level 6:reboot

Init從/etc/fstab

文件中查找分區表信息,用真正的根文件系統替換initrd。Init然後啟動默認運行級別的/etc/init.d目錄中指定的所有服務或者腳本。這是所有服務由init逐個初始化的步驟。在這個過程中,一次一個服務由init啟動,所有守護程序在後臺運行,init繼續管理它們。

至此,內核啟動基本結束了。


分享到:


相關文章: