linux啓動流程--Android內核部分啓動流程觸類旁通(轉載)

先給大家來個圖來總體認識一下Linux內核的引導過程,然後詳細介紹。

          

     這個圖是X86 PC上的Linux 內核的引導過程,在嵌入式系統上的Linux內核的引導過程基本類似。不同只是在X86 PC上有一個從BIOS轉移到Bootlloader的過程,嵌入式系統往往是復位後就直接運行Bootloader

     從圖上可以看出,在系統啓動進入與Linux相關代碼之前,會經歷如下階段:

     1)當系統上電或復位時,CPU會將程序計數器指針賦值爲一個特定地址0XFFF0,並執行該地址裏存放的指令。在PC中,該地址是存放在BIOS中,它保存在主板上的ROMFlash中。

     2)BIOS運行時安裝CMOS的設置定義的啓動設備順利來搜索處於活動狀態且可以引導的設備。若從硬盤啓動,BIOS會將硬盤MBR(主引導記錄)中的內容加載到RAM。當MBR被加載到RAM中之後,BIOS就會將控制權交給MBR

     3)主引導加載程序查找並加載次引導加載程序。它在分區表中查找活動分區,當找到一個活動分區時,掃描分區表中的其他分區,一確保它們都不是活動的。當這個過程驗證完成之後,就將活動分區的引導記錄從這個設備中讀入RAM並執行它。

     4)次引導加載程序加載Linux內核和可選的初始RAM磁盤,將控制權交給Linux內核源代碼。

     5)運行被加載的內核,並啓用用戶空間應用程序。

     下面都上邊加到linux內核的步驟5進行更詳細的分析,它主要是完成啓動內核並運行用戶空間的init進程的功能。

     當內核映像被加載到RAM之後,Bootloader的控制權被釋放。內核映像並不是可直接執行的目標代碼,而是一個壓縮過的zImage(小內核)或者bzImage(大內核)。當bzImage(用於i386映像)被調用時它從/arch/i386/boot/head.Sstart彙編例程開始執行。這個例程進行一些基本的硬件配置,並調用/arch/i386/boot/compressed/head.S中的 startup_32例程。它設置一個基本的的運行環境(如堆棧)後清除BSS段,調用/arch/i386/boot/compressed/misc.c中的decompressed_kernel()解壓縮內核。如下圖:

          

      內核被解壓縮到內存中之後會再調用/arch/i386/kernel/head.S文件中的startup_32例程,這個新的例程會初始化頁表,並啓用內存分頁機制,接着爲任何可選的浮點單元(FPU)檢測CPU的類型,並將其存儲起來供以後使用。

      這些都在做完以後,/init/main.c中的start_kernel()函數被調用,進入到與體系結構無關的Linux內核部分。

      start_kernel()函數會調用一系列初始化函數來設置終端,執行進一步的內存配置。之後,/arch/i386/kernel/process.ckernel_thread()被調用一啓動第一個核心線程,該線程執行init()函數,而原執行序列會調用cpu_idle(),等待調度。

      作爲核心線程的init()函數完成外設及其驅動程序的加載和初始化,掛載根文件系統。init()打開/dev/console設備,重定向stdin,stdoutstderr到控制檯。之後,它搜索文件系統中的init程序(當然也可以用"init="命令行參數制定init程序),並使用execve()系統調用執行init程序。其中搜索的順序爲/sbin/init,/etc/init,/bin/init/bin/sh。在嵌入式系統中,多數情況下,可以給內核傳入一個簡單的shell腳本來啓動必需的嵌入式應用程序。

      走到這裏,終於把linux內核的引導和啓動過程給走完了,而init()對應的由start_kernel()創建的第一個線程也進入到用戶模式。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Linux0.11僅支持x86架構。它的內核引導啓動程序在文件夾boot內,共有三個彙編代碼文件。按照啓動流程依次是:

    (1)bootsect.s。boot是啓動引導的意思,sect即sector,是扇區的意思,二者合在一起啓動引導扇區。這是磁盤引導程序。

    (2)setup.s

    (3)head.s

 

    前兩個彙編程序採用近似Intel的彙編語言語法,第三個採用GNU的AT&T語法。必須採用相應的編譯器才能進行編譯。

 

    系統上電後,Intel的CPU自動進入實模式,CS:IP=FFFF:0000,也就是說CPU在上電或者復位時總是執行物理地址0xFFFF0處的代碼。這個地址默認是ROM-BIOS中的地址,在嵌入式系統來說,這裏存放的是一級bootloader的執行代碼。它完成的操作就是執行系統自檢,在物理地址0x00000處開始初始化中斷向量表。最後,將啓動盤的第一個扇區(0磁道,0磁頭,引導扇區)裝載到物理地址0x07C00處,並且跳轉到這裏開始執行此處的代碼。而此處代碼的作用是將自己移到物理地址0x90000處,因爲第一個扇區的代碼共512KB=0x0200,所以複製過去的範圍就是0x90000-0x901FF。然後,把啓動設備中第二個扇區開始的連續4個扇區共2KB代碼(setup.s)讀入到物理地址0x90200處,內核的其他部分(system模塊)則被讀入到物理地址0x10000處。因爲當時system模塊的長度不會超過0x80000字節大小,所以不會覆蓋0x90000處開始的bootsect和setup模塊。裝入完成後,控制轉向setup.s。

 

   setup.s首先設置一些硬件設備,然後將內核文件從0x10000處移至0x00000處。系統轉入保護模式,執行0x00000處的代碼。內核文件的頭部是用彙編語言編寫的代碼,即head.s。

 

   head.s會把IDT(中斷向量表)、GDT(全局段描述符表)、LDT(局部段描述符表)的首地址裝入到相應的寄存器裏,初始化處理器和協處理器,設置好分頁,最後調用init/main.c中的main()程序。

 

