分享

制作Linux映像和Kernel的启动

 langhuayipian 2011-02-22

进入bootm命令分析之前,先来看看README里面的几段话,简单翻译之

制作Linux映像
============

使用uboot时,内核通常生成的文件"zImage""bzImage"是没用的。较新一些的内核原码会
生成"uImage", 这个可以为uboot使用。
"uImage"
全用了一个工具"tools/mkimage"来封装压缩后的映像文件,在其头部添加一
些信息以及crc32校验等。

我们需要做如下几件事情:

*
制作一个标准的内核映像文件"vmlinux"(这个是ELF格式的)
*
将其转换为二进制映像
        arm-linux-objcopy -O binarty \
                -R .note -R .comment \
                -S vmlinux linux.bin

*
压缩这个二进制映像
        gzip -9 linux.bin
*
封装这个已压缩的映像
        mkimage -A arm -O linux -T kernel -C gzip \
                -a 0 -e 0 -n "Linux Kernel Image" \
                -d linux.bin.gz uImage

For i.MX51 BBG Board
    $ ~/myandroid/bootable/bootloader/uboot-imx/tools/mkimage -A arm -O linux -T kernel -C none -a 0x90008000 -e 0x90008000 -n "Android Linux Kernel" -d ./zImage ./uImage

    During boot, when uboot try to load above "uImage", it will know to load it (without image head added by mkimage tool) into 0x90008000 (specified by "-a"), and then jump to 0x90008000  (specified by "-e") to execute it. "-C none" means no compression when generating "uImage". This is because the original zImage is already a compressed one.


mkimage
也可以用来制作ramdisk映像。
mkimage
在映像的头部添加了64字节的信息,用来指明映像文件用于的体系结构,操作系统, 映像类型, 压缩方式,入口地址,时间戳,crc32校验等。

mkimage
一般用于两种情况: 为映像添加头信息 列出文件的头信息
         tools/mkimage -l image
             -l ==>
列出映像的头信息

添加头信息时
    tools/mkimage -A arch -O os -T type -C comp -a addr -e ep \
              -n name -d data_file image
      -A ==> set architecture to 'arch'
体系结构
      -O ==> set operating system to 'os'
操作系统
      -T ==> set image type to 'type'
映像类型
      -C ==> set compression type 'comp'
压缩方式
      -a ==> set load address to 'addr' (hex)
加载地址
      -e ==> set entry point to 'ep' (hex)
入口地址
      -n ==> set image name to 'name'
映像文件名
      -d ==> use image data from 'datafile'
制作映像的时间

----------------------------------------------
bootm
=====
这个命令用于启动一个操作系统映像。它会从映像文件的头部取得一些信息,这些信息包括:
映像文件的基于的cpu架构、其操作系统类型、映像的类型、压缩方式、映像文件在内存中的加载地址、
映像文件运行的入口地址、映像文件名等。
紧接着bootm将映像加载到指定的地址,如果需要的话,还会解压映像并传递必要有参数给内核,最后
跳到入口地址进入内核。

bootm
的第一个参数是映像存储的地址。

例如Linux,可以附带一个参数。此参数会被认为是一个initrd的起始地址,此时bootm命令有三个
步骤:首先解压Linux内核映像并将其复制到ram中,然后将ramdisk映像加载到ram中,最后将控制
权交给内核,并向内核传递ramdisk的位置和大小等信息.

单单用来启动Linux内核,而没有initrd时,可用如下命令:
 => bootm ${kernel_addr}

如果还有initrd,则可使用下面的命令:
 => bootm ${kernel_addr} ${ramdisk_addr}

使用时确保地址参数正确。

当待启动的映像文件已经被加载于RAM(例如用tftp下载到sdram),需要对内存部局更加小心。
需要确保映像文件(可能是已被压缩的映像文件)加载的地址不会与解压后的内核位置重叠。例如,如果
将一个ramdisk映像加载于内存的低地址,则在Linux内核加载时可能会覆盖它。这将导致未知的系统
崩溃。

上面几段是README里的内容,介绍了内核映像的制作及bootm命令。下面来看具体代码



do_bootm

static bootm_headers_t images;
这个静态全局变量就是头信息结构了。注意这里是
bootm_headers_t
而不是image_header_t.


