1. 程式人生 > >U-BOOT啟動linux的過程

U-BOOT啟動linux的過程

 一、概述

  linux核心映象常見到的有兩種形式,zImage和uImage。這兩種檔案的格式稍有差別,所以啟動這兩種格式的核心映象也會有所不同。目前,uboot只支援啟動uImage型別的映象,對zImage還不支援(但是可以移植,TQ2440就是這樣做的)。

二、uImage和zImage

1、zImage

       zImage是用命令“#make zImage”生成的,我截取了生成資訊最後部分的內容如下:

複製程式碼
  OBJCOPY arch/arm/boot/Image
  Kernel: arch/arm/boot/Image is ready
  GZIP    arch/arm/boot/compressed/piggy.gz
  AS      arch
/arm/boot/compressed/piggy.o LD arch/arm/boot/compressed/vmlinux OBJCOPY arch/arm/boot/zImage Kernel: arch/arm/boot/zImage is ready
複製程式碼

  從中可以看到,zImage是經過gzip壓縮過的,所以在核心啟動過程(不屬於u-boot控制範圍,在核心映象的頭部嵌有解壓函式)中必然會對應一個解壓過程。

2、uImage

(1) 生成方法

  uImage是u-boot專用的核心映象,可用命令“#make uImage”生成。生成資訊最後部分的內容如下:

複製程式碼
  Kernel: arch/arm/boot/Image is
ready Kernel: arch/arm/boot/zImage is ready UIMAGE arch/arm/boot/uImage Image Name: Linux-2.6.30.4-EmbedSky Created: Thu Mar 20 19:53:32 2014 Image Type: ARM Linux Kernel Image (uncompressed) Data Size: 2314736 Bytes = 2260.48 kB = 2.21 MB Load Address: 0x30008000 Entry Point: 0x30008000 Image arch/arm/boot/uImage is
ready
複製程式碼

  事實上,uImage是呼叫mkimage(uboot製作的工具)這個工具生成的。

複製程式碼
[email protected]:/opt/EmbedSky#  mkimage -n 'linux-2.6.30' -A arm -O linux -T kernel -C none -a 0x30008000 -e 0x30008000 -d zImage uImage
Image Name:   linux-2.6.30
Created:      Thu Mar 20 19:59:36 2014
Image Type:   ARM Linux Kernel Image (uncompressed)
Data Size:    2314736 Bytes = 2260.48 kB = 2.21 MB
Load Address: 0x30008000
Entry Point:  0x30008000
複製程式碼

(2)特點

  在原來的可執行映象檔案zImage的前面加上一個0x40位元組的頭, 記錄引數所指定的資訊,這樣uboot才能識別這個映象是針對哪個CPU體系結構的,哪個OS的, 哪種型別,載入記憶體中的哪個位置,入口點在記憶體的那個位置以及映象名是什麼。

(3)image_header

  頭部的結構是在include/image.h中定義的,如下所示:

複製程式碼
typedef struct image_header {
       uint32_t  ih_magic;       /* Image Header Magic Number   */
       uint32_t  ih_hcrc;   /* Image Header CRC Checksum  */
       uint32_t  ih_time;  /* Image Creation Timestamp       */
       uint32_t  ih_size;   /* Image Data Size        */
       uint32_t  ih_load;   /* Data    Load  Address            */
       uint32_t  ih_ep;            /* Entry Point Address          */
       uint32_t  ih_dcrc;   /* Image Data CRC Checksum      */
       uint8_t           ih_os;             /* Operating System             */
       uint8_t           ih_arch;   /* CPU architecture              */
       uint8_t           ih_type;   /* Image Type               */
       uint8_t           ih_comp; /* Compression Type            */
       uint8_t           ih_name[IH_NMLEN];  /* Image Name             */
} image_header_t;
複製程式碼

  開啟上邊生成的uImage檔案,可以看到對應的資料。

(1)ih_magic    0x27051956  magic值,我覺得是uImage的頭部開始值,根據這個值,判斷是否是uImage

(2)ih_crc    0x19dbf9c6    頭部校驗

(3)ih_time   0x74295319   建立時間

(4)ih_size   0x002351f0     映象大小為2260.48KB

