'「操作系統」內核引導過程詳解'

"

本文翻譯並且修改自:http://duartes.org/gustavo/blog/

微信公眾號:技術原理君

今天讓我們深入內核,去看看操作系統是怎麼啟動的吧。由於我習慣以事實為依據討論問題,所以文中會出現大量的鏈接引用Linux 內核2.6.25.6版的源代碼(源自Linux Cross Reference)。如果你熟悉C的 語法,這些代碼就會非常容易讀懂;即使你忽略一些細節,仍能大致明白程序都幹了些什麼。最主要的障礙在於對一些代碼的理解需要相關的背景知識,比如機器的 底層特性或什麼時候、為什麼它會運行。我希望能儘量給讀者提供一些背景知識。為了保持簡潔,許多有趣的東西,比如中斷和內存,文中只能點到為止了。在本文 的最後列出了Windows的引導過程的要點。

當Intel x86的引導程序運行到此刻時,處理器處於實模式(可以尋址1MB的內存),(針對現代的Linux系統)RAM的內容大致如下:

"

本文翻譯並且修改自:http://duartes.org/gustavo/blog/

微信公眾號:技術原理君

今天讓我們深入內核,去看看操作系統是怎麼啟動的吧。由於我習慣以事實為依據討論問題,所以文中會出現大量的鏈接引用Linux 內核2.6.25.6版的源代碼(源自Linux Cross Reference)。如果你熟悉C的 語法,這些代碼就會非常容易讀懂;即使你忽略一些細節,仍能大致明白程序都幹了些什麼。最主要的障礙在於對一些代碼的理解需要相關的背景知識,比如機器的 底層特性或什麼時候、為什麼它會運行。我希望能儘量給讀者提供一些背景知識。為了保持簡潔,許多有趣的東西,比如中斷和內存,文中只能點到為止了。在本文 的最後列出了Windows的引導過程的要點。

當Intel x86的引導程序運行到此刻時,處理器處於實模式(可以尋址1MB的內存),(針對現代的Linux系統)RAM的內容大致如下:

「操作系統」內核引導過程詳解

引導裝載完成後的RAM內容

引導裝載程序通過BIOS的磁盤I/O服務,已經把內核鏡像加載到內存當中。這個鏡像只是硬盤中內核文件(比如/boot/vmlinuz-2.6.22-14-server)的一份完全相同的拷貝。鏡像分為兩個部分:一個較小的部分,包含實模式的內核代碼,被加載到640KB內存邊界以下;另一部分是一大塊內核,運行在保護模式,被加載到低端1MB內存地址以上。

如上圖所示,之後的事情發生在實模式內核的頭部(kernel header)。這段內存區域用於實現引導裝載程序與內核之間的Linux引導協議。 此處的一些數據會被引導裝載程序讀取。這些數據包括一些令人愉快的信息,比如包含內核版本號的可讀字符串,也包括一些關鍵信息,比如實模式內核代碼的大 小。引導裝載程序還會向這個區域寫入數據,比如用戶選中的引導菜單項對應的命令行參數所在的內存地址。之後就到了跳轉到內核入口點的時刻。下圖顯示了內核 初始化代碼的執行順序,包括源代碼的目錄、文件和行號:

"

本文翻譯並且修改自:http://duartes.org/gustavo/blog/

微信公眾號:技術原理君

今天讓我們深入內核,去看看操作系統是怎麼啟動的吧。由於我習慣以事實為依據討論問題,所以文中會出現大量的鏈接引用Linux 內核2.6.25.6版的源代碼(源自Linux Cross Reference)。如果你熟悉C的 語法,這些代碼就會非常容易讀懂;即使你忽略一些細節,仍能大致明白程序都幹了些什麼。最主要的障礙在於對一些代碼的理解需要相關的背景知識,比如機器的 底層特性或什麼時候、為什麼它會運行。我希望能儘量給讀者提供一些背景知識。為了保持簡潔,許多有趣的東西,比如中斷和內存,文中只能點到為止了。在本文 的最後列出了Windows的引導過程的要點。

