Linux內核啓動及根文件系統載入過程

上接博文u-boot之u-boot-2009.11啓動過程分析linux

Linux內核啓動及文件系統載入過程
緩存

u-boot開始運行bootcmd命令,就進入Linux內核啓動階段。與u-boot相似,普通Linux內核的啓動過程也可以分爲兩個階段,但針對壓縮了的內核如uImage就要包含內核自解壓過程了。本文以linux-2.6.37版源代碼爲例分三個階段來描寫敘述內核啓動全過程。第一階段爲內核自解壓過程,第二階段主要工做是設置ARM處理器工做模式、使能MMU、設置一級頁表等,而第三階段則主要爲C代碼,包含內核初始化的全部工做,如下是具體介紹。函數

/******************************************************************************************************************************************/ui

 原創做品,轉載時請務必以超連接形式標明文章原始出處:http://blog.csdn.net/gqb_driver/article/details/26954425,做者:gqb666  
/******************************************************************************************************************************************/

1、Linux內核自解壓過程編碼

linux內核啓動過程當中通常能看到圖1內核自解壓界面,本小節本文重點討論內核的自解壓過程。spa

                

                                     圖1 解壓內核.net

內核壓縮和解壓縮代碼都在文件夾kernel/arch/arm/boot/compressed。編譯完畢後將產生head.o、misc.o、piggy.gzip.o、vmlinux、decompress.o這幾個文件,head.o是內核的頭部文件。負責初始設置;misc.o將主要負責內核的解壓工做。它在head.o以後;piggy.gzip.o是一箇中間文件,事實上是一個壓縮的內核(kernel/vmlinux),僅僅只是沒有和初始化文件及解壓文件連接而已;vmlinux是沒有(zImage是壓縮過的內核)壓縮過的內核。就是由piggy.gzip.o、head.o、misc.o組成的,而decompress.o是爲支持不少其它的壓縮格式而新引入的。指針

BootLoader完畢系統的引導之後並將Linux內核調入內存以後,調用do_bootm_linux()。這個函數將跳轉到kernel的起始位置。假設kernel沒有被壓縮。就可以啓動了。假設kernel被壓縮過。則要進行解壓,在壓縮過的kernel頭部有解壓程序。壓縮過的kernel入口第一個文件源代碼位置在arch/arm/boot/compressed/head.S。它將調用函數decompress_kernel()。這個函數在文件arch/arm/boot/compressed/misc.c中。decompress_kernel()又調用proc_decomp_setup(),arch_decomp_setup()進行設置,而後打印出信息「Uncompressing Linux...」後,調用gunzip()將內核放於指定的位置。rest

如下簡介一下解壓縮過程,也就是函數decompress_kernel實現的功能:解壓縮代碼位於kernel/lib/inflate.c,inflate.c是從gzip源程序中分離出來的,包括了一些對全局數據的直接引用。在使用時需要直接嵌入到代碼中。gzip壓縮文件時老是在前32K字節的範圍內尋找反覆的字符串進行編碼, 在解壓時需要一個至少爲32K字節的解壓緩衝區,它定義爲window[WSIZE]inflate.c使用get_byte()讀取輸入文件,它被定義成宏來提升效率。code

輸入緩衝區指針必須定義爲inptr,inflate.c中對之有減量操做。

inflate.c調用flush_window()來輸出window緩衝區中的解壓出的字節串。每次輸出長度用outcnt變量表示。在flush_window()中,還必須對輸出字節串計算CRC並且刷新crc變量。在調用gunzip()開始解壓以前,調用makecrc()初始化CRC計算表。最後gunzip()返回0表示解壓成功。咱們在內核啓動的開始都會看到這種輸出:

UncompressingLinux...done, booting the kernel.

這也是由decompress_kernel函數輸出的。運行完解壓過程。再返回到head.S中的583行,啓動內核

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中已經在head.S的第180行處預置爲內核鏡像的地址,例如如下代碼:

#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內核的第一階段,咱們也稱之爲stage1

2、Linux內核啓動第一階段stage1

承接上文,這裏因此說的第一階段stage1就是內核解壓完畢並出現Uncompressing Linux...done,booting the kernel.以後的階段。該部分代碼實現在arch/arm/kernel的 head.S中。該文件裏的彙編代碼經過查找處理器內核類型和機器碼類型調用對應的初始化函數,再建 立頁表,最後跳轉到start_kernel()函數開始內核的初始化工做。檢測處理器類型是在彙編子函數__lookup_processor_type中完畢的,經過下面代碼可實現對它的調用:bl__lookup_processor_type(在文件head-commom.S實現)。__lookup_processor_type調用結束返回原程序時,會將返回結果保存到寄存器中。當中r5寄存器返回一個用來描寫敘述處理器的結構體地址,並對r5進行推斷,假設r5的值爲0則說明不支持這樣的處理器。將進入__error_pr8保存了頁表的標誌位,r9 保存了處理器的ID 號,r10保存了與處理器相關的struct proc_info_list結構地址。