這個流程跟嵌入式系統中的bootloader要完成的功能是一致的。在AT91RM9200上移植U-boot時,也是要有三個文件load.bin、boot.bin、u-boot.bin。它們共同點都是先從上電起始位置(硬件設置)跳轉到ROM的一級bootloader處,經過處理,跳轉到二級bootloader處。完成引導,就可以啓動內核,掛載文件系統了。下面的工作是詳細分析三個彙編程序完成的工作,對整個啓動過程有進一步的瞭解。以後還要研究一下AT91RM9200的bootloader部分,爭取自己寫一個比較簡單的bootloader。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

開機過程指的是從打開計算機電源直到LINUX顯示用戶登錄畫面的全過程。分析LINUX開機過程也是深入瞭解LINUX核心工作原理的一個很好的途徑。


啓動第一步--加載BIOS

當你打開計算機電源,計算機會首先加載BIOS信息,BIOS信息是如此的重要,以至於計算機必須在最開始就找到它。這是因爲BIOS中包含了CPU的相關信息、設備啓動順序信息、硬盤信息、內存信息、時鐘信息、PnP特性等等。在此之後,計算機心裏就有譜了,知道應該去讀取哪個硬件設備了。在BIOS將系統的控制權交給硬盤第一個扇區之後,就開始由Linux來控制系統了。

啓動第二步--讀取MBR

硬盤上第0磁道第一個扇區被稱爲MBR,也就是Master Boot Record,即主引導記錄,它的大小是512字節,可裏面卻存放了預啓動信息、分區表信息。可分爲兩部分:第一部分爲引導(PRE-BOOT)區,佔了446個字節;第二部分爲分區表(PARTITION PABLE),共有66個字節,記錄硬盤的分區信息。預引導區的作用之一是找到標記爲活動(ACTIVE)的分區,並將活動分區的引導區讀入內存。

