uboot啓動linux的過程 uboot全局變量 uboot全局變量

 1、概述html

  linux內核鏡像常見到的有兩種形式,zImage和uImage。這兩種文件的格式稍有差異,因此啓動這兩種格式的內核鏡像也會有所不一樣。目前,uboot只支持啓動uImage類型的鏡像,對zImage還不支持(可是能夠移植,TQ2440就是這樣作的)。linux

2、uImage和zImageios

一、zImage架構

       zImage是用命令「#make zImage」生成的,我截取了生成信息最後部分的內容以下:app

  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控制範圍,在內核鏡像的頭部嵌有解壓函數)中必然會對應一個解壓過程。less

二、uImageide

(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製做的工具)這個工具生成的。post

root@daneiqi:/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

3、u-boot內核啓動流程概述

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

  另外聲明一點,步驟三四的測試uboot代碼是韋東山視頻提供的。

一、從NandFlash中讀取內核到RAM中

二、在RAM中,給內核進行重定位

三、給內核傳遞參數

四、啓動內核

4、u-boot啓動內核細節分析

一、啓動命令

從環境變量中查看啓動命令:

二、從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:256k@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的大小必須是頁對齊的。

三、讀取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變量中

四、判斷當前的內存區是不是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)

五、校驗頭部

    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;
    }

六、打印頭部信息

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

七、覈查內核數據

    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。

八、覈查架構、內核類型、壓縮類型等信息,其中會涉及到重定位

    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);
View Code

  在這部分代碼中,有這麼一部分關於壓縮類型的:

    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)一致。

  若是一致,就打印一句話。

  若是不一致,則須要調用 memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len);進行內核的重定位,要知道它有2M多的大小,會花費一些時間。儘可能使讀取內核的時候,就讀取到hdr->ih_load-64的位置上,這樣就沒必要再搬運一次。

九、根據操做系統類型,啓動對應的操做系統

    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, verify);
        break;
    case IH_OS_NETBSD:   

十、執行do_bootm_linux,繼續啓動linux系統

  此函數在lib_arm/armlinux.c中

    void (*theKernel)(int zero, int arch, uint params);
    image_header_t *hdr = &header;
    bd_t *bd = gd->bd;

#ifdef CONFIG_CMDLINE_TAG
    char *commandline = getenv ("bootargs");
#endif

    theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);

  可見,已經將內核運行的首地址賦給了theKernel函數指針變量,未來能夠利用這個變量調用進入內核的函數。

  另外,在進入內核以前,要給內核傳遞參數。方法是將參數以必定的結構放在內存指定的位置上,未來內核從該地址讀取數據便可。

  命令行的啓動參數存儲在以bootargs命名的對象裏,值爲

bootargs=noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0

  告訴內核,啓動後的根文件系統位於mtd的哪一個區,初始進程,以及控制檯

十一、判斷是不是一個ramdisk或者multi鏡像

    /*
     * Check if there is an initrd image
     */
    if (argc >= 3) {
        SHOW_BOOT_PROGRESS (9);

        addr = simple_strtoul (argv[2], NULL, 16);

        printf ("## Loading Ramdisk Image at %08lx ...\n", addr);

        /* Copy header so we can blank CRC field for re-calculation */
#ifdef CONFIG_HAS_DATAFLASH
        if (addr_dataflash (addr)) {
            read_dataflash (addr, sizeof (image_header_t),
                    (char *) &header);
        } else
#endif
            memcpy (&header, (char *) addr,
                sizeof (image_header_t));

        if (ntohl (hdr->ih_magic) != IH_MAGIC) {
            printf ("Bad Magic Number\n");
            SHOW_BOOT_PROGRESS (-10);
            do_reset (cmdtp, flag, argc, argv);
        }

        data = (ulong) & header;
        len = sizeof (image_header_t);

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

        if (crc32 (0, (unsigned char *) data, len) != checksum) {
            printf ("Bad Header Checksum\n");
            SHOW_BOOT_PROGRESS (-11);
            do_reset (cmdtp, flag, argc, argv);
        }

        SHOW_BOOT_PROGRESS (10);

        print_image_hdr (hdr);

        data = addr + sizeof (image_header_t);
        len = ntohl (hdr->ih_size);

#ifdef CONFIG_HAS_DATAFLASH
        if (addr_dataflash (addr)) {
            read_dataflash (data, len, (char *) CFG_LOAD_ADDR);
            data = CFG_LOAD_ADDR;
        }
#endif

        if (verify) {
            ulong csum = 0;

            printf ("   Verifying Checksum ... ");
            csum = crc32 (0, (unsigned char *) data, len);
            if (csum != ntohl (hdr->ih_dcrc)) {
                printf ("Bad Data CRC\n");
                SHOW_BOOT_PROGRESS (-12);
                do_reset (cmdtp, flag, argc, argv);
            }
            printf ("OK\n");
        }

        SHOW_BOOT_PROGRESS (11);

        if ((hdr->ih_os != IH_OS_LINUX) ||
            (hdr->ih_arch != IH_CPU_ARM) ||
            (hdr->ih_type != IH_TYPE_RAMDISK)) {
            printf ("No Linux ARM Ramdisk Image\n");
            SHOW_BOOT_PROGRESS (-13);
            do_reset (cmdtp, flag, argc, argv);
        }

#if defined(CONFIG_B2) || defined(CONFIG_EVB4510) || defined(CONFIG_ARMADILLO)
        /*
         *we need to copy the ramdisk to SRAM to let Linux boot
         */
        memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len);
        data = ntohl(hdr->ih_load);
