原文:https://manybutfinite.com/post/kernel-boot-process/
翻譯:RobotCode俱樂部
如果你熟悉類似C的文法,那麼源代碼是非常易讀的,即使你錯過了一些細節,你也能大緻了解正在發生的事情。主要的障礙是缺少對代碼的上下文環境的了解,比如它什麼時候運作,為什麼運作,或者機器的底層特性。這裡我希望提供一些背景知識。
在Intel x86引導的故事中,此時處理器是在實模式下運作的,能夠處理1 MB的記憶體,RAM對于現代Linux系統是這樣的:
引導加載程式完成後的RAM内容
核心鏡像已經由BIOS中的引導加載程式中的I/O服務加載到記憶體中。此鏡像是包含核心檔案的副本,例如/boot/vmlinuz-2.6.22-14-server。鏡像被分成兩部分:一小部分包含實模式核心代碼,加載在640K以下;核心的大部分在保護模式下運作,加載在第一個兆位元組記憶體之後。
該操作從上面所示的實模式核心頭部開始。這個記憶體區域用于在 引導加載程式 和 核心 之間實作Linux引導協定。其中一些值是引導加載程式在執行其工作時讀取的。比如包含核心版本的可讀字元串,也包含其他重要資訊,比如實模式核心塊的大小。
引導加載程式還将值寫入該區域,例如使用者在引導菜單中給出的指令行參數的記憶體位址。引導加載程式完成後,它已經填充了核心頭部所需的所有參數。現在是跳到核心入口點的時候了。下圖顯示了核心初始化的程式流程,以及源目錄、檔案和行号:
特定于體系結構的Linux核心初始化
Intel架構的早期核心啟動位于檔案arch/x86/boot/header.S中。它是用彙編語言編寫的,這在核心中很少見,但在引導代碼中很常見。這個檔案的開頭實際上包含引導扇區代碼,這是考慮到在沒有引導加載程式的情況下Linux也可以工作。現在,這個引導扇區如果執行,隻會向使用者列印“bugger_off_msg”并重新啟動。現代的引導加載程式忽略了這些遺留代碼。在引導扇區代碼之後,我們有實際模式核心頭的前15個位元組;這兩部分加起來是512位元組,相當于英特爾硬體上一個典型磁盤扇區的大小。
在這512位元組之後,即在偏移0x200處,我們找到作為Linux核心的一部分運作的第一條指令:實模式入口點。它在head.S:110,它是一個2位元組的跳轉,直接用機器碼寫成0x3aeb。你可以通過在核心映像上運作hexdump并檢視偏移量處的位元組來驗證這一點。引導加載程式在完成時跳到這個位置,然後跳到header.S:229,其中我們有一個正常的彙程式設計式
start_of_setup。這個簡短的程式設定一個堆棧,将實模式核心的bss段初始化為零,然後跳到arch/x86/boot/main.c:122上的舊C代碼。
main()做一些雜活,比如檢測記憶體布局、設定視訊模式等。然後調用
go_to_protected_mode()。然而,在将CPU設定為保護模式之前,必須完成一些任務。有兩個主要問題:
中斷和記憶體。在實模式下,處理器的中斷向量表總是位于記憶體位址0,而在受保護模式下,中斷向量表的位置存儲在一個名為IDTR的CPU寄存器中。同時,在實模式和保護模式之間,邏輯記憶體位址(程式操作的)到線性記憶體位址(從0到記憶體頂部的原始數字)的轉換是不同的。保護模式要求使用全局描述符表的位址加載名為GDTR的寄存器。是以
go_to_protected_mode()調用
setup_idt()和
setup_gdt()來安裝臨時中斷描述符表和全局描述符表。
現在,我們準備進入保護模式,這是由另一個彙程式設計式
protected_mode_jump完成的。這個程式通過在CR0 CPU寄存器中設定PE位來啟用保護模式。此時,我們正在禁用分頁的情況下運作;分頁是處理器的一個可選特性,即使在保護模式下也不需要。重要的是,我們不再局限于640K屏障,現在可以處理最多4GB的RAM。這個程式然後調用32位核心入口點,這是壓縮核心的
startup_32。這個程式執行一些基本的寄存器初始化,并調用一個C函數
decompress_kernel()來執行實際的解壓縮。
decompress_kernel()列印熟悉的“Decompressing Linux...”。解壓縮是在适當的位置進行的,一旦解壓縮完成,未壓縮的核心映像就會覆寫第一個圖中所示的壓縮映像。是以,未壓縮的内容也從1MB開始。然後,
decompress_kernel()輸出“done.”和令人欣慰的“Booting the kernel.”。通過“Booting”,它意味着跳轉到整個流程的最後一個入口點,這是上帝在Halti山頂親自賜予Linus的,它是RAM (0x100000)的第二個兆位元組(MB)開始時的保護模式核心入口點。這個神聖的位置包含一個程式
startup_32。但是這個在不同的目錄下。
startup_32的第二個版本也是一個彙程式設計式,但它包含32位模式初始化。它清除保護模式核心的bss段(這是真正的核心,将運作,直到機器重新啟動或關閉),為記憶體建立最終的全局描述符表,建構頁表以支援分頁機制。初始化一個堆棧,建立最終的中斷描述符表,跳轉到
獨立于體系結構的核心啟動函數
start_kernel()。下圖顯示了引導的最後一段過程:
獨立于體系結構的Linux核心初始化
start_kernel()看起來更像典型的核心代碼,它幾乎完全獨立于C和機器類型。該函數是對各種核心子系統和資料結構的初始化的調用,包括排程系統、記憶體管理、定時器等等。
start_kernel()然後調用
rest_init(),此時幾乎所有的系統都在工作。
rest_init()建立一個核心線程,該線程傳遞另一個函數
kernel_init()作為入口點。然後
rest_init()調用
schedule()來啟動任務排程,并通過調用
cpu_idle()進入睡眠狀态,
cpu_idle()是Linux核心的空閑線程。
cpu_idle()永遠運作,程序0也是如此,程序0調用它。
當有工作要做時——一個可運作程序——程序0被切出CPU,隻在沒有可運作程序可用時他才會被運作。但這是給我們的驚喜。這個空閑循環是我們整個引導流程的結束點,它是處理器在啟動後執行的第一次跳轉的最終後代。所有這些混亂,從重置向量表到BIOS,到MBR,到引導加載程式,到實模式核心,到保護模式核心,所有這些都在這裡引導,一個接一個地跳轉,最後在引導處理器的空閑循環
cpu_idle()中結束。這真的很酷。然而,這不是全部,否則計算機将不工作。
此時,先前啟動的核心線程已經準備好啟動,替換程序0及其空閑線程。是以,當
kernel_init()被指定為線程入口點時,它就會開始運作。
kernel_init()負責初始化系統中自引導以來已停止的其餘CPU。到目前為止,我們看到的所有代碼都是在一個名為引導處理器的CPU中執行的。當其他CPU(稱為應用程式處理器)啟動時,它們以實模式出現,并且必須運作幾個初始化過程。正如你在
startup_32的代碼中所看到的那樣,許多代碼都是通用的,但是新出現的應用程式處理器會稍微分叉。最後,
kernel_init()調用
init_post(),後者嘗試按以下順序執行使用者模式流程:/sbin/init、/etc/init、/bin/init和/bin/sh。如果全部失敗,核心将會panic。幸運的是,init通常是存在的,并開始作為PID 為1的程序運作。它檢查配置檔案以确定要啟動哪些程序,這些程序可能包括X11 Windows、用于登入控制台的程式、網絡守護程序,等等。這樣,當另一個Linux機器在某個地方開始運作時,引導過程就結束了。希望你的正常運作時間長而不受幹擾。
考慮到通用架構,Windows的流程在許多方面是相似的。遇到了許多相同的問題,必須進行類似的初始化。在引導時,最大的差別之一是Windows将所有實模式的核心代碼和一些初始保護模式代碼打包到引導加載程式本身中(C:NTLDR)。是以,Windows不再在同一個核心映像中有兩個區域,而是使用不同的二進制映像。加上Linux完全分離引導加載程式和核心;在某種程度上,這自動地從開源過程中消失了。下圖顯示了Windows核心的主要部分:
Windows核心初始化
Windows使用者模式的啟動自然非常不同。沒有/sbin/init,隻有Csrss.exe和Winlogon.exe。Winlogon.exe生成Services.exe,之後啟動所有Windows Services、Lsass.exe、本地安全認證子系統。經典的Windows登入對話框在Winlogon上下文中運作。
這是本Linux引導系列的最後一部分。感謝大家的閱讀和回報。我很抱歉有些事情得到了表面的解釋,沒有深入講解,部落格大小的内容隻能滿足這麼多。同時,這裡有一些參考資源:
- 最好、最重要的資源是真正核心的源代碼,無論是Linux還是BSD的版本。
- 英特爾釋出的Software Developer's Manuals,你可以免費下載下傳。
- 《深入了解Linux核心》是一本好書,介紹了很多Linux核心源代碼。它使用的核心版本已經過時了,但我還是要推薦給任何想要品嘗核心的人。《Linux裝置驅動》更有趣,但介紹範圍有限。最後,Patrick Moroney在本文的評論中建議Robert Love《Linux核心的設計與實作》。我聽過其他對這本書的正面評價,是以它聽起來值得一讀。
- 對于Windows,目前最好的參考是David Solomon和Mark Russinovich的Windows Internals,後者因Sysinternals而出名。這是一本偉大的書,寫得很好,也很透徹。主要缺點是缺乏源代碼。
由于本人水準有限,翻譯必然有很多不妥的地方,歡迎指正。
同時,歡迎關注下方微信公衆号,一起交流學習:)