當Intel x86的引導程序運行到此刻時,處理器處於實模式(可以尋址1MB的內存),(針對現代的Linux系統)RAM的內容大致如下:

「操作系統」內核引導過程詳解

引導裝載完成後的RAM內容

引導裝載程序通過BIOS的磁盤I/O服務,已經把內核鏡像加載到內存當中。這個鏡像只是硬盤中內核文件(比如/boot/vmlinuz-2.6.22-14-server)的一份完全相同的拷貝。鏡像分為兩個部分:一個較小的部分,包含實模式的內核代碼,被加載到640KB內存邊界以下;另一部分是一大塊內核,運行在保護模式,被加載到低端1MB內存地址以上。

如上圖所示,之後的事情發生在實模式內核的頭部(kernel header)。這段內存區域用於實現引導裝載程序與內核之間的Linux引導協議。 此處的一些數據會被引導裝載程序讀取。這些數據包括一些令人愉快的信息,比如包含內核版本號的可讀字符串,也包括一些關鍵信息,比如實模式內核代碼的大 小。引導裝載程序還會向這個區域寫入數據,比如用戶選中的引導菜單項對應的命令行參數所在的內存地址。之後就到了跳轉到內核入口點的時刻。下圖顯示了內核 初始化代碼的執行順序,包括源代碼的目錄、文件和行號:

「操作系統」內核引導過程詳解

與體系結構相關的Linux內核初始化過程

對於Intel體系結構,內核啟動前期會執行arch/x86/boot/header.S文件中的程序。它是用匯編語言書寫的。一般說來彙編代碼在內核中很少出現,但常見於引導代碼。這個文件的開頭實際上包含了引導扇區代碼。早期的Linux不需要引導裝載程序就可以工作,這段代碼是從那個時候留傳下來的。現今,如果這個引導扇區被執行,它僅僅給用戶輸出一個"bugger_off_msg"之後就會重啟系統。現代的引導裝載程序會忽略這段遺留代碼。在引導扇區代碼之後,我們會看到實模式內核頭部(kernel header)最開始的15字節;這兩部分合起來是512字節,正好是Intel硬件平臺上一個典型的磁盤扇區的大小。

在這512字節之後,偏移量0x200處,我們會發現Linux內核的第一條指令,也就是實模式內核的入口點。具體的說,它在header.S:110,是一個2字節的跳轉指令,直接寫成了機器碼的形式0x3AEB。你可以通過對內核鏡像運行hexdump,並查看偏移量0x200處的內容來驗證這一點——這僅僅是一個對神志清醒程度的檢查,以確保這一切並不是在做夢。引導裝載程序運行完畢時就會跳轉執行這個位置的指令,進而跳轉到header.S:229執行一個普通的用匯編寫成的子程序,叫做start_of_setup。這個短小的子程序初始化棧空間(stack),把實模式內核的bss段清零(這個區域包含靜態變量,所以用0來初始化它們),之後跳轉執行一段又老又好的C語言程序:arch/x86/boot/main.c:122。

main()會處理一些登記工作(比如檢測內存佈局),設置顯示模式等。然後它會調用go_to_protected_mode()。然而,在把CPU置於保護模式之前,還有一些工作必須完成。有兩個主要問題:中斷和內存。在實模式中,處理器的中斷向量表總是從內存的0地址開始的,然而在保護模式中,這個中斷向量表的位置是保存在一個叫IDTR的CPU寄存器當中的。與此同時,從邏輯內存地址(在程序中使用)到線性內存地址(一個從0連續編號到內存頂端的數值)的翻譯方法在實模式和保護模式中是不同的。保護模式需要一個叫做GDTR的寄存器來存放內存全局描述符表的地址。所以go_to_protected_mode()調用了setup_idt() 和 setup_gdt(),用於裝載臨時的中斷描述符表和全局描述符表。

