天天看點

基于Linux2.6.35核心的zImage啟動過程研究

環境:

         硬體平台: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核心學習時的一些筆記,并不一定正确,希望大家指正批評,謝謝。

繼續閱讀