(5)ih_load  0x30008000 核心載入地址

(6)ih_ep        0x30008000 核心執行地址,“theKernel”指向該地址,說明這裡藏著進入第一個函式--解壓

(7)ih_dcrc      0x38fc654e    核心校驗

(8)ih_os        0x05       #define IH_OS_LINUX  5 /* Linux */

(9)ih_arch     0x02     #define IH_CPU_ARM  2 /* ARM  */

(10)ih_type   0x02         #define IH_TYPE_KERNEL  2 /* OS Kernel Image  */

(11)ih_comp  0x00        #define IH_COMP_NONE  0 /*  No  Compression Used */

(12)ih_name         Linux_2.6.30.4-EmbedSky

三、u-boot核心啟動流程概述

  前文已經說明u-boot只支援uImage,步驟三、四都是針對uImage的。

  另外宣告一點,步驟三四的測試uboot程式碼是韋東山視訊提供的。

1、從NandFlash中讀取核心到RAM中

2、在RAM中,給核心進行重定位

3、給核心傳遞引數

4、啟動核心

四、u-boot啟動核心細節分析

1、啟動命令

從環境變數中檢視啟動命令:

2、從NandFlash中讀取核心到RAM中

  nand read.jffs2 0x30007FC0 kernel

  此命令會啟用(common/cmd_nand.c)中的do_nand函式,從而將nandflash上的kernel分割槽載入到0x30007fc0位置處。

複製程式碼
OpenJTAG> mtd

device nand0 <nandflash0>, # parts = 4
 #: name                        size            offset          mask_flags
 0: bootloader          0x00040000      0x00000000      0
 1: params              0x00020000      0x00040000      0
 2: kernel              0x00200000      0x00060000      0
 3: root                0x0fda0000      0x00260000      0

active partition: nand0,0 - (bootloader) 0x00040000 @ 0x00000000

defaults:
mtdids  : nand0=nandflash0
mtdparts: mtdparts=nandflash0:[email protected]0(bootloader),128k(params),2m(kernel),-(root)
複製程式碼

  從分割槽表中,可以看出kernel分割槽的起始地址是0x60000,大小是0x200000(2M),這條命令實際上等效於

nand read.jffs2 0x30007FC0 0x60000 0x200000

  也可以使用命令

nand read 0x30007FC0 0x60000 0x200000

  nand read.jffs2可以自動頁對齊,所以大小可以是非頁整的;如果使用nand read的大小必須是頁對齊的。

3、讀取uImage頭部

  bootm 0x30007fc0

  此命令會啟用(common/cmd_bootm.c)中的do_bootm函式,從而開始執行

2、在RAM中,給核心進行重定位
3、給核心傳遞引數
4、啟動核心

image_header_t header;  定義一個全域性變數header,是讀取頭部的緩衝區

addr = simple_strtoul(argv[1], NULL, 16);  定位頭部地址,將字串“0x30007fc0”轉化為整型

printf ("## Booting image at %08lx ...\n", addr); 顯示從哪兒啟動

memmove (&header, (char *)addr, sizeof(image_header_t)); 讀取頭部到header變數中

4、判斷當前的記憶體區是否是uImage的開始位置

複製程式碼
 if (ntohl(hdr->ih_magic) != IH_MAGIC) {
      {
  puts ("Bad Magic Number\n");
  SHOW_BOOT_PROGRESS (-1);
  return 1;
     }
 }
複製程式碼

注意到:

#define IH_MAGIC 0x27051956 /* Image Magic Number  */(include/image.h)

5、校驗頭部

複製程式碼
    data = (ulong)&header;
    len  = sizeof(image_header_t);

    checksum = ntohl(hdr->ih_hcrc);
    hdr->ih_hcrc = 0;

    if (crc32 (0, (uchar *)data, len) != checksum) {
        puts ("Bad Header Checksum\n");
        SHOW_BOOT_PROGRESS (-2);
        return 1;
    }
複製程式碼

6、列印頭部資訊

    /* for multi-file images we need the data part, too */
    print_image_hdr ((image_header_t *)addr);

7、核查核心資料