現在我們可以轉入保護模式啦,這是由另一段彙編子程序protected_mode_jump來完成的。這個子程序通過設定CPU的CR0寄存器的PE位來使能保護模式。此時,分頁功能還處於關閉狀態;分頁是處理器的一個可選的功能,即使運行於保護模式也並非必要。真正重要的是,我們不再受制於640K的內存邊界,現在可以尋址高達4GB的RAM了。這個子程序進而調用壓縮狀態內核的32位內核入口點startup_32。startup32會做一些簡單的寄存器初始化工作,並調用一個C語言編寫的函數decompress_kernel(),用於實際的解壓縮工作。

decompress_kernel()會打印一條大家熟悉的信息"Decompressing Linux…"(正在解壓縮Linux)。解壓縮過程是原地進行的,一旦完成內核鏡像的解壓縮,第一張圖中所示的壓縮內核鏡像就會被覆蓋掉。因此解壓後的內核也是從1MB位置開始的。之後,decompress_kernel()會顯示"done"(完成)和令人振奮的"Booting the kernel"(正在引導內核)。這裡"Booting"的意思是跳轉到整個故事的最後一個入口點,也是保護模式內核的入口點,位於RAM的第二個1MB開始處(偏移量0x100000,此值是由芬蘭Halti山巔之上的神靈授意給Linus的)。在這個神聖的位置含有一個子程序調用,名叫…呃…startup_32。但你會發現這一位是在另一個目錄中的。

這位startup_32的第二個化身也是一個彙編子程序,但它包含了32位模式的初始化過程:

  • 1、 它清理了保護模式內核的bss段。(這回是真正的內核了,它會一直運行,直到機器重啟或關機。)
  • 2、 為內存建立最終的全局描述符表。
  • 3、 建立頁表以便可以開啟分頁功能。
  • 4、 使能分頁功能。
  • 5、 初始化棧空間。
  • 6、 創建最終的中斷描述符表。
  • 7、 最後,跳轉執行一個體繫結構無關的內核啟動函數:start_kernel()。

下圖顯示了引導最後一步的代碼執行流程:

"

本文翻譯並且修改自:http://duartes.org/gustavo/blog/

微信公眾號:技術原理君

今天讓我們深入內核,去看看操作系統是怎麼啟動的吧。由於我習慣以事實為依據討論問題,所以文中會出現大量的鏈接引用Linux 內核2.6.25.6版的源代碼(源自Linux Cross Reference)。如果你熟悉C的 語法,這些代碼就會非常容易讀懂;即使你忽略一些細節,仍能大致明白程序都幹了些什麼。最主要的障礙在於對一些代碼的理解需要相關的背景知識,比如機器的 底層特性或什麼時候、為什麼它會運行。我希望能儘量給讀者提供一些背景知識。為了保持簡潔,許多有趣的東西,比如中斷和內存,文中只能點到為止了。在本文 的最後列出了Windows的引導過程的要點。

當Intel x86的引導程序運行到此刻時,處理器處於實模式(可以尋址1MB的內存),(針對現代的Linux系統)RAM的內容大致如下:

「操作系統」內核引導過程詳解

引導裝載完成後的RAM內容

引導裝載程序通過BIOS的磁盤I/O服務,已經把內核鏡像加載到內存當中。這個鏡像只是硬盤中內核文件(比如/boot/vmlinuz-2.6.22-14-server)的一份完全相同的拷貝。鏡像分為兩個部分:一個較小的部分,包含實模式的內核代碼,被加載到640KB內存邊界以下;另一部分是一大塊內核,運行在保護模式,被加載到低端1MB內存地址以上。

如上圖所示,之後的事情發生在實模式內核的頭部(kernel header)。這段內存區域用於實現引導裝載程序與內核之間的Linux引導協議。 此處的一些數據會被引導裝載程序讀取。這些數據包括一些令人愉快的信息,比如包含內核版本號的可讀字符串,也包括一些關鍵信息,比如實模式內核代碼的大 小。引導裝載程序還會向這個區域寫入數據,比如用戶選中的引導菜單項對應的命令行參數所在的內存地址。之後就到了跳轉到內核入口點的時刻。下圖顯示了內核 初始化代碼的執行順序,包括源代碼的目錄、文件和行號:

「操作系統」內核引導過程詳解

與體系結構相關的Linux內核初始化過程

對於Intel體系結構,內核啟動前期會執行arch/x86/boot/header.S文件中的程序。它是用匯編語言書寫的。一般說來彙編代碼在內核中很少出現,但常見於引導代碼。這個文件的開頭實際上包含了引導扇區代碼。早期的Linux不需要引導裝載程序就可以工作,這段代碼是從那個時候留傳下來的。現今,如果這個引導扇區被執行,它僅僅給用戶輸出一個"bugger_off_msg"之後就會重啟系統。現代的引導裝載程序會忽略這段遺留代碼。在引導扇區代碼之後,我們會看到實模式內核頭部(kernel header)最開始的15字節;這兩部分合起來是512字節,正好是Intel硬件平臺上一個典型的磁盤扇區的大小。

在這512字節之後,偏移量0x200處,我們會發現Linux內核的第一條指令,也就是實模式內核的入口點。具體的說,它在header.S:110,是一個2字節的跳轉指令,直接寫成了機器碼的形式0x3AEB。你可以通過對內核鏡像運行hexdump,並查看偏移量0x200處的內容來驗證這一點——這僅僅是一個對神志清醒程度的檢查,以確保這一切並不是在做夢。引導裝載程序運行完畢時就會跳轉執行這個位置的指令,進而跳轉到header.S:229執行一個普通的用匯編寫成的子程序,叫做start_of_setup。這個短小的子程序初始化棧空間(stack),把實模式內核的bss段清零(這個區域包含靜態變量,所以用0來初始化它們),之後跳轉執行一段又老又好的C語言程序:arch/x86/boot/main.c:122。

main()會處理一些登記工作(比如檢測內存佈局),設置顯示模式等。然後它會調用go_to_protected_mode()。然而,在把CPU置於保護模式之前,還有一些工作必須完成。有兩個主要問題:中斷和內存。在實模式中,處理器的中斷向量表總是從內存的0地址開始的,然而在保護模式中,這個中斷向量表的位置是保存在一個叫IDTR的CPU寄存器當中的。與此同時,從邏輯內存地址(在程序中使用)到線性內存地址(一個從0連續編號到內存頂端的數值)的翻譯方法在實模式和保護模式中是不同的。保護模式需要一個叫做GDTR的寄存器來存放內存全局描述符表的地址。所以go_to_protected_mode()調用了setup_idt() 和 setup_gdt(),用於裝載臨時的中斷描述符表和全局描述符表。

現在我們可以轉入保護模式啦,這是由另一段彙編子程序protected_mode_jump來完成的。這個子程序通過設定CPU的CR0寄存器的PE位來使能保護模式。此時,分頁功能還處於關閉狀態;分頁是處理器的一個可選的功能,即使運行於保護模式也並非必要。真正重要的是,我們不再受制於640K的內存邊界,現在可以尋址高達4GB的RAM了。這個子程序進而調用壓縮狀態內核的32位內核入口點startup_32。startup32會做一些簡單的寄存器初始化工作,並調用一個C語言編寫的函數decompress_kernel(),用於實際的解壓縮工作。

decompress_kernel()會打印一條大家熟悉的信息"Decompressing Linux…"(正在解壓縮Linux)。解壓縮過程是原地進行的,一旦完成內核鏡像的解壓縮,第一張圖中所示的壓縮內核鏡像就會被覆蓋掉。因此解壓後的內核也是從1MB位置開始的。之後,decompress_kernel()會顯示"done"(完成)和令人振奮的"Booting the kernel"(正在引導內核)。這裡"Booting"的意思是跳轉到整個故事的最後一個入口點,也是保護模式內核的入口點,位於RAM的第二個1MB開始處(偏移量0x100000,此值是由芬蘭Halti山巔之上的神靈授意給Linus的)。在這個神聖的位置含有一個子程序調用,名叫…呃…startup_32。但你會發現這一位是在另一個目錄中的。

