2011年12月9日 星期五

Linux Kernel 開機大追蹤


前言
多年之前大家都在談論所謂後PC時代,在當時大家無法很確定什麼是後PC時代,在今天因為智慧型手機及平板電腦的興起,己使大家可以很清楚地了解什麼是後PC時代,就是嵌入式系統,而在這些嵌入式系統中,會需要一個作業系統,而Linux的免費,以及它的穩定、open source code、Linux 已成為現在嵌入式首選的作業系統,也因此現在已有越來越多人投入開發。通常若要了解一個作業作統,可以從其開機流程來了解,若清楚地了解其開機流程程,將有助於你了解整個作業系統。
Kernel原始碼
Linux將其所有的程式碼全部公開,用以讓所有世界高手來修改,其實你可以將其視做一個framework的原始碼,所以你若了解其原始碼目錄的擺放將會有助於你開發的速度。以下是kernel source code根目錄的內容:
圖一Linux Kernel source code根目錄
其中幾個比較重要的目錄說明如下:
  • arch
這是一個HAL層的原始碼,和CPU以及各個板子有關,通常這部分會由CPU廠商所提供,另外程式起始包含在這裏面,後面將會給予說明。
  • drivers
這是各個元件的驅動元件,而各個元件的驅動程式,將會依照各分門別類給予不同的目錄而放置。
  • mm
這是有關記憶體管理的部分程式碼,這部分是和硬件無關的程式,其底層則是呼叫HAL層的程式碼,而這部分則是前面所提的arch目錄。
  • fs
在這個目錄所放置的則是各種檔案系統的程式,通常檔案系統和硬體沒有關係,而其底層的IDE或者SCSI等等則是放在前面所提的driver目錄之中。對於各種的虛擬檔案系統也是在這裏。
  • kernel
這是系統微核心的地方,而這部分通常和硬體無關,排程的管理也是在這裏。
  • net
有關各種網路協定的實做,都在這裏,從網路的link層至應用層都在這裏,以及和應用程式之間的socket層介面也是在此目錄中。
  • include
kernel中用到C和組合語言的include file放在這個目錄。
  • init
這是系統開機時的初始化動作,這會呼叫arch之下以及各個driver的初始化動作。
Kernel編譯模式
我們在編譯kernel有兩種模式可以產生,一種是有壓縮的形式,這種形式檔案會較小很多,但是在啓動時會先做自我解壓縮,這樣會造成開機時會較慢一點,另外一種是直接產生一個binary (位元)檔,這是一個kernel的執行檔,這個檔案會大很多,但其實就是前一種解壓縮完之後的內容。如何編譯這兩種kernel呢? 其實很簡單,若要產生壓縮的程式碼,在根目錄執行 ’make zImage’就可以了,它會產生一個檔案 zImage ,放在目錄arch/arm/boot之內,另外要產生沒有壓縮的kernel執行 ’make linux’就可以了,而其產生的檔名為 vmlinux 在原始檔的根目錄之下,最後要說明的是要產生壓縮的 zImage 一定會先產生沒有壓縮的 vmlinux,再由其做壓縮成 zImage。
自我解壓縮
壓縮的zImage檔其前面有一個自我解壓縮的程式碼,這段程式碼在arch/arm/boot/compressed目錄之內,第一個執行的程式為該目錄之下的head.S,原則上bootloader可以傳遞參數給kernel,它將透過暫存器r1及r2來傳送,其中r1是CPU的ID,這個值其實是由CP15的指令所讀來的,而r2則是存於一些tag的指標位置,通常在embedded系統中我們都會省略而不使用。這是一小段解壓縮的程式碼,它可以放在記憶中任何位置來執行,也就是為independent code,其實它是將kernel壓縮後利用linker及objdump公用程式,將其結合成為一個可執行檔或者是firmware,其實是將壓縮過的核心程式當做是資料和head.S的程式結合在一起,另外值得一提的是,這個解壓縮程式可放在記憶體中任何位置執行,所以它會自動判別解壓縮後的程式是否會覆蓋自己而給予避開。
第一個指令
如果是有壓縮的第一個指令如前所提的,放在arch/arm/boot/compressed/head.S,若不是壓縮檔則是放在arch/arm/kernel/head.S之內,應該說這才是真正kernel的第一個指令,一樣的在解壓縮之後會將bootloader所傳送過來的r1 & r2暫存器原封不動的再傳給kernel,而 r1 則是傳遞 CPU 的代碼,而kernel則會使用cp15的指令讀回做檢查,並且查核那一個ARM core,則會使用相對的cache指令將cache打開,通常打開cache時需要啓動MMU才能打開cache,而這時通常會設定虛擬位址和真實位址是一樣的,而打開cache是為了要加快解壓縮的速度,而在解壓縮之前會將r1 & r2分別拷貝至r7 & r8暫存器,而在解壓縮完之後,會將這兩個暫存器存回原先的r1 & r2,而r2則是存放參數位置,而這些參數是有關開機時的選項,而這些說明目前已經寫在程式的開頭地方,當你看其原始碼時即可看到,而第一個指令則是在標示在.setion “.text.head”的地方。
接著它會檢查從bootloader傳來的CPU ID是否正確,在kernel中可以撰寫支援多顆CPU的程式碼,因此可以透過這個代碼來取得相對應的程式碼,主要是cache以及CP15指令的不同,另外還有關MMU方面的操作也有所不同,當確認讓CPU ID是有被支援的,就會繼續執行下去。
再來的動作則取回該CPU的訊息,以及該CPU在kernel己經準備好的一些初始化程式碼,我們在porting kernel時會建立一個錄在arch/arm之下,然後以mach-當開頭的目錄,像是mach-ixp4xx,在這目錄之內將會建立一個C程式碼,在這之內會有一個巨集指令MACHINE_START,在這之內會有一些call back function的設定,在初始化的過程中會被呼叫,其主要是設定那些硬體需要被mapping至那些virtual address,以及timer & interrupt controller的初始化程式碼,當這些初始化的程式碼都可以正確無誤地被執行,它最後則會執會init/main.c中的start_kernel,而在它執行的當中它會檢查從bootloader傳來的command line,若沒有會有一個警告訊息,說這個tag有問題,若你確定没有傳,那你可以忽略掉它。
當它初始都没有問題,則會開始載入各個驅動程式,以及kernel本身的初始化動作,如記憶體管理,當這些都正常執行完之後,就會開始執行第一支AP,它會先執行由bootloader或者在kernel中設定的command line所指定的第一支程式,若没有則會尋找/sbin/init這支程式,若無法執行,則行bash,還是無法執行則會panic系統,並會說明找不到程式可執行,若第一支程式可以正常執行,則其它的程式則會由這第一支程式去執行其它程式,若它沒有執行其它程式,則將不會執行其它程式,另外第一支程式可以是一支shell script,以上則是kernel開機的順序。