typedef struct bootm_headers {
    image_header_t    *legacy_hdr_os;        /* image header pointer */
    image_header_t    legacy_hdr_os_copy;    /* header copy */
    ulong        legacy_hdr_valid;
    int        verify;        /* getenv("verify")[0] != 'n' */
    struct lmb    *lmb;        /* for memory mgmt */
} bootm_headers_t;


    mem_start = getenv_bootm_low(); //
获得内存sdram的起始地址
1.
取环境变量中的boom_low
2.
没有boom_low,取则配置时的CFG_SDRAM_BASE
3.
没有CFG_SDRAM_BASE则取 gd->bd->bi_dram[0].start
4.
若还没有,则取 0

    mem_size = getenv_bootm_size(); //
获得内存sdram的大小
1.
取环境变量中的bootm_size
2.
若没有bootm_size,则取 gd->bd->bi_dram[0].size

    lmb_add(&lmb, (phys_addr_t)mem_start, mem_size);
//

os_hdr = boot_get_kernel (cmdtp, flag, argc, argv,
            &images, &os_data, &os_len);

/******************************************************************************
*******
进入   boot_get_kernel       ********************************************
*******************************************************************************/



    if (argc < 2) {
        img_addr = load_addr;
        debug ("*  kernel: default image load address = 0x%08lx\n",
                load_addr);
    } else {
        img_addr = simple_strtoul(argv[1], NULL, 16);
        debug ("*  kernel: cmdline image address = 0x%08lx\n", img_addr);
    }

//argc < 2
即直接输入bootm命令,此时映像存储地址为load_addr; 定义处有
//ulong load_addr = CFG_LOAD_ADDR;
即初始的映像存储地址,但在u-boot运行期间,load_addr
//
值会被环境变量所更改。
//
bootm后面还带了一参数,如bootm 30000,则映像的存储地址为0x30000
//
接下来要从nand中读出kernel image时,就从地址0x3000开始读

    img_addr = genimg_get_image (img_addr);

//
从存储介质中将image读出,img_addr是前面获得的映像存储地址。这个函数体内被CONFIG_HAS_DATAFLASH
//
包围,是指kernel存于atmel的数据flash.由于我这里只有nand,所以这个函数相当于空。
//
在运行bootm命令之前,一般使用tftp or nand read.jffs2 kernel image 先读到sdram中,所以此时
// kernel image
已经在sdram中了。

genimg_get_format ((void *)img_addr))

//
判断imgae的格式,通过判断头信息中的幻数来确断格式,格式有三种
// IMAGE_FORMAT_LEGACY
// IMAGE_FORMAT_FIT  
这里不支持,在预定义里就没有定义 CONFIG_FIT这项
// IMAGE_FORMAT_INVALID
无效的格式

hdr = image_get_kernel (img_addr, images->verify);
//
检查头信息,函数内做了以下几件事
// image_check_magic
再次检查幻数
// image_check_hcrc 
头信息校验
// image_print_contents (hdr);
打印头信息
// image_check_dcrc
数据校验,这里是校验kernel实际的数据
// image_check_target_arch
检查运行的cpu架构


switch (image_get_type (hdr)) {
        case IH_TYPE_KERNEL:
            *os_data = image_get_data (hdr);
            *os_len = image_get_data_size (hdr);
            break;
        case IH_TYPE_MULTI:
            image_multi_getimg (hdr, 0, os_data, os_len);
            break;
                default:
            printf ("Wrong Image Type for %s command\n", cmdtp->name);
            show_boot_progress (-5);
            return NULL;
        }

//
这里先检查映像的类型,有kernel, ramdisk, multi, firmware, script等,
//
根据不同的类型来获得真正的image data(即不包括头信息的imgae)的起始地址(os_data) 和大小(os_len)

memmove (&images->legacy_hdr_os_copy, hdr, sizeof(image_header_t));

//
再次来看下images结构

typedef struct bootm_headers {
    image_header_t    *legacy_hdr_os;        /* image header pointer */
    image_header_t    legacy_hdr_os_copy;    /* header copy */
    ulong        legacy_hdr_valid;
    int        verify;        /* getenv("verify")[0] != 'n' */
    struct lmb    *lmb;        /* for memory mgmt */
} bootm_headers_t;

