[轉載] linux啓動流程分析(3)---內核解壓縮過程

原文地址:http://www.eetop.cn/blog/html/45/11145-571.html

 

================================

Author: taoyuetao
Email:[email protected]
Blog:http://www.eetop.cn/blog/?11145

2006-11-06

================================

內核壓縮和解壓縮代碼都在目錄kernel/arch/arm/boot/compressed,
編譯完成後將產生vmlinux、head.o、misc.o、head-xscale.o、piggy.o這幾個文件,
head.o是內核的頭部文件,負責初始設置;
misc.o將主要負責內核的解壓工作,它在head.o之後;
head-xscale.o文件主要針對Xscale的初始化,將在鏈接時與head.o合併;
piggy.o是一箇中間文件,其實是一個壓縮的內核(kernel/vmlinux),只不過沒有和初始化文件及解壓文件鏈接而已;
vmlinux是(沒有--lw:zImage是壓縮過的內核)壓縮過的內核,就是由piggy.o、head.o、misc.o、head-xscale.o組成的。

BootLoader 完成系統的引導以後並將Linux 內核調入內存之後,調用bootLinux(),
這個函數將跳轉到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()。將內核放於指定的位置。


以下分析head.S文件:
(1)對於各種Arm CPU的DEBUG輸出設定,通過定義宏來統一操作。
(2)設置kernel開始和結束地址,保存architecture ID。
(3)如果在ARM2以上的CPU中,用的是普通用戶模式,則升到超級用戶模式,然後關中斷。
(4)分析LC0結構delta offset,判斷是否需要重載內核地址(r0存入偏移量,判斷r0是否爲零)。
   這裏是否需要重載內核地址,我以爲主要分析arch/arm/boot/Makefile、arch/arm/boot/compressed/Makefile
   和arch/arm/boot/compressed/vmlinux.lds.in三個文件,主要看vmlinux.lds.in鏈接文件的主要段的位置,
   LOAD_ADDR(_load_addr)=0xA0008000,而對於TEXT_START(_text、_start)的位置只設爲0,BSS_START(__bss_start)=ALIGN(4)。
   對於這樣的結果依賴於,對內核解壓的運行方式,也就是說,內核解壓前是在內存(RAM)中還是在FLASH上,
   因爲這裏,我們的BOOTLOADER將壓縮內核(zImage)移到了RAM的0xA0008000位置,我們的壓縮內核是在內存(RAM)從0xA0008000地址開始順序排列,
   因此我們的r0獲得的偏移量是載入地址(0xA0008000)。接下來的工作是要把內核鏡像的相對地址轉化爲內存的物理地址,即重載內核地址。
(5)需要重載內核地址,將r0的偏移量加到BSS region和GOT table中。
(6)清空bss堆棧空間r2-r3。
(7)建立C程序運行需要的緩存,並賦於64K的棧空間。
(8)這時r2是緩存的結束地址,r4是kernel的最後執行地址,r5是kernel境象文件的開始地址。檢查是否地址有衝突。
   將r5等於r2,使decompress後的kernel地址就在64K的棧之後。
(9)調用文件misc.c的函數decompress_kernel(),解壓內核於緩存結束的地方(r2地址之後)。此時各寄存器值有如下變化:
   r0爲解壓後kernel的大小
   r4爲kernel執行時的地址
   r5爲解壓後kernel的起始地址
   r6爲CPU類型值(processor ID)
   r7爲系統類型值(architecture ID)
(10)將reloc_start代碼拷貝之kernel之後(r5+r0之後),首先清除緩存,而後執行reloc_start。
(11)reloc_start將r5開始的kernel重載於r4地址處。
(12)清除cache內容,關閉cache,將r7中architecture ID賦於r1,執行r4開始的kernel代碼。

下面簡單介紹一下解壓縮過程,也就是函數decompress_kernel實現的功能:
解壓縮代碼位於kernel/lib/inflate.c,inflate.c是從gzip源程序中分離出來的。包含了一些對全局數據的直接引用。
在使用時需要直接嵌入到代碼中。gzip壓縮文件時總是在前32K字節的範圍內尋找重複的字符串進行編碼,
在解壓時需要一個至少爲32K字節的解壓緩衝區,它定義爲window[WSIZE]。inflate.c使用get_byte()讀取輸入文件,
它被定義成宏來提高效率。輸入緩衝區指針必須定義爲inptr,inflate.c中對之有減量操作。inflate.c調用flush_window()
來輸出window緩衝區中的解壓出的字節串,每次輸出長度用outcnt變量表示。在flush_window()中,還必
須對輸出字節串計算CRC並且刷新crc變量。在調用gunzip()開始解壓之前,調用makecrc()初始化CRC計算表。
最後gunzip()返回0表示解壓成功。

我們在內核啓動的開始都會看到這樣的輸出:
Uncompressing Linux...done, booting the kernel.
這也是由decompress_kernel函數內部輸出的,它調用了puts()輸出字符串,
puts是在kernel/include/asm-arm/arch-pxa/uncompress.h中實現的。

執行完解壓過程,再返回到head.S中,啓動內核:

call_kernel:    bl  cache_clean_flush
         bl  cache_off
         mov r0, #0
         mov r1, r7          @ restore architecture number
         mov pc, r4          @ call kernel
        
下面就開始真正的內核了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章