這位startup_32的第二個化身也是一個彙編子程序,但它包含了32位模式的初始化過程:

  • 1、 它清理了保護模式內核的bss段。(這回是真正的內核了,它會一直運行,直到機器重啟或關機。)
  • 2、 為內存建立最終的全局描述符表。
  • 3、 建立頁表以便可以開啟分頁功能。
  • 4、 使能分頁功能。
  • 5、 初始化棧空間。
  • 6、 創建最終的中斷描述符表。
  • 7、 最後,跳轉執行一個體繫結構無關的內核啟動函數:start_kernel()。

下圖顯示了引導最後一步的代碼執行流程:

「操作系統」內核引導過程詳解

與體系結構無關的Linux內核初始化過程

start_kernel()看起來更像典型的內核代碼,幾乎全用C語言編寫而且與特定機器無關。這個函數調用了一長串的函數,用來初始化各個內核子系統和數據結構,包括調度器(scheduler),內存分區(memory zones),計時器(time keeping)等等。之後,start_kernel()調用rest_init(),此時幾乎所有的東西都可以工作了。rest_init()會創建一個內核線程,並以另一個函數kernel_init()作為此線程的入口點。之後,rest_init()會調用schedule()來激活任務調度功能,然後調用cpu_idle()使自己進入睡眠(sleep)狀態,成為Linux內核中的一個空閒線程(idle thread)。cpu_idle()會在0號進程(process zero)中永遠的運行下去。一旦有什麼事情可做,比如有了一個活動就緒的進程(runnable process),0號進程就會激活CPU去執行這個任務,直到沒有活動就緒的進程後才返回。

但是,還有一個小麻煩需要處理。我們跟隨引導過程一路走下來,這個漫長的線程以一個空閒循環(idle loop)作為結尾。處理器上電執行第一條跳轉指令以後,一路運行,最終會到達此處。從復位向量(reset vector)->BIOS->MBR->引導裝載程序->實模式內核->保護模式內核,跳轉跳轉再跳轉,經過所有這些雜七雜八的步驟,最後來到引導處理器(boot processor)中的空閒循環cpu_idle()。看起來真的很酷。然而,這並非故事的全部,否則計算機就不會工作。

在這個時候,前面啟動的那個內核線程已經準備就緒,可以取代0號進程和它的空閒線程了。事實也是如此,就發生在kernel_init()開始運行的時刻(此函數之前被作為線程的入口點)。kernel_init()的職責是初始化系統中其餘的CPU,這些CPU從引導過程開始到現在,還一直處於停機狀態。之前我們看過的所有代碼都是在一個單獨的CPU上運行的,它叫做引導處理器(boot processor)。當其他CPU——稱作應用處理器(application processor)——啟動以後,它們是處於實模式的,必須通過一些初始化步驟才能進入保護模式。大部分的代碼過程都是相同的,你可以參考startup_32,但對於應用處理器,還是有些細微的不同。最終,kernel_init()會調用init_post(),後者會嘗試啟動一個用戶模式(user-mode)的進程,嘗試的順序為:/sbin/init,/etc/init,/bin/init,/bin/sh。如果都不行,內核就會報錯。幸運的是init經常就在這些地方的,於是1號進程(PID 1)就開始運行了。它會根據對應的配置文件來決定啟動哪些進程,這可能包括X11 Windows,控制檯登陸程序,網絡後臺程序等。從而結束了引導進程,同時另一個Linux程序開始在某處運行。至此,讓我祝福您的電腦可以一直正常運行下去,不出毛病。

在同樣的體系結構下,Windows的啟動過程與Linux有很多相似之處。它也面臨同樣的問題,也必須完成類似的初始化過程。當引導過程開始後,一個最大的不同是,Windows把全部的實模式內核代碼以及一部分初始的保護模式代碼都打包到了引導加載程序(C:/NTLDR)當中。因此,Windows使用的二進制鏡像文件就不一樣了,內核鏡像中沒有包含兩個部分的代碼。另外,Linux把引導裝載程序與內核完全分離,在某種程度上自動的形成不同的開源項目。下圖顯示了Windows內核主要的啟動過程:

"

本文翻譯並且修改自:http://duartes.org/gustavo/blog/

微信公眾號:技術原理君

今天讓我們深入內核,去看看操作系統是怎麼啟動的吧。由於我習慣以事實為依據討論問題,所以文中會出現大量的鏈接引用Linux 內核2.6.25.6版的源代碼(源自Linux Cross Reference)。如果你熟悉C的 語法,這些代碼就會非常容易讀懂;即使你忽略一些細節,仍能大致明白程序都幹了些什麼。最主要的障礙在於對一些代碼的理解需要相關的背景知識,比如機器的 底層特性或什麼時候、為什麼它會運行。我希望能儘量給讀者提供一些背景知識。為了保持簡潔,許多有趣的東西,比如中斷和內存,文中只能點到為止了。在本文 的最後列出了Windows的引導過程的要點。

當Intel x86的引導程序運行到此刻時,處理器處於實模式(可以尋址1MB的內存),(針對現代的Linux系統)RAM的內容大致如下:

「操作系統」內核引導過程詳解

引導裝載完成後的RAM內容

引導裝載程序通過BIOS的磁盤I/O服務,已經把內核鏡像加載到內存當中。這個鏡像只是硬盤中內核文件(比如/boot/vmlinuz-2.6.22-14-server)的一份完全相同的拷貝。鏡像分為兩個部分:一個較小的部分,包含實模式的內核代碼,被加載到640KB內存邊界以下;另一部分是一大塊內核,運行在保護模式,被加載到低端1MB內存地址以上。

如上圖所示,之後的事情發生在實模式內核的頭部(kernel header)。這段內存區域用於實現引導裝載程序與內核之間的Linux引導協議。 此處的一些數據會被引導裝載程序讀取。這些數據包括一些令人愉快的信息,比如包含內核版本號的可讀字符串,也包括一些關鍵信息,比如實模式內核代碼的大 小。引導裝載程序還會向這個區域寫入數據,比如用戶選中的引導菜單項對應的命令行參數所在的內存地址。之後就到了跳轉到內核入口點的時刻。下圖顯示了內核 初始化代碼的執行順序,包括源代碼的目錄、文件和行號:

「操作系統」內核引導過程詳解

與體系結構相關的Linux內核初始化過程

對於Intel體系結構,內核啟動前期會執行arch/x86/boot/header.S文件中的程序。它是用匯編語言書寫的。一般說來彙編代碼在內核中很少出現,但常見於引導代碼。這個文件的開頭實際上包含了引導扇區代碼。早期的Linux不需要引導裝載程序就可以工作,這段代碼是從那個時候留傳下來的。現今,如果這個引導扇區被執行,它僅僅給用戶輸出一個"bugger_off_msg"之後就會重啟系統。現代的引導裝載程序會忽略這段遺留代碼。在引導扇區代碼之後,我們會看到實模式內核頭部(kernel header)最開始的15字節;這兩部分合起來是512字節,正好是Intel硬件平臺上一個典型的磁盤扇區的大小。

在這512字節之後,偏移量0x200處,我們會發現Linux內核的第一條指令,也就是實模式內核的入口點。具體的說,它在header.S:110,是一個2字節的跳轉指令,直接寫成了機器碼的形式0x3AEB。你可以通過對內核鏡像運行hexdump,並查看偏移量0x200處的內容來驗證這一點——這僅僅是一個對神志清醒程度的檢查,以確保這一切並不是在做夢。引導裝載程序運行完畢時就會跳轉執行這個位置的指令,進而跳轉到header.S:229執行一個普通的用匯編寫成的子程序,叫做start_of_setup。這個短小的子程序初始化棧空間(stack),把實模式內核的bss段清零(這個區域包含靜態變量,所以用0來初始化它們),之後跳轉執行一段又老又好的C語言程序:arch/x86/boot/main.c:122。