複製程式碼
    data = addr + sizeof(image_header_t);
    len  = ntohl(hdr->ih_size);

    if (verify) {
        puts ("   Verifying Checksum ... ");
        if (crc32 (0, (uchar *)data, len) != ntohl(hdr->ih_dcrc)) {
            printf ("Bad Data CRC\n");
            SHOW_BOOT_PROGRESS (-3);
            return 1;
        }
        puts ("OK\n");
    }
    SHOW_BOOT_PROGRESS (4);
複製程式碼

  注意到data已經跳過了uImage的頭部,指向了真正的核心首部,也即0x30008000。

8、核查架構、核心型別、壓縮型別等資訊,其中會涉及到重定位

複製程式碼
    len_ptr = (ulong *)data;

#if defined(__PPC__)
    if (hdr->ih_arch != IH_CPU_PPC)
#elif defined(__ARM__)
    if (hdr->ih_arch != IH_CPU_ARM)
#elif defined(__I386__)
    if (hdr->ih_arch != IH_CPU_I386)
#elif defined(__mips__)
    if (hdr->ih_arch != IH_CPU_MIPS)
#elif defined(__nios__)
    if (hdr->ih_arch != IH_CPU_NIOS)
#elif defined(__M68K__)
    if (hdr->ih_arch != IH_CPU_M68K)
#elif defined(__microblaze__)
    if (hdr->ih_arch != IH_CPU_MICROBLAZE)
#elif defined(__nios2__)
    if (hdr->ih_arch != IH_CPU_NIOS2)
#elif defined(__blackfin__)
    if (hdr->ih_arch != IH_CPU_BLACKFIN)
#elif defined(__avr32__)
    if (hdr->ih_arch != IH_CPU_AVR32)
#else
# error Unknown CPU type
#endif
    {
        printf ("Unsupported Architecture 0x%x\n", hdr->ih_arch);
        SHOW_BOOT_PROGRESS (-4);
        return 1;
    }
    SHOW_BOOT_PROGRESS (5);

    switch (hdr->ih_type) {
    case IH_TYPE_STANDALONE:
        name = "Standalone Application";
        /* A second argument overwrites the load address */
        if (argc > 2) {
            hdr->ih_load = htonl(simple_strtoul(argv[2], NULL, 16));
        }
        break;
    case IH_TYPE_KERNEL:
        name = "Kernel Image";
        break;
    case IH_TYPE_MULTI:
        name = "Multi-File Image";
        len  = ntohl(len_ptr[0]);
        /* OS kernel is always the first image */
        data += 8; /* kernel_len + terminator */
        for (i=1; len_ptr[i]; ++i)
            data += 4;
        break;
    default: printf ("Wrong Image Type for %s command\n", cmdtp->name);
        SHOW_BOOT_PROGRESS (-5);
        return 1;
    }
    SHOW_BOOT_PROGRESS (6);

    /*
     * We have reached the point of no return: we are going to
     * overwrite all exception vector code, so we cannot easily
     * recover from any failures any more...
     */

    iflag = disable_interrupts();

#ifdef CONFIG_AMIGAONEG3SE 
    /*
     * We've possible left the caches enabled during
     * bios emulation, so turn them off again
     */
    icache_disable();  
    invalidate_l1_instruction_cache();
    flush_data_cache();
    dcache_disable();
#endif

    switch (hdr->ih_comp) {
    case IH_COMP_NONE:
        if(ntohl(hdr->ih_load) == data) {
            printf ("   XIP %s ... ", name);
        } else {
#if defined(CONFIG_HW_WATCHDOG) || defined(CONFIG_WATCHDOG)
            size_t l = len;
            void *to = (void *)ntohl(hdr->ih_load);
            void *from = (void *)data;

            printf ("   Loading %s ... ", name);

            while (l > 0) {
                size_t tail = (l > CHUNKSZ) ? CHUNKSZ : l;
                WATCHDOG_RESET();
                memmove (to, from, tail);
                to += tail;
                from += tail;
                l -= tail;
            }
#else    /* !(CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG) */
            memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len);
#endif    /* CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG */
        }
        break;
    case IH_COMP_GZIP:
        printf ("   Uncompressing %s ... ", name);
        if (gunzip ((void *)ntohl(hdr->ih_load), unc_len,
                (uchar *)data, &len) != 0) {
            puts ("GUNZIP ERROR - must RESET board to recover\n");
            SHOW_BOOT_PROGRESS (-6);
            do_reset (cmdtp, flag, argc, argv);
        }
        break;