//
可知上面的memmove实际上是将头信息都保存到images legacy_hdr_os_copy成员中

        images->legacy_hdr_os = hdr;
//
保存kernel image头信息的地址

        images->legacy_hdr_valid = 1;
//
映像文件是有效的

return (void *)img_addr;
//
最后返回映像文件的地址


/******************************************************************************
*******
退出   boot_get_kernel       ********************************************
*******************************************************************************/
//小结:从上面的分析可以看出 boot_get_kernel 的作用就是找到kernel image,并通过头信息的检查
//来判断其是否有效。最后返回kernel image的地址给do_bootm


//
继续do_bootm

    switch (genimg_get_format (os_hdr)) { //boot_get_kernel
中做过一次,检查幻数,判断image
    case IMAGE_FORMAT_LEGACY:
        type = image_get_type (os_hdr);
// 映像类型
        comp = image_get_comp (os_hdr);
// 压缩方式
        os = image_get_os (os_hdr);  
// 操作系统类型

        image_end = image_get_image_end (os_hdr);
// 结束地址
        load_start = image_get_load (os_hdr);
// 头信息中定义的加载地址
        break;

    image_start = (ulong)os_hdr;
// 映像起始地址,包括头信息
    load_end = 0;
    type_name = genimg_get_type_name (type);
// 映像名


    iflag = disable_interrupts();
// 关中断,这里为空,中断未使能


switch (comp) {
//根据压缩方式来选择将要执行的动作
    case IH_COMP_NONE: // image
未压缩
        if (load_start == (ulong)os_hdr) {
// 头信息中的加载地址是否等于现在所在的地址
            printf ("   XIP %s ... ", type_name);
        } else {
            printf ("   Loading %s ... ", type_name);

            memmove_wd ((void *)load_start,
// 若不等的话,还要先将image复制到
                   (void *)os_data, os_len, CHUNKSZ);
// 头信息指定的加载地址处
        }
        load_end = load_start + os_len;
// 加载后,整个image的结束地址
        puts("OK\n");
        break;
    case IH_COMP_GZIP: // gzip
的压缩方式
        printf ("   Uncompressing %s ... ", type_name);
        if (gunzip ((void *)load_start, unc_len,   
// 先将image全部解压到头信息中指定
                    (uchar *)os_data, &os_len) != 0) {
//的加载地址处
            puts ("GUNZIP: uncompress or overwrite error "
                "- must RESET board to recover\n");
            show_boot_progress (-6);
            do_reset (cmdtp, flag, argc, argv);
        }

        load_end = load_start + os_len; //
加载后,整个image的结束地址
        break;



if ((load_start < image_end) && (load_end > image_start)) {
        if (images.legacy_hdr_valid) {
            if (image_get_type (&images.legacy_hdr_os_copy) == IH_TYPE_MULTI)
                puts ("WARNING: legacy format multi component "
                    "image overwritten\n");
        } else {
            puts ("ERROR: new format image overwritten - "
                "must RESET the board to recover\n");
            show_boot_progress (-113);
            do_reset (cmdtp, flag, argc, argv);
        }
    }

// load_start load_end
是头信息中预定的
// image_start image_end
是映像文件在内存中放的位置
//
这里主要判断存放地址和加载地址是否有重叠,因为重叠会造成未知的系统崩溃


    lmb_reserve(&lmb, load_start, (load_end - load_start));

switch (os) {
    default:            /* handled by (original) Linux case */
    case IH_OS_LINUX:
        do_bootm_linux (cmdtp, flag, argc, argv, &images);
        break;

    case IH_OS_NETBSD:
        do_bootm_netbsd (cmdtp, flag, argc, argv, &images);
        break;

    case IH_OS_RTEMS:
        do_bootm_rtems (cmdtp, flag, argc, argv, &images);
        break;
}

//
上面可以很清楚的看到,根据头信息中定义的操作系统类型,进入相应的入口中
//
这里Linux就是 do_bootm_linux(cmdtp, flag, argc, argv, &images)



do_bootm_linux() 位于 lib_arm/bootm.c

void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[],
             bootm_headers_t *images)
{
    ulong    initrd_start, initrd_end;
    ulong    ep = 0;
    bd_t    *bd = gd->bd;
    char    *s;
    int    machid = bd->bi_arch_number;
    void    (*theKernel)(int zero, int arch, uint params);
    int    ret;

#ifdef CONFIG_CMDLINE_TAG
    char *commandline = getenv ("bootargs");
//
启动时的命令行参数
#endif

    /* find kernel entry point */
    if (images->legacy_hdr_valid) {
        ep = image_get_ep (&images->legacy_hdr_os_copy);
               
// 获得内核入口地址,即头地址+0x40 跳过头信息
    } else {

        puts ("Could not find kernel entry point!\n");
        goto error;
    }
    theKernel = (void (*)(int, int, uint))ep;
// 终于看到theKernel了,给函数指针赋地址值

    s = getenv ("machid");
// 目标板的ID smdk2410193
    if (s) {
        machid = simple_strtoul (s, NULL, 16);
        printf ("Using machid 0x%x from environment\n", machid);
    }

    //
检查是否还有ramdisk,如果有的话,则将其加载到指定地址
    ret = boot_get_ramdisk (argc, argv, images, IH_ARCH_ARM,
            &initrd_start, &initrd_end);
    if (ret)
 // 只有当有ramdisk,且加载ramdisk失败时才返回1
        goto error;

    // taglist
向内核传递的参数的设置
    //
这里先去掉预编译,加载memory command line 参数

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

    setup_end_tag (bd);

    /* we assume that the kernel is in place */
    printf ("\nStarting kernel ...\n\n");

    /*
     *
在进入内核前要做几件事
     *
关中断 关I/D-cache
     */

    cleanup_before_linux ();

    /*
     *
进入内核,注意传递的几个参数
     * 1. 0
     * 2. mach id
     * 3.
参数taglist地址
     */

    theKernel (0, machid, bd->bi_boot_params);
    /* does not return */
    return;

error:
    do_reset (cmdtp, flag, argc, argv);
    return;
}


/*
 *
重点来看一下taglist, 因为这个之前在vivi中没有。vivi中使用的是param struct
 *
来传递参数的。新的内核都推荐用taglist来传递参数
 */

static struct tag *params;
struct tag {
        struct tag_header hdr;
        union {
                struct tag_core        core;
                struct tag_mem32    mem;
                struct tag_videotext    videotext;
                struct tag_ramdisk    ramdisk;
                struct tag_initrd    initrd;
                struct tag_serialnr    serialnr;
                struct tag_revision    revision;
                struct tag_videolfb    videolfb;
                struct tag_cmdline    cmdline;

                /*
                 * Acorn specific
                 */
                struct tag_acorn    acorn;

                /*
                 * DC21285 specific
                 */
                struct tag_memclk    memclk;
        } u;
};


+=============+==================================================+ <= bd->bi_boot_params
| tag_header  |  hdr.tag = ATAG_CORE                                             |
|    hdr      |  hdr.size = tag_size(tag_core)                                   |
+-------------+------------------------------------------------------------------+
|             |  u.core.flags    = 0                                             |
| union u     |  u.core.pagesize = 0;                                            |
|             |  u.core.rootdev  = 0;                                            |
+=============+==================================================================+
| tag_header  |  hdr.tag = ATAG_MEM                                              |
|    hdr      |  hdr.size = tag_size(tag_mem32)                                  |
+-------------+------------------------------------------------------------------+
|             |  u.mem.start = bd->bi_dram[i].start                              |
| union u     |  u.mem.size = bd->bi_dram[i].size                                |
|             |                                                                  |
+=============+==================================================================+
| tag_header  |  hdr.tag = ATAG_CMDLINE                                          |
|    hdr      | hdr.size = (sizeof(struct tag_header) + strlen(p) + 1 + 4) >> 2; |
+-------------+------------------------------------------------------------------+
|             |                                                                  |     
| union u     |  strcpy(u.cmdline.cmdline, p)                                    |
|             |                                                              |     
+=============+==================================================================+
| tag_header  |  hdr.tag = ATAG_NONE                                             |
|    hdr      |  hdr.size = 0                                                    |
+-------------+------------------------------------------------------------------+
|             |                                                                  |     
| union u     |                                                                  |
|             |                                                              |     
+=============+==================================================================+

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多