#endif /* CONFIG_B2 || CONFIG_EVB4510 */

        /*
         * Now check if we have a multifile image
         */
    } else if ((hdr->ih_type == IH_TYPE_MULTI) && (len_ptr[1])) {
        ulong tail = ntohl (len_ptr[0]) % 4;
        int i;

        SHOW_BOOT_PROGRESS (13);

        /* skip kernel length and terminator */
        data = (ulong) (&len_ptr[2]);
        /* skip any additional image length fields */
        for (i = 1; len_ptr[i]; ++i)
            data += 4;
        /* add kernel length, and align */
        data += ntohl (len_ptr[0]);
        if (tail) {
            data += 4 - tail;
        }

        len = ntohl (len_ptr[1]);

    } else {
        /*
         * no initrd image
         */
        SHOW_BOOT_PROGRESS (14);

        len = data = 0;
    }

#ifdef    DEBUG
    if (!data) {
        printf ("No initrd\n");
    }
#endif
View Code

十二、給內核傳遞參數

#if defined (CONFIG_SETUP_MEMORY_TAGS) || \
    defined (CONFIG_CMDLINE_TAG) || \
    defined (CONFIG_INITRD_TAG) || \
    defined (CONFIG_SERIAL_TAG) || \
    defined (CONFIG_REVISION_TAG) || \
    defined (CONFIG_LCD) || \
    defined (CONFIG_VFD)
    setup_start_tag (bd);
#ifdef CONFIG_SERIAL_TAG
    setup_serial_tag (&params);
#endif
#ifdef CONFIG_REVISION_TAG
    setup_revision_tag (&params);
#endif
#ifdef CONFIG_SETUP_MEMORY_TAGS
    setup_memory_tags (bd);
#endif
#ifdef CONFIG_CMDLINE_TAG
    setup_commandline_tag (bd, commandline);
#endif
#ifdef CONFIG_INITRD_TAG
    if (initrd_start && initrd_end)
        setup_initrd_tag (bd, initrd_start, initrd_end);
#endif
#if defined (CONFIG_VFD) || defined (CONFIG_LCD)
    setup_videolfb_tag ((gd_t *) gd);
#endif
    setup_end_tag (bd);
#endif

  比較重要的函數有:

   setup_start_tag (bd);

  setup_memory_tags (bd);

  setup_commandline_tag (bd, commandline);

  setup_end_tag (bd);

  其中 bd->bi_boot_params(參考uboot全局變量),bi_boot_params=>>0x30000100,啓動參數存放的位置。

1三、啓動內核

    printf ("\nStarting kernel ...\n\n");
  theKernel (0, bd->bi_arch_number, bd->bi_boot_params);

  把機器碼以及啓動參數存放的位置都告訴給內核。

5、啓動過程展現

一、不須要重定位啓動

二、重定位啓動

      下例中讀取到的位置,不是合適的位置,內核的入口不是0x30008000,因此還要對內核進行重定位,也就是將內核搬移到指定的位置。

6、u-boot啓動zImage

 一、直接啓動zImage

  既然,zImage是uImage去除頭部的部分,那麼能夠從0x30008000直接啓動zImage,咱們用go命令去執行。

可見,內核的第一個函數果真是解壓函數。可是程序卡到圖片最後的位置,不能繼續執行。

  緣由是因爲沒有給內核傳遞啓動參數,也就是說在執行函數theKernel以前,沒有作好準備

void (*theKernel)(int zero, int arch, uint params);

二、移植u-boot支持啓動zImage

  具體代碼可看TQ2440開發板的u-boot代碼。

  再來看一下啓動大綱:

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

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

3、給內核傳遞參數

4、啓動內核

  能夠直接從nandflash中將內核zImage讀取到內存0x30008000位置處,而後在0x30000100位置處傳遞參數

也就是調用函數 

setup_start_tag (bd);
setup_memory_tags (bd);
setup_commandline_tag (bd, commandline);
setup_end_tag (bd);

  最後,調用theKernel函數啓動內核。

 

參考資料:韋東山u-boot啓動內核視頻

     uboot全局變量

       linux的uboot啓動映像、zImage和uImage的區別

相關文章
相關標籤/搜索