系統找到BIOS所指定的硬盤的MBR後,就會將其複製到0×7c00地址所在的物理內存中。其實被複制到物理內存的內容就是Boot Loader,而具體到你的電腦,那就是lilo或者grub了。

啓動第三步--Boot Loader

Boot Loader 就是在操作系統內核運行之前運行的一段小程序。通過這段小程序,我們可以初始化硬件設備、建立內存空間的映射圖,從而將系統的軟硬件環境帶到一個合適的狀態,以便爲最終調用操作系統內核做好一切準備。通常,Boot Loader是嚴重地依賴於硬件而實現的,不同體系結構的系統存在着不同的Boot Loader

Linux的引導扇區內容是採用彙編語言編寫的程序,其源代碼在arch/i386/boot(不同體系的CPU有其各自的boot目錄),有4個程序文件:

bootsect.S,引導扇區的主程序,彙編後的代碼不超過512字節,即一個扇區的大小
setup.S,引導輔助程序
edd.S,輔助程序的一部分,用於支持BIOS增強磁盤設備服務
video.S,輔助程序的另一部分,用於引導時的屏幕顯示

Boot Loader有若干種,其中GrubLilospfdisk是常見的Loader,這裏以Grub爲例來講解吧。

系統讀取內存中的grub配置信息(一般爲menu.lstgrub.lst),並依照此配置信息來啓動不同的操作系統。

啓動第四步--加載內核

根據grub設定的內核映像所在路徑,系統讀取內存映像,並進行解壓縮操作。此時,屏幕一般會輸出“Uncompressing Linux”的提示。當解壓縮內核完成後,屏幕輸出“OK, booting the kernel”

系統將解壓後的內核放置在內存之中,並調用start_kernel()函數來啓動一系列的初始化函數並初始化各種設備,完成Linux核心環境的建立。至此,Linux內核已經建立起來了,基於Linux的程序應該可以正常運行了。

start_kenrel()定義在init/main.c中,它就類似於一般可執行程序中的main()函數,系統在此之前所做的僅僅是一些能讓內核程序最低限度執行的初始化操作,真正的內核初始化過程是從這裏纔開始。函數start_kernel()將會調用一系列的初始化函數,用來完成內核本身的各方面設置,目的是最終建立起基本完整的Linux核心環境。

start_kernel()中主要執行了以下操作:
(1)
在屏幕上打印出當前的內核版本信息。
(2)
執行setup_arch(),對系統結構進行設置。

(3)執行sched_init(),對系統的調度機制進行初始化。先是對每個可用CPU上的runqueue進行初始化;然後初始化0號進程(task_struct和系統空M堆棧在startup_32()中己經被分配)爲系統idle進程,即系統空閒時佔據CPU的進程。
(4)
執行parse_early_param()parsees_args()解析系統啓動參數。
(5)
執行trap_initQ,先設置了系統中斷向量表。019號的陷阱門用於CPU異常處理;然後初始化系統調用向量;最後調用cpu_init()完善對CPU的初始化,用於支持進程調度機制,包括設定標誌位寄存器、任務寄存器、初始化程序調試相關寄存器等等。
(6)
執行rcu_init(),初始化系統中的Read-Copy Update互斥機制。
(7)
執行init_IRQ()函數,初始化用於外設的中斷,完成對IDT的最終初始化過程。
(8)
執行init_timers(), softirq_init()time_init()函數,分別初始系統的定時器機制,軟中斷機制以及系統日期和時間。
(9)
執行mem_init()函數,初始化物理內存頁面的page數據結構描述符,完成對物理內存管理機制的創建。
(10)
執行kmem_cache_init(),完成對通用slab緩衝區管理機制的初始化工作。

(11)執行fork_init(),計算出當前系統的物理內存容量能夠允許創建的進程(線程)數量。

(12)執行proc_caches_init() , bufer_init(),unnamed_dev_init() ,vfs_caches_init(), signals_init()等函數對各種管理機制建立起專用的slab緩衝區隊列。
(13 )
執行proc_root_init()Wl數,對虛擬文件系統/proc進行初始化。