Head.S核心代碼例如如下:

ENTRY(stext)
setmode PSR_F_BIT | PSR_I_BIT | SVC_MODE, r9 @設置SVC模式關中斷
      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         @ 調用函數,返回值r5=machinfo
      movs      r8, r5            @ 假設返回值r5=0,則不支持當前機器(開發板)
THUMB( it   eq )             @ force fixup-able long branch encoding
      beq __error_a                   @ 機器碼不匹配。轉__error_a並打印錯誤信息
      bl    __vet_atags
#ifdef CONFIG_SMP_ON_UP    @ 假設是多核處理器進行對應設置
      bl    __fixup_smp
#endif
      bl    __create_page_tables  @最後開始建立頁表

檢測機器碼類型是在彙編子函數__lookup_machine_type (相同在文件head-common.S實現) 中完畢的。與__lookup_processor_type相似,經過代碼:「bl __lookup_machine_type」來實現對它的調 用。該函數返回時,會將返回結構保存放在r五、r6 和r7三個寄存器中。

當中r5寄存器返回一個用來描寫敘述機器(也就是開發板)的結構體地址,並對r5進行推斷,假設r5的值爲0則說明不支持這樣的機器(開發板),將進入__error_a,打印出內核不支持u-boot傳入的機器碼的錯誤如圖2。

r6保存了I/O基地址,r7 保存了 I/O的頁表偏移地址。 當檢測處理器類型和機器碼類型結束後,將調用__create_page_tables子函數來創建頁表,它所要作的工做就是將 RAM 基地址開始的1M 空間的物理地址映射到 0xC0000000開始的虛擬地址處。對本項目的開發板DM3730而言,RAM掛接到物理地址0x80000000處,當調用__create_page_tables 結束後 0x80000000 ~ 0x80100000物理地址將映射到 0xC0000000~0xC0100000虛擬地址處。

當所有的初始化結束以後。使用例如如下代碼來跳到C 程序的入口函數start_kernel()處。開始以後的內核初始化工做: bSYMBOL_NAME(start_kernel) 。

    

                                     圖2 機器碼不匹配錯誤

3、Linux內核啓動第二階段stage2

 從start_kernel函數開始

Linux內核啓動的第二階段從start_kernel函數開始。

start_kernel是所有Linux平臺進入系統內核初始化後的入口函數,它主要完畢剩餘的與 硬件平臺相關的初始化工做,在進行一系列與內核相關的初始化後,調用第一個用戶進程- init 進程並等待用戶進程的運行。這樣整個 Linux內核便啓動完畢。該函數位於init/main.c文件裏,主要工做流程如圖3所看到的:

                                                                                 圖3 start_kernel流程圖

該函數所作的詳細工做有 :

1) 調用setup_arch()函數進行與體系結構相關的第一個初始化工做。對不一樣的體系結構來講該函數有不一樣的定義。

對於ARM平臺而言,該函數定義在 arch/arm/kernel/setup.c。它首先經過檢測出來的處理器類型進行處理器內核的初始化。而後 經過bootmem_init()函數依據系統定義的meminfo結構進行內存結構的初始化,最後調用 paging_init()開啓MMU,建立內核頁表,映射所有的物理內存和IO空間。 

2) 建立異常向量表和初始化中斷處理函數; 

3) 初始化系統核心進程調度器和時鐘中斷處理機制。 

4) 初始化串口控制檯(console_init); 

ARM-Linux 在初始化過程當中通常都會初始化一個串口作爲內核的控制檯。而串口Uart驅動卻把串口設備名寫死了。如本例中linux2.6.37串口設備名爲ttyO0,而不是常用的ttyS0。有了控制檯內核在啓動過程當中就可以經過串口輸出信息以便開發人員或用戶瞭解系統的啓動進程。

 

5) 建立和初始化系統cache。爲各類內存調用機制提供緩存。包含;動態內存分配,虛擬文件系統(VirtualFile System)及頁緩存。

 

6) 初始化內存管理。檢測內存大小及被內核佔用的內存狀況; 

