環境:
硬體平台:ARM9 S3C2440 TQ2440開發闆。
軟體環境:VM7.1虛拟機;Fedora10;arm-linux-gcc 4.3.3;Linux2.6.35;u-boot2010.06(天嵌原版本)
一、 zImage、uImage和vmLinux相關概念
當正确配置完核心後,采用make zImage 、make bzImage 、make uImage等指令編譯核心鏡像時,都會生成vmLinux。Ls –l arch/arm/boot/compressed/ 可以看到:
-rwxr-xr-x 1 root root 2084135 09-26 14:18 vmlinux
Vmlinux是ELF格式的可引導的、壓縮核心。其中的VM是”virtual memory”的縮寫。其中make zImage和make bzImage的差別在這裡就不多說了,需要注意的是,make bzImage常常會讓人誤解成核心是bzip2格式進行壓縮的,其實則不然,bz實際上是”big zImage”的意思。在Linux2.6.35核心中,可以選用三種壓縮方式,分别為:GZIP、LZMA和LZO。其中最常用的是GZIP。在zImage核心的内部,開頭處内嵌有解壓縮的代碼,當bootloader引導結束後,将控制權将給核心的時候,核心可以實作自解壓,有興趣有讀者可以參考核心代碼:arch/arm/boot/compressed/head.S和該目錄下的misc.c檔案。
打開arch/arm/boot/Makefile檔案:
56 $(obj)/zImage: $(obj)/compressed/vmlinux FORCE
57 $(call if_changed,objcopy)
58 @echo ' Kernel: [email protected] is ready'
可以看出,zImage是vmlinux通過objcopy後生成的;打開arch/arm/boot/compressed/Makefie檔案:
66 suffix_$(CONFIG_KERNEL_GZIP) = gzip
可以看出,當配置核心為GZIP方式時,zImage的壓縮方式使用gzip。現在可以清楚的明白zImage和vmlinux的關系了,一句話總結一下:zImage就是vmlinux通過objcopy、gzip壓縮後,得到的核心,其頭部是由head.S misc.c組成的自解壓代碼。
而u-boot所一直追捧的uImage格式,隻是在zImage的前面加上了40Byte的核心頭部資訊,由于本文主要講解zImage,此處暫不對uImage進行過多的分析。
二、 啟動過程綜述
zImage核心有着解壓速度快、體積小、編譯簡單等優點,很适合應用在嵌入式領域中,但U-BOOT一直以來都追捧uImage格式,對zImage的格式是不支援的。是以,要想完全了解zImage的啟動過程,我們還要從U-BOOT說起。
我們這裡使用的U-BOOT版本是天嵌科技提供的2010.06月版,支援的核心大小為3M。天嵌科技的U-BOOT(以下簡稱u-boot),支援zImage鏡像,其主要實作的代碼在 ./lib-arm/boot-zImage.c檔案中實作。
當啟動zImage核心時,u-boot調用boot_zImage函數(前面部分略過,從boot_zImage函數講起),該函數完成以下工作:
1. 設定核心由nand flash複制到sdram中的位址:0x30008000;
2. 調用copy_kernel_img 函數複制核心到sdram;
3. 設定Image magic number;
4. 設定傳遞給核心的參數位址為0x30001000;
5. 設定機器碼為168;
6. 最後調用call_linux函數,将控制權徹底交給核心。
當完成了上述工作後,核心開始啟動,zImage核心的入口程式為:arch/arm/boot/compressedhead.S 。它完的工作主要是:開啟MMU和CACHE,設定解壓位址、緩存位址、入口位址等(具體的内容見第三部分代碼詳解),調用decompress_kernel函數(arch/arm/boot/compressedmisc.c)解壓核心,調用call_kernel (注意:call_kernel 是arch/arm/boot/compressedhead.S的一個标号)調用解壓後的核心。
三、重點代碼分析
1. arch/arm/boot/compressedhead.S分析
進入核心代碼後,首先儲存u-boot傳進來的機器碼到r7中,儲存參數指針到r8中,代碼如下:
1: mov r7, r1 @ save architecture ID
mov r8, r2 @ save atags pointer
接着,對處理器的啟動模式進行判斷,進入SVC模式,關中斷等。重點是要了解這段代碼:
adr r0, LC0
ARM( ldmia r0, {r1, r2, r3, r4, r5, r6, r11, ip, sp})
subs r0, r0, r1 @ calculate the delta offsetmj
@ if delta is zero, we are
beq not_relocated @ running at the address we
@ were linked at.
add r5, r5, r0
add r11, r11, r0
add ip, ip, r0
````````````````````````
LC0: .word LC0 @ r1
.word __bss_start @ r2
.word _end @ r3
.word zreladdr @ r4
.word _start @ r5
.word _image_size @ r6
.word _got_start @ r11
.word _got_end @ ip
.word user_stack+4096 @ sp
這段代碼的作用的是将LC0标号後面的位址,傳到各個寄存器中,經過這段代碼後,r1儲存的是連結zImage時LC0标号處的位址,r2中儲存的是bss段的起始位址,r4中儲存的是核心加載的實體位址,r5儲存的是鏡像的起始位址,r6儲存的是鏡像的大小,如果還是不能了解,請用source insight的查找功能(Ctrl+/),對其進行查找,便于了解。
其中r0儲存的是運作到LC0标号時的PC值。要注意了解adr指令的功能:将基于PC值的偏移位址存入寄存器中。後面的subs r0,r0,r1,是判斷連結位址和運作位址的差,如果是0,說明無偏移,則跳過,無需重定位;否則,則要重定位。
接着,如果沒有定義CONFIG_ZBOOT_ROM,還要重定位bss段和GOT。
最後,打開CACHE,設定指針,設定緩存(64K),準備解壓核心,在解壓核心之前,還要檢測是否overwrite,代碼如下:
* r4 + image length <= r5 -> OK
*/
cmp r4, r2
bhs wont_overwrite
add r0, r4, r6
cmp r0, r5
bls wont_overwrite
mov r5, r2 @ decompress after malloc space
mov r0, r5
mov r3, r7
bl decompress_kernel
經過判斷後,如果沒有出現overwrite現象,就直接解壓核心,否則的話,還要進行代碼的搬運。搬運的代碼在此就不列出了。
調用decompress_kernel函數時需要傳遞四個參數,根據規則,使用r0—r3四個寄存器,分别傳遞:r0傳遞Image的首位址;r1傳遞配置設定緩存空間的首位址;r2緩存空間的末位址;r3傳遞的是一開始儲存的機器碼,這也正與
decompress_kernel(unsigned long output_start, unsigned long free_mem_ptr_p,unsigned long free_mem_ptr_end_p, int arch_id)
函數的四個參數相對應。
為了能更形象的表示記憶體中各段的位置,請看下面的記憶體位置表。u-boot會将zImage鏡像copy到sdram的0x30008000位置處。此時為初始狀态,這裡稱為狀态1。
狀态1:
.text
0x30008000 (zImage)
.got
.data
.bss
.stack (4K)
當調用解壓函數decompress_kernel後,記憶體中的各段位置如下,狀态2:
狀态2:
.text
0x30008000 (zImage)
.got
.data
.bss
.stack (4K)
.64K緩存區 解壓核心所需
.Image 解壓後的核心
當如果head.S中有代碼搬運工作時,即出現overwrite時,記憶體中的各段位置如,狀态3:
狀态3:
.text
0x30008000 (zImage)
.got
.data
.bss
.stack (4K)
.64K緩存區 解壓核心所需
.Image 解壓後的核心
.reloc_start 重定位的代碼
.reloc_end
以上3個表,是我個人淺見,并不代表其它人的觀點,如果錯誤,還請不吝賜教。
2、部分重點位址的說明
看了上面一堆位址,現在肯定是有點暈了,要想真正弄懂其本質,還要多看代碼,下面列出部分重要位址在代碼中的出處,友善讀者。
(1).arch/arm/Makefile中的
111 textofs-y := 0x00008000
212 # The byte offset of the kernel image in RAM from the start of RAM.
213 TEXT_OFFSET := $(textofs-y)
此處,定義了Image的偏移位址。注意看原版注釋。
(2).arch/arm/boot/Makefile中的
20 # Note: the following conditions must always be true:
21 # ZRELADDR == virt_to_phys(PAGE_OFFSET + TEXT_OFFSET)
22 # PARAMS_PHYS must be within 4MB of ZRELADDR
23 # INITRD_PHYS must be in RAM
24 ZRELADDR := $(zreladdr-y)
25 PARAMS_PHYS := $(params_phys-y)
26 INITRD_PHYS := $(initrd_phys-y)
此處定義了,核心的實體位址和u-boot傳遞參數的位址,也就是加載位址。此處要和u-boot一緻。
(3).arch/arm/mach-s3c2410/Makefile.boot中的
1 ifeq ($(CONFIG_PM_H1940),y)
2 zreladdr-y := 0x30108000
3 params_phys-y := 0x30100100
4 else
5 zreladdr-y := 0x30008000
6 params_phys-y := 0x30000100
7 endif
此處定了實體位址和參數位址的具體址。
(4).include/asm-generic/Page.h中
#ifdef CONFIG_KERNEL_RAM_BASE_ADDRESS
#define PAGE_OFFSET (CONFIG_KERNEL_RAM_BASE_ADDRESS)
#else
#define PAGE_OFFSET (0)
#endif
此處定義了PAGE_OFFSET。
3、解壓代碼分析
經過head.S的初始化後,調用了arch/arm/boot/compressed/misc.c檔案中的decompress_kernel函數,現在來看一下這個函數,為友善浏覽,現将函數的代碼貼到下面,并在後面注釋。
unsigned long
decompress_kernel(unsigned long output_start, unsigned long free_mem_ptr_p,
unsigned long free_mem_ptr_end_p,
int arch_id)
{
unsigned char *tmp;
output_data = (unsigned char *)output_start;
free_mem_ptr = free_mem_ptr_p;
free_mem_end_ptr = free_mem_ptr_end_p;
__machine_arch_type = arch_id;
arch_decomp_setup();
tmp = (unsigned char *) (((unsigned long)input_data_end) - 4);
output_ptr = get_unaligned_le32(tmp);
putstr("Uncompressing Linux...");
do_decompress(input_data, input_data_end - input_data,
output_data, error);
putstr(" done, booting the kernel.\n");
return output_ptr;
}
此函數在取出head.S傳遞過來的參數後,調用arch_decomp_setup函數進行初始化,實作的功能是檢測CPU型号、使能看門狗(如果在配置核心時配置的前提下)和序列槽。之後調用do_decompress函數進行解壓,do_decompress函數是在arch/arm/boot/compressed/Decompress.c檔案中的。其代碼如下:
#ifdef CONFIG_KERNEL_GZIP
#include "../../../../lib/decompress_inflate.c"
#endif
#ifdef CONFIG_KERNEL_LZO
#include "../../../../lib/decompress_unlzo.c"
#endif
#ifdef CONFIG_KERNEL_LZMA
#include "../../../../lib/decompress_unlzma.c"
#endif
void do_decompress(u8 *input, int len, u8 *output, void (*error)(char *x))
{
decompress(input, len, NULL, NULL, output, NULL, error);
}
當配置核心時選中GZIP方式,就會#include “../../../../lib/decompress_inflate.c”。檔案最後一行為:
#define decompress gunzip
即:最後調用 gunzip函數對zImage進行解壓。
當完成所有解壓任務後,又将跳轉回head.S檔案中,執行call_kernel,将啟動真正的Image.
以上隻是這幾天對 2.6.35核心學習時的一些筆記,并不一定正确,希望大家指正批評,謝謝。