start_kernel()的結尾,內核通過kernel_thread()創建出第一個系統內核線程(1號進程),該線程執行的是內核中的init()函數,負責的是下一階段的啓動任務。最後調用cpues_idle()函數:進入了系統主循環體口默認將一直執行default_idle()函數中的指令,即CPUhalt指令,直到就緒隊列中存在其他進程需要被調度時纔會轉向執行其他函數。此時,系統中唯一存在就緒狀態的進程就是由kernel_thread()創建的init進程(內核線程),所以內核並不進入default_idle()函數,而是轉向init()函數繼續啓動過程。

啓動第五步--用戶層init依據inittab文件來設定運行等級

內核被加載後,第一個運行的程序便是/sbin/init,該文件會讀取/etc/inittab文件,並依據此文件來進行初始化工作。

其實/etc/inittab文件最主要的作用就是設定Linux的運行等級,其設定形式是id:5:initdefault:”,這就表明Linux需要運行在等級5上。Linux的運行等級設定如下:

0:關機

1:單用戶模式

2:無網絡支持的多用戶模式

3:有網絡支持的多用戶模式

4:保留,未使用

5:有網絡支持有X-Window支持的多用戶模式

6:重新引導系統,即重啓

啓動第六步--init進程執行rc.sysinit

在設定了運行等級後,Linux系統執行的第一個用戶層文件就是/etc/rc.d/rc.sysinit腳本程序,它做的工作非常多,包括設定PATH、設定網絡配置(/etc/sysconfig/network)、啓動swap分區、設定/proc等等。如果你有興趣,可以到/etc/rc.d中查看一下rc.sysinit文件。

線程init的最終完成狀態是能夠使得一般的用戶程序可以正常地被執行,從而真正完成可供應用程序運行的系統環境。它主要進行的操作有:
(1)
執行函數do_basic_setup(),它會對外部設備進行全面地初始化。

(2) 構建系統的虛擬文件系統目錄樹,掛接系統中作爲根目錄的設備(其具體的文件系統已經在上一步驟中註冊)

(3) 打開設備/dev/console,並通過函數sys_dup()打開的連接複製兩次,使得文件號0,1 ,2 全部指向控制檯。這三個文件連接就是通常所說的標準輸入”stdin,“標準輸出”stdout標準出錯信息”stderr這三個標準I/O通道。

(4) 準備好以上一切之後,系統開始進入用戶層的初始化階段。內核通過系統調用execve()加載執T子相應的用戶層初始化程序,依次嘗試加載程序"/sbin/init","/etc/init"," /bin/init',和“/bin/sh。只要其中有一個程序加載獲得成功,那麼系統就將開始用戶層的初始化,而不會再回到init()函數段中。至此,init()函數結束,Linux內核的引導部分也到此結束。

啓動第七步--啓動內核模塊

具體是依據/etc/modules.conf文件或/etc/modules.d目錄下的文件來裝載內核模塊。

啓動第八步--執行不同運行級別的腳本程序

根據運行級別的不同,系統會運行rc0.drc6.d中的相應的腳本程序,來完成相應的初始化工作和啓動相應的服務。

啓動第九步--執行/etc/rc.d/rc.local

你如果打開了此文件,裏面有一句話,讀過之後,你就會對此命令的作用一目瞭然:

# This script will be executed *after*all the other init scripts.
# You can put your own initialization stuff in here if you don’t
# want to do the full Sys V style init stuff.

rc.local就是在一切初始化工作後,Linux留給用戶進行個性化的地方。你可以把你想設置和啓動的東西放到這裏。

啓動第十步--執行/bin/login程序,進入登錄狀態

此時,系統已經進入到了等待用戶輸入usernamepassword的時候了,你已經可以用自己的帳號登入系統了。

 

 

發佈了53 篇原創文章 · 獲贊 16 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章