#ifdef CONFIG_BZIP2
    case IH_COMP_BZIP2:
        printf ("   Uncompressing %s ... ", name);
        /*
         * If we've got less than 4 MB of malloc() space,
         * use slower decompression algorithm which requires
         * at most 2300 KB of memory.
         */
        i = BZ2_bzBuffToBuffDecompress ((char*)ntohl(hdr->ih_load),
                        &unc_len, (char *)data, len,
                        CFG_MALLOC_LEN < (4096 * 1024), 0);
        if (i != BZ_OK) {
            printf ("BUNZIP2 ERROR %d - must RESET board to recover\n", i);
            SHOW_BOOT_PROGRESS (-6);
            udelay(100000);
            do_reset (cmdtp, flag, argc, argv);
        }
        break;
#endif /* CONFIG_BZIP2 */
    default:
        if (iflag)
            enable_interrupts();
        printf ("Unimplemented compression type %d\n", hdr->ih_comp);
        SHOW_BOOT_PROGRESS (-7);
        return 1;
    }
    puts ("OK\n");
    SHOW_BOOT_PROGRESS (7);

    switch (hdr->ih_type) {
    case IH_TYPE_STANDALONE:
        if (iflag)
            enable_interrupts();

        /* load (and uncompress), but don't start if "autostart"
         * is set to "no"
         */
        if (((s = getenv("autostart")) != NULL) && (strcmp(s,"no") == 0)) {
            char buf[32];
            sprintf(buf, "%lX", len);
            setenv("filesize", buf);
            return 0;
        }
        appl = (int (*)(int, char *[]))ntohl(hdr->ih_ep);
        (*appl)(argc-1, &argv[1]);
        return 0;
    case IH_TYPE_KERNEL:
    case IH_TYPE_MULTI:
        /* handled below */
        break;
    default:
        if (iflag)
            enable_interrupts();
        printf ("Can't boot image type %d\n", hdr->ih_type);
        SHOW_BOOT_PROGRESS (-8);
        return 1;
    }
    SHOW_BOOT_PROGRESS (8);
複製程式碼

  在這部分程式碼中,有這麼一部分關於壓縮型別的:

複製程式碼
    switch (hdr->ih_comp) {
    case IH_COMP_NONE:
        if(ntohl(hdr->ih_load) == data) {
            printf ("   XIP %s ... ", name);
        } else {
#if defined(CONFIG_HW_WATCHDOG) || defined(CONFIG_WATCHDOG)
            size_t l = len;
            void *to = (void *)ntohl(hdr->ih_load);
            void *from = (void *)data;

            printf ("   Loading %s ... ", name);

            while (l > 0) {
                size_t tail = (l > CHUNKSZ) ? CHUNKSZ : l;
                WATCHDOG_RESET();
                memmove (to, from, tail);
                to += tail;
                from += tail;
                l -= tail;
            }
#else    /* !(CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG) */
            memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len);
#endif    /* CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG */
        }
        break;
複製程式碼

  可以看到,u-boot會判斷當前去除uImage頭部核心程式碼所處的位置(7步驟已經說明地址是data)是否與編譯時安排的重定位位置(hdr->ih_load)一致。

  如果一致,就列印一句話例如:"XIP Kernel Image ... OK"(XIP表示的是Execute In Place就是就地執行的意思,因為不用搬運

  如果不一致,則需要呼叫 memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len);進行核心的重定位,要知道它有2M多的大小,會花費一些時間。儘量使讀取核心的時候,就讀取到hdr->ih_load-64的位置上,這樣就不必再搬運一次。

9、根據作業系統型別,啟動對應的作業系統

複製程式碼
    switch (hdr->ih_os) {
    default:            /* handled by (original) Linux case */
    case IH_OS_LINUX:
#ifdef CONFIG_SILENT_CONSOLE
        fixup_silent_linux();
#endif
        do_bootm_linux  (cmdtp, flag, argc, argv,
                 addr, len_ptr,