1. 程式人生 > >計算機從加電到系統(Linux)啟動完成

計算機從加電到系統(Linux)啟動完成

0x0 背景



在我參加的面試和我面試別人、或者參加別人對別人的面試的事後經常遇到的一個問題就是:請從計算機加電開始描述一下計算機啟動到作業系統正式啟動起來的全過程。這是一個考驗對計算機體系結構和基本知識瞭解程度的問題。今天也就特別針對這個問題做一個回答,答案是基於80x86結構Linux 2.6及更高版本核心的為基準作業系統為例來回答的。

0x1 從加電到BIOS啟動



STEP 1 加電引導暫存器置位


這一過程指的是,計算機加電後,一個特殊電路會在CPU對應的一陣針腳產生一個邏輯電平,這個電平的值從針腳進入CPU後,會引發將暫存器(cs、eip等等)設定成特定值。

STEP 2 引導BIOS啟動


這一過程指的是系統從實體地址0xfffffff0處載入一段程式到只讀記憶體(ROM-> Read Only Memory),這個程式在80x86體系架構中一般稱為BIOS。

相關知識學習


  • MS-DOS的很多系統呼叫依賴BIOS
  • Linux進入保護模式後不再依賴BIOS,BIOS只能以真實模式執行。
真實模式的定址是20位匯流排定址,支援的定址空間為2^20,也就是1MB,保護模式目前在x86結構下,支援4GB定址;
實際區別主要是EIP中的虛地址到實地址轉化的區別:
真實模式是seg(eip地址)*16+offset(4為偏移量);
保護模式實EIP的16位地址代表頁面位置,一個頁在作業系統中都學習過是4KB,1M*4K = 4G,我相信很多人就此理解了為啥頁的大小要設計成4K;

0x2 BIOS引導作業系統映象的載入



STEP 1 檢查硬體


沒啥可說的,一般可認為是開機加電自檢,這個階段會顯示一些資訊,包括BIOS版本這一類的資訊。

STEP 2 初始化硬體


主要是避免IRQ先與I/O衝突,本階段最後會顯示所有PCI(匯流排--內部硬體通訊線路)裝置資訊。

STEP 3 搜尋作業系統


從軟盤、網路、磁碟、CD-ROM的主引導扇區上搜索。找到後加載注意到扇區的內容到0x00007c00的位置(RAM中),跳轉到這個地址,開始執行這段程式碼,這段程式叫做bootloader。

由於大小限制,linux的啟動程式GRUB(GRand Unified BootLoader)或者是LILO(LInux LOader)被分為兩部分。
第一部分就是載入到0x00007c00的這一段,他會把自己移動到0x00096a00的位置,建立真實模式棧(0x00098000~0x000969ff)
第一部分吧第二部分載入到0x00096c00開始的位置中。
以上的位置都是在RAM中。
第二部分搜尋磁碟上的OS景象,,把對應的扇區拷貝到RAM中執行:
1、首先把核心景象的第一個512B的部分從0x00090000處裝入RAM中;
2、把setup()函式程式碼段裝入0x00090200位置(RAM);
3、載入其他核心部分從高(0x00100000)或低(0x00010000)兩個位置任選其一載入到RAM中,分別稱為大映像核心和小映像核心;
4、跳轉到setup函式執行;

0x3 setup函式引導核心



這個過程主要是檢查和初始化硬體、雖然BIOS完成了相似的大部分工作,但是因為不依賴與BIOS,所以,還是重新初始化了硬體方面的事情;重要的過程有:

1、移動低裝載小映像核心的位置到0x00001000去,如果是高裝載則不移動;
2、建立IDT(臨時中斷描述符表)和GDT(臨時全域性描述符表);
3、如果需要,重置浮點單元(FPU);
4、重新編寫可程式設計終端控制器(PIC),遮蔽除IRQ2外的所有終端;
5、設定cr0暫存器到PE位,設定PG位為0,切換到保護模式,暫未啟用分頁;
6、跳轉到startup_32()函式;

0x4 核心建立階段



STEP 1 startup_32()函式


主要做的事情如下:

1、初始化段暫存器和一個臨時堆疊,並清零eflags暫存器所有為;
2、用0填充_edata 和_end符號標識的核心未初始化資料區;
3、呼叫decompress_kernel函式解壓核心映像;
【低裝載的情況解壓內容放在0x00100000位置開始的RAM中,高裝載的放在這後面的一個臨時緩衝區內,解壓後的核心就被移動到0x00100000位置】
4、跳轉到0x00100000位置開始執行,新的執行點事arch/i386/kernerlhead.s中的另一個startup_32函式。

STEP 2 再戰startup_32()函式


這個函式就是init程序(也是pid=0的0號程序)主要做了一下工作:

1、段暫存器初始化為最終值,核心的bss段填寫為0;
2、初始化臨時核心頁表,初始化pg0,使得線性地址一律對映到統一的實體地址上;
3、cr3暫存器儲存了頁全域性目錄,並設定cr0的pg位啟用分頁;
4、清零eflags,使用setup_idt函式用空的終端處理程式填充IDT;
5、從bios獲取的資料(系統引數和傳遞給os的引數)放入頁框1;
6、識別處理器、用GDT和IDT填充gdtr和idtr暫存器;
7、跳轉到start_kernel函式

0x5 核心完善階段start_kernel函式



這一階段最終完善了核心的初始化的後續工作,啟動了程式排程、記憶體管理等作業系統的功能,其中就涉及到了著名的函式sched_init函式,至此,系統完全啟動成功。