轉(zhuǎn):http://www.xuebuyuan.com/2122235.html
Linux內(nèi)核啟動及文件系統(tǒng)加載過程
當u-boot開始執(zhí)行bootcmd命令,就進入Linux內(nèi)核啟動階段,與u-boot類似,普通Linux內(nèi)核的啟動過程也可以分為兩個階段,但針對壓縮了的內(nèi)核如uImage就要包括內(nèi)核自解壓過程了。本文以linux-2.6.37版源碼為例分三個階段來描述內(nèi)核啟動全過程。第一階段為內(nèi)核自解壓過程,第二階段主要工作是設(shè)置ARM處理器工作模式、使能MMU、設(shè)置一級頁表等,而第三階段則主要為C代碼,包括內(nèi)核初始化的全部工作,下面是詳細介紹。
/******************************************************************************************************************************************/
原創(chuàng)作品,轉(zhuǎn)載時請務(wù)必以超鏈接形式標明文章原始出處:http://blog.csdn.net/gqb_driver/article/details/26954425,作者:gqb666
/******************************************************************************************************************************************/
一、Linux內(nèi)核自解壓過程
在linux內(nèi)核啟動過程中一般能看到圖1內(nèi)核自解壓界面,本小節(jié)本文重點討論內(nèi)核的自解壓過程。
圖1 解壓內(nèi)核
內(nèi)核壓縮和解壓縮代碼都在目錄kernel/arch/arm/boot/compressed,編譯完成后將產(chǎn)生head.o、misc.o、piggy.gzip.o、vmlinux、decompress.o這幾個文件,head.o是內(nèi)核的頭部文件,負責(zé)初始設(shè)置;misc.o將主要負責(zé)內(nèi)核的解壓工作,它在head.o之后;piggy.gzip.o是一個中間文件,其實是一個壓縮的內(nèi)核(kernel/vmlinux),只不過沒有和初始化文件及解壓文件鏈接而已;vmlinux是沒有(zImage是壓縮過的內(nèi)核)壓縮過的內(nèi)核,就是由piggy.gzip.o、head.o、misc.o組成的,而decompress.o是為支持更多的壓縮格式而新引入的。
在BootLoader完成系統(tǒng)的引導(dǎo)以后并將Linux內(nèi)核調(diào)入內(nèi)存之后,調(diào)用do_bootm_linux(),這個函數(shù)將跳轉(zhuǎn)到kernel的起始位置。如果kernel沒有被壓縮,就可以啟動了。如果kernel被壓縮過,則要進行解壓,在壓縮過的kernel頭部有解壓程序。壓縮過的kernel入口第一個文件源碼位置在arch/arm/boot/compressed/head.S。它將調(diào)用函數(shù)decompress_kernel(),這個函數(shù)在文件arch/arm/boot/compressed/misc.c中,decompress_kernel()又調(diào)用proc_decomp_setup(),arch_decomp_setup()進行設(shè)置,然后打印出信息“Uncompressing
Linux...”后,調(diào)用gunzip()將內(nèi)核放于指定的位置。
下面簡單介紹一下解壓縮過程,也就是函數(shù)decompress_kernel實現(xiàn)的功能:解壓縮代碼位于kernel/lib/inflate.c,inflate.c是從gzip源程序中分離出來的,包含了一些對全局數(shù)據(jù)的直接引用,在使用時需要直接嵌入到代碼中。gzip壓縮文件時總是在前32K字節(jié)的范圍內(nèi)尋找重復(fù)的字符串進行編碼, 在解壓時需要一個至少為32K字節(jié)的解壓緩沖區(qū),它定義為window[WSIZE]。inflate.c使用get_byte()讀取輸入文件,它被定義成宏來提高效率。輸入緩沖區(qū)指針必須定義為inptr,inflate.c中對之有減量操作。inflate.c調(diào)用flush_window()來輸出window緩沖區(qū)中的解壓出的字節(jié)串,每次輸出長度用outcnt變量表示。在flush_window()中,還必須對輸出字節(jié)串計算CRC并且刷新crc變量。在調(diào)用gunzip()開始解壓之前,調(diào)用makecrc()初始化CRC計算表。最后gunzip()返回0表示解壓成功。我們在內(nèi)核啟動的開始都會看到這樣的輸出:
UncompressingLinux...done, booting the kernel.
這也是由decompress_kernel函數(shù)輸出的,執(zhí)行完解壓過程,再返回到head.S中的583行,啟動內(nèi)核
call_kernel: bl cache_clean_flush bl cache_off mov r0, #0 @ must be zero mov r1, r7 @ restore architecture number mov r2, r8 @ restore atags pointer mov pc, r4 @ call kernel
其中r4中已經(jīng)在head.S的第180行處預(yù)置為內(nèi)核鏡像的地址,如下代碼:
#ifdef CONFIG_AUTO_ZRELADDR @determine final kernel image address mov r4, pc and r4, r4, #0xf8000000 add r4, r4, #TEXT_OFFSET#else ldr r4, =zreladdr#endif
這樣就進入Linux內(nèi)核的第一階段,我們也稱之為stage1。
二、Linux內(nèi)核啟動第一階段stage1
承接上文,這里所以說的第一階段stage1就是內(nèi)核解壓完成并出現(xiàn)Uncompressing
Linux...done,booting the kernel.之后的階段。該部分代碼實現(xiàn)在arch/arm/kernel的 head.S中,該文件中的匯編代碼通過查找處理器內(nèi)核類型和機器碼類型調(diào)用相應(yīng)的初始化函數(shù),再建 立頁表,最后跳轉(zhuǎn)到start_kernel()函數(shù)開始內(nèi)核的初始化工作。檢測處理器類型是在匯編子函數(shù)__lookup_processor_type中完成的,通過以下代碼可實現(xiàn)對它的調(diào)用:bl__lookup_processor_type(在文件head-commom.S實現(xiàn))。__lookup_processor_type調(diào)用結(jié)束返回原程序時,會將返回結(jié)果保存到寄存器中。其中r5寄存器返回一個用來描述處理器的結(jié)構(gòu)體地址,并對r5進行判斷,如果r5的值為0則說明不支持這種處理器,將進入__error_p。r8保存了頁表的標志位,r9
保存了處理器的ID 號,r10保存了與處理器相關(guān)的struct
proc_info_list結(jié)構(gòu)地址。Head.S核心代碼如下:
ENTRY(stext)setmode PSR_F_BIT | PSR_I_BIT | SVC_MODE, r9 @設(shè)置SVC模式關(guān)中斷 mrc p15, 0, r9, c0, c0 @ 獲得處理器ID,存入r9寄存器 bl __lookup_processor_type @ 返回值r5=procinfo r9=cpuid movs r10, r5 THUMB( it eq ) @ force fixup-able long branch encoding beq __error_p @如果返回值r5=0,則不支持當前處理器' bl __lookup_machine_type @ 調(diào)用函數(shù),返回值r5=machinfo movs r8, r5 @ 如果返回值r5=0,則不支持當前機器(開發(fā)板)THUMB( it eq ) @ force fixup-able long branch encoding beq __error_a @ 機器碼不匹配,轉(zhuǎn)__error_a并打印錯誤信息 bl __vet_atags#ifdef CONFIG_SMP_ON_UP @ 如果是多核處理器進行相應(yīng)設(shè)置 bl __fixup_smp#endif bl __create_page_tables @最后開始創(chuàng)建頁表
檢測機器碼類型是在匯編子函數(shù)__lookup_machine_type (同樣在文件head-common.S實現(xiàn))
中完成的。與__lookup_processor_type類似,通過代碼:“bl __lookup_machine_type”來實現(xiàn)對它的調(diào) 用。該函數(shù)返回時,會將返回結(jié)構(gòu)保存放在r5、r6 和r7三個寄存器中。其中r5寄存器返回一個用來描述機器(也就是開發(fā)板)的結(jié)構(gòu)體地址,并對r5進行判斷,如果r5的值為0則說明不支持這種機器(開發(fā)板),將進入__error_a,打印出內(nèi)核不支持u-boot傳入的機器碼的錯誤如圖2。r6保存了I/O基地址,r7 保存了 I/O的頁表偏移地址。 當檢測處理器類型和機器碼類型結(jié)束后,將調(diào)用__create_page_tables子函數(shù)來建立頁表,它所要做的工作就是將
RAM 基地址開始的1M 空間的物理地址映射到 0xC0000000開始的虛擬地址處。對本項目的開發(fā)板DM3730而言,RAM掛接到物理地址0x80000000處,當調(diào)用__create_page_tables 結(jié)束后 0x80000000
~ 0x80100000物理地址將映射到 0xC0000000~0xC0100000虛擬地址處。當所有的初始化結(jié)束之后,使用如下代碼來跳到C 程序的入口函數(shù)start_kernel()處,開始之后的內(nèi)核初始化工作: bSYMBOL_NAME(start_kernel) 。
圖2 機器碼不匹配錯誤
三、Linux內(nèi)核啟動第二階段stage2
從start_kernel函數(shù)開始
Linux內(nèi)核啟動的第二階段從start_kernel函數(shù)開始。start_kernel是所有Linux平臺進入系統(tǒng)內(nèi)核初始化后的入口函數(shù),它主要完成剩余的與 硬件平臺相關(guān)的初始化工作,在進行一系列與內(nèi)核相關(guān)的初始化后,調(diào)用第一個用戶進程- init 進程并等待用戶進程的執(zhí)行,這樣整個 Linux內(nèi)核便啟動完畢。該函數(shù)位于init/main.c文件中,主要工作流程如圖3所示:
圖3 start_kernel流程圖
該函數(shù)所做的具體工作有 :
1) 調(diào)用setup_arch()函數(shù)進行與體系結(jié)構(gòu)相關(guān)的第一個初始化工作;對不同的體系結(jié)構(gòu)來說該函數(shù)有不同的定義。對于ARM平臺而言,該函數(shù)定義在 arch/arm/kernel/setup.c。它首先通過檢測出來的處理器類型進行處理器內(nèi)核的初始化,然后 通過bootmem_init()函數(shù)根據(jù)系統(tǒng)定義的meminfo結(jié)構(gòu)進行內(nèi)存結(jié)構(gòu)的初始化,最后調(diào)用 paging_init()開啟MMU,創(chuàng)建內(nèi)核頁表,映射所有的物理內(nèi)存和IO空間。
2) 創(chuàng)建異常向量表和初始化中斷處理函數(shù);
3) 初始化系統(tǒng)核心進程調(diào)度器和時鐘中斷處理機制;
4) 初始化串口控制臺(console_init);
ARM-Linux 在初始化過程中一般都會初始化一個串口做為內(nèi)核的控制臺,而串口Uart驅(qū)動卻把串口設(shè)備名寫死了,如本例中linux2.6.37串口設(shè)備名為ttyO0,而不是常用的ttyS0。有了控制臺內(nèi)核在啟動過程中就可以通過串口輸出信息以便開發(fā)者或用戶了解系統(tǒng)的啟動進程。
5) 創(chuàng)建和初始化系統(tǒng)cache,為各種內(nèi)存調(diào)用機制提供緩存,包括;動態(tài)內(nèi)存分配,虛擬文件系統(tǒng)(VirtualFile
System)及頁緩存。
6) 初始化內(nèi)存管理,檢測內(nèi)存大小及被內(nèi)核占用的內(nèi)存情況;
7) 初始化系統(tǒng)的進程間通信機制(IPC); 當以上所有的初始化工作結(jié)束后,start_kernel()函數(shù)會調(diào)用rest_init()函數(shù)來進行最后的初始化,包括創(chuàng)建系統(tǒng)的第一個進程-init進程來結(jié)束內(nèi)核的啟動。
掛載根文件系統(tǒng)并啟動init
Linux內(nèi)核啟動的下一過程是啟動第一個進程init,但必須以根文件系統(tǒng)為載體,所以在啟動init之前,還要掛載根文件系統(tǒng)。
四、掛載根文件系統(tǒng)
根文件系統(tǒng)至少包括以下目錄:
/etc/:存儲重要的配置文件。
/bin/:存儲常用且開機時必須用到的執(zhí)行文件。
/sbin/:存儲著開機過程中所需的系統(tǒng)執(zhí)行文件。
/lib/:存儲/bin/及/sbin/的執(zhí)行文件所需的鏈接庫,以及Linux的內(nèi)核模塊。
/dev/:存儲設(shè)備文件。
注:五大目錄必須存儲在根文件系統(tǒng)上,缺一不可。
以只讀的方式掛載根文件系統(tǒng),之所以采用只讀的方式掛載根文件系統(tǒng)是因為:此時Linux內(nèi)核仍在啟動階段,還不是很穩(wěn)定,如果采用可讀可寫的方式掛載根文件系統(tǒng),萬一Linux不小心宕機了,一來可能破壞根文件系統(tǒng)上的數(shù)據(jù),再者Linux下次開機時得花上很長的時間來檢查并修復(fù)根文件系統(tǒng)。
掛載根文件系統(tǒng)的而目的有兩個:一是安裝適當?shù)膬?nèi)核模塊,以便驅(qū)動某些硬件設(shè)備或啟用某些功能;二是啟動存儲于文件系統(tǒng)中的init服務(wù),以便讓init服務(wù)接手后續(xù)的啟動工作。
執(zhí)行init服務(wù)
Linux內(nèi)核啟動后的最后一個動作,就是從根文件系統(tǒng)上找出并執(zhí)行init服務(wù)。Linux內(nèi)核會依照下列的順序?qū)ふ?/span>init服務(wù):
1)/sbin/是否有init服務(wù)
2)/etc/是否有init服務(wù)
3)/bin/是否有init服務(wù)
4)如果都找不到最后執(zhí)行/bin/sh
找到init服務(wù)后,Linux會讓init服務(wù)負責(zé)后續(xù)初始化系統(tǒng)使用環(huán)境的工作,init啟動后,就代表系統(tǒng)已經(jīng)順利地啟動了linux內(nèi)核。啟動init服務(wù)時,init服務(wù)會讀取/etc/inittab文件,根據(jù)/etc/inittab中的設(shè)置數(shù)據(jù)進行初始化系統(tǒng)環(huán)境的工作。/etc/inittab定義init服務(wù)在linux啟動過程中必須依序執(zhí)行以下幾個Script:
/etc/rc.d/rc.sysinit
/etc/rc.d/rc
/etc/rc.d/rc.local
/etc/rc.d/rc.sysinit主要的功能是設(shè)置系統(tǒng)的基本環(huán)境,當init服務(wù)執(zhí)行rc.sysinit時
要依次完成下面一系列工作:
(1)啟動udev
(2)設(shè)置內(nèi)核參數(shù)
執(zhí)行sysctl –p,以便從/etc/sysctl.conf設(shè)置內(nèi)核參數(shù)
(3)設(shè)置系統(tǒng)時間
將硬件時間設(shè)置為系統(tǒng)時間
(4)啟用交換內(nèi)存空間
執(zhí)行swpaon –a –e,以便根據(jù)/etc/fstab的設(shè)置啟用所有的交換內(nèi)存空間。
(5)檢查并掛載所有文件系統(tǒng)
檢查所有需要掛載的文件系統(tǒng),以確保這些文件系統(tǒng)的完整性。檢查完畢后以可讀可寫的方式掛載文件系統(tǒng)。
(6)初始化硬件設(shè)備
Linux除了在啟動內(nèi)核時以靜態(tài)驅(qū)動程序驅(qū)動部分的硬件外,在執(zhí)行rc.sysinit時,也會試著驅(qū)動剩余的硬件設(shè)備。rc.sysinit驅(qū)動的硬件設(shè)備包含以下幾項:
a)定義在/etc/modprobe.conf的模塊
b)ISA PnP的硬件設(shè)備
c)USB設(shè)備
(7)初始化串行端口設(shè)備
Init服務(wù)會管理所有的串行端口設(shè)備,比如調(diào)制解調(diào)器、不斷電系統(tǒng)、串行端口控制臺等。Init服務(wù)則通過rc.sysinit來初始化linux的串行端口設(shè)備。當rc.sysinit發(fā)現(xiàn)linux才能在這/etc/rc.serial時,才會執(zhí)行/etc/rc.serial,借以初始化所有的串行端口設(shè)備。因此,你可以在/etc/rc.serial中定義如何初始化linux所有的串行端口設(shè)備。
(8)清除過期的鎖定文件與IPC文件
(9)建立用戶接口
在執(zhí)行完3個主要的RC Script后,init服務(wù)的最后一個工作,就是建立linux的用戶界面,好讓用戶可以使用linux。此時init服務(wù)會執(zhí)行以下兩項工作:
(10)建立虛擬控制臺
Init會在若干個虛擬控制臺中執(zhí)行/bin/login,以便用戶可以從虛擬控制臺登陸linux。linux默認在前6個虛擬控制臺,也就是tty1~tty6,執(zhí)行/bin/login登陸程序。當所有的初始化工作結(jié)束后,cpu_idle()函數(shù)會被調(diào)用來使系統(tǒng)處于閑置(idle)狀態(tài)并等待用戶程序的執(zhí)行。至此,整個Linux內(nèi)核啟動完畢。整個過程見圖4。
圖4:linux內(nèi)核啟動及文件系統(tǒng)加載全過程
聯(lián)系客服