main()會處理一些登記工作(比如檢測內存佈局),設置顯示模式等。然後它會調用go_to_protected_mode()。然而,在把CPU置於保護模式之前,還有一些工作必須完成。有兩個主要問題:中斷和內存。在實模式中,處理器的中斷向量表總是從內存的0地址開始的,然而在保護模式中,這個中斷向量表的位置是保存在一個叫IDTR的CPU寄存器當中的。與此同時,從邏輯內存地址(在程序中使用)到線性內存地址(一個從0連續編號到內存頂端的數值)的翻譯方法在實模式和保護模式中是不同的。保護模式需要一個叫做GDTR的寄存器來存放內存全局描述符表的地址。所以go_to_protected_mode()調用了setup_idt() 和 setup_gdt(),用於裝載臨時的中斷描述符表和全局描述符表。

現在我們可以轉入保護模式啦,這是由另一段彙編子程序protected_mode_jump來完成的。這個子程序通過設定CPU的CR0寄存器的PE位來使能保護模式。此時,分頁功能還處於關閉狀態;分頁是處理器的一個可選的功能,即使運行於保護模式也並非必要。真正重要的是,我們不再受制於640K的內存邊界,現在可以尋址高達4GB的RAM了。這個子程序進而調用壓縮狀態內核的32位內核入口點startup_32。startup32會做一些簡單的寄存器初始化工作,並調用一個C語言編寫的函數decompress_kernel(),用於實際的解壓縮工作。

decompress_kernel()會打印一條大家熟悉的信息"Decompressing Linux…"(正在解壓縮Linux)。解壓縮過程是原地進行的,一旦完成內核鏡像的解壓縮,第一張圖中所示的壓縮內核鏡像就會被覆蓋掉。因此解壓後的內核也是從1MB位置開始的。之後,decompress_kernel()會顯示"done"(完成)和令人振奮的"Booting the kernel"(正在引導內核)。這裡"Booting"的意思是跳轉到整個故事的最後一個入口點,也是保護模式內核的入口點,位於RAM的第二個1MB開始處(偏移量0x100000,此值是由芬蘭Halti山巔之上的神靈授意給Linus的)。在這個神聖的位置含有一個子程序調用,名叫…呃…startup_32。但你會發現這一位是在另一個目錄中的。

這位startup_32的第二個化身也是一個彙編子程序,但它包含了32位模式的初始化過程:

  • 1、 它清理了保護模式內核的bss段。(這回是真正的內核了,它會一直運行,直到機器重啟或關機。)
  • 2、 為內存建立最終的全局描述符表。
  • 3、 建立頁表以便可以開啟分頁功能。
  • 4、 使能分頁功能。
  • 5、 初始化棧空間。
  • 6、 創建最終的中斷描述符表。
  • 7、 最後,跳轉執行一個體繫結構無關的內核啟動函數:start_kernel()。

下圖顯示了引導最後一步的代碼執行流程:

「操作系統」內核引導過程詳解

與體系結構無關的Linux內核初始化過程

start_kernel()看起來更像典型的內核代碼,幾乎全用C語言編寫而且與特定機器無關。這個函數調用了一長串的函數,用來初始化各個內核子系統和數據結構,包括調度器(scheduler),內存分區(memory zones),計時器(time keeping)等等。之後,start_kernel()調用rest_init(),此時幾乎所有的東西都可以工作了。rest_init()會創建一個內核線程,並以另一個函數kernel_init()作為此線程的入口點。之後,rest_init()會調用schedule()來激活任務調度功能,然後調用cpu_idle()使自己進入睡眠(sleep)狀態,成為Linux內核中的一個空閒線程(idle thread)。cpu_idle()會在0號進程(process zero)中永遠的運行下去。一旦有什麼事情可做,比如有了一個活動就緒的進程(runnable process),0號進程就會激活CPU去執行這個任務,直到沒有活動就緒的進程後才返回。