7) 初始化系統的進程間通訊機制(IPC); 當以上所有的初始化工做結束後,start_kernel()函數會調用rest_init()函數來進行最後的初始化,包含建立系統的第一個進程-init進程來結束內核的啓動。

掛載根文件系統並啓動init

Linux內核啓動的下一過程是啓動第一個進程init。但必須以根文件系統爲載體,因此在啓動init以前,還要掛載根文件系統。

4、掛載根文件系統

根文件系統至少包含下面文件夾:

 /etc/:存儲重要的配置文件。

 /bin/:存儲常用且開機時必須用到的運行文件。

 /sbin/:存儲着開機過程當中所需的系統運行文件。

 /lib/:存儲/bin//sbin/的運行文件所需的連接庫,以及Linux的內核模塊。

 /dev/:存儲設備文件。

  注:五大文件夾必須存儲在根文件系統上,缺一不可。

以僅僅讀的方式掛載根文件系統。之因此採用僅僅讀的方式掛載根文件系統是因爲:此時Linux內核仍在啓動階段,還不是很是穩定,假設採用可讀可寫的方式掛載根文件系統。萬一Linux不當心宕機了,一來可能破壞根文件系統上的數據。再者Linux下次開機時得花上很是長的時間來檢查並修復根文件系統。

    掛載根文件系統的而目的有兩個:一是安裝適當的內核模塊,以便驅動某些硬件設備或啓用某些功能;二是啓動存儲於文件系統中的init服務。以便讓init服務接手興許的啓動工做。

運行init服務

Linux內核啓動後的最後一個動做。就是從根文件系統上找出並運行init服務。

Linux內核會按照下列的順序尋找init服務:

1)/sbin/是否有init服務

2)/etc/是否有init服務

3)/bin/是否有init服務

4)假設都找不到最後運行/bin/sh

找到init服務後,Linux會讓init服務負責興許初始化系統使用環境的工做,init啓動後,就表明系統已經順利地啓動了linux內核。啓動init服務時。init服務會讀取/etc/inittab文件,依據/etc/inittab中的設置數據進行初始化系統環境的工做。

/etc/inittab定義init服務在linux啓動過程當中必須依序運行下面幾個Script

 /etc/rc.d/rc.sysinit

 /etc/rc.d/rc

/etc/rc.d/rc.local

/etc/rc.d/rc.sysinit基本的功能是設置系統的基本環境,當init服務運行rc.sysinit時 要依次完畢如下一系列工做:

(1)啓動udev

(2)設置內核參數

運行sysctl –p,以便從/etc/sysctl.conf設置內核參數

(3)設置系統時間

將硬件時間設置爲系統時間

(4)啓用交換內存空間

運行swpaon –a –e,以便依據/etc/fstab的設置啓用所有的交換內存空間。

(5)檢查並掛載所有文件系統

檢查所有需要掛載的文件系統。以確保這些文件系統的完整性。

檢查完成後以可讀可寫的方式掛載文件系統。

(6)初始化硬件設備

      Linux除了在啓動內核時以靜態驅動程序驅動部分的硬件外。在運行rc.sysinit時,也會試着驅動剩餘的硬件設備。rc.sysinit驅動的硬件設備包括下面幾項:

  a)定義在/etc/modprobe.conf的模塊

  b)ISA PnP的硬件設備

  c)USB設備

(7)初始化串行port設備

 Init服務會管理所有的串行port設備。比方調制解調器、不斷電系統、串行port控制檯等。Init服務則經過rc.sysinit來初始化linux的串行port設備。當rc.sysinit發現linux才幹在這/etc/rc.serial時。纔會運行/etc/rc.serial,藉以初始化所有的串行port設備。

所以,你可以在/etc/rc.serial中定義怎樣初始化linux所有的串行port設備。

(8)清除過時的鎖定文件與IPC文件

(9)創建用戶接口

在運行完3個基本的RC Script後,init服務的最後一個工做,就是創建linux的用戶界面,好讓用戶可以使用linux。此時init服務會運行下面兩項工做:

(10)創建虛擬控制檯

 Init會在若干個虛擬控制檯中運行/bin/login。以便用戶可以從虛擬控制檯登錄linuxlinux默認在前6個虛擬控制檯,也就是tty1~tty6,運行/bin/login登錄程序。

當所有的初始化工做結束後,cpu_idle()函數會被調用來使系統處於閒置(idle)狀態並等待用戶程序的運行。至此。整個Linux內核啓動完成。

整個過程見圖4。

      

                          圖4:linux內核啓動及文件系統載入全過程

相關文章
相關標籤/搜索