但是,還有一個小麻煩需要處理。我們跟隨引導過程一路走下來,這個漫長的線程以一個空閒循環(idle loop)作為結尾。處理器上電執行第一條跳轉指令以後,一路運行,最終會到達此處。從復位向量(reset vector)->BIOS->MBR->引導裝載程序->實模式內核->保護模式內核,跳轉跳轉再跳轉,經過所有這些雜七雜八的步驟,最後來到引導處理器(boot processor)中的空閒循環cpu_idle()。看起來真的很酷。然而,這並非故事的全部,否則計算機就不會工作。

在這個時候,前面啟動的那個內核線程已經準備就緒,可以取代0號進程和它的空閒線程了。事實也是如此,就發生在kernel_init()開始運行的時刻(此函數之前被作為線程的入口點)。kernel_init()的職責是初始化系統中其餘的CPU,這些CPU從引導過程開始到現在,還一直處於停機狀態。之前我們看過的所有代碼都是在一個單獨的CPU上運行的,它叫做引導處理器(boot processor)。當其他CPU——稱作應用處理器(application processor)——啟動以後,它們是處於實模式的,必須通過一些初始化步驟才能進入保護模式。大部分的代碼過程都是相同的,你可以參考startup_32,但對於應用處理器,還是有些細微的不同。最終,kernel_init()會調用init_post(),後者會嘗試啟動一個用戶模式(user-mode)的進程,嘗試的順序為:/sbin/init,/etc/init,/bin/init,/bin/sh。如果都不行,內核就會報錯。幸運的是init經常就在這些地方的,於是1號進程(PID 1)就開始運行了。它會根據對應的配置文件來決定啟動哪些進程,這可能包括X11 Windows,控制檯登陸程序,網絡後臺程序等。從而結束了引導進程,同時另一個Linux程序開始在某處運行。至此,讓我祝福您的電腦可以一直正常運行下去,不出毛病。

在同樣的體系結構下,Windows的啟動過程與Linux有很多相似之處。它也面臨同樣的問題,也必須完成類似的初始化過程。當引導過程開始後,一個最大的不同是,Windows把全部的實模式內核代碼以及一部分初始的保護模式代碼都打包到了引導加載程序(C:/NTLDR)當中。因此,Windows使用的二進制鏡像文件就不一樣了,內核鏡像中沒有包含兩個部分的代碼。另外,Linux把引導裝載程序與內核完全分離,在某種程度上自動的形成不同的開源項目。下圖顯示了Windows內核主要的啟動過程:

「操作系統」內核引導過程詳解

Windows內核初始化過程

自然而然的,Windows用戶模式的啟動就非常不同了。沒有/sbin/init程序,而是運行Csrss.exe和Winlogon.exe。Winlogon會啟動Services.exe(它會啟動所有的Windows服務程序)、Lsass.exe和本地安全認證子系統。經典的Windows登陸對話框就是運行在Winlogon的上下文中的。

到這裡文章就要快結束了。感謝每一位讀者,感謝你們的反饋。我很抱歉,有些內容只能點到為止;我打算把它們留在其他文章中深入討論,並儘量保持文章的長度適合blog的風格。下次我打算定期的撰寫關於"Software Illustrated"的文章,就像本系列一樣。最後,給大家一些參考資料:

  • 最好也最重要的資料是實際的內核代碼,Linux或BSD的都成。
  • Intel出版的傑出的軟件開發人員手冊,你可以免費下載到。
  • 《理解Linux內核》是本好書,其中討論了大量的Linux內核代碼。這書也許有點過時有點枯燥,但我還是將它推薦給那些想要與內核心意相通的人們。《Linux設備驅動程序》讀起來會有趣得多,講的也不錯,但是涉及的內容有些侷限性。最後,網友Patrick Moroney推薦Robert Love所寫的《Linux內核開發》,我曾聽過一些對此書的正面評價,所以還是值得列出來的。
  • 對於Windows,目前最好的參考書是《Windows Internals》,作者是David Solomon和Mark Russinovich,後者是Sysinternals的知名專家。這是本特棒的書,寫的很好而且講解全面。主要的缺點是缺少源代碼的支持。
"

相關推薦

推薦中...