初步了解UBOOT (4)

对于do_bootm函数,它的内容如下:

int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
    ...

    image_header_t *hdr = &header;

    ...

    if (argc < 2) {
        addr = load_addr;
    } else {
        addr = simple_strtoul(argv[1], NULL, 16);  //argv[1]为bootm后面的地址参数
    }

    ...

    memmove (&header, (char *)addr, sizeof(image_header_t));

    ...

    data = addr + sizeof(image_header_t);

    ... 

    if(ntohl(hdr->ih_load) == data) {
            printf (" XIP %s ... ", name);
    }else
    {
        ...

        memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len);
    }

    ...


        do_bootm_linux  (cmdtp, flag, argc, argv,
                 addr, len_ptr, verify);

    ...

}

首先

image_header_t *hdr = &header;  
memmove (&header, (char *)addr, sizeof(image_header_t));

则是会读取uImage的头部,填充到image_header_t类型的结构体header中,对于uImage来说,它分成两个部分,其中前64字节是头部,后面的才是真正的内核。对于结构体image_header_t来说,它有两个成员比较重要,分别为ih_load和ih_ep,前者指出了内核的加载地址,后者指出了内核的入口地址。所以读取uImage头部之后,这两个成员就有相应值。

下面这句语句指出了内核的位置,即紧接着放在了uImage的头部后面。

data = addr + sizeof(image_header_t); 

下面这句语句则是将内核移动到hdr->ih_load指定的加载地址中,若内核所在地址ata与hdr->ih_load所指示的加载地址一样,则不需要移动,否则则移动:

if(ntohl(hdr->ih_load) == data) {
            printf (" XIP %s ... ", name);
}else
{
...

    memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len);
}

do_bootm函数执行到最后,则会调用do_bootm_linux这个函数,用它来完成最后的一些工作。即向内核传递一些参数。例如内存的大小,板子的机器ID,内核需要的其它一些参数等。最后跳到入口地址启动内核。

以下的do_bootm_linux函数的内容:

void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[],
             ulong addr, ulong *len_ptr, int verify)
{
    void (*theKernel)(int zero, int arch, uint params);
    bd_t *bd = gd->bd;

    ...

    char *commandline = getenv ("bootargs");

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

    ...

    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");

    cleanup_before_linux ();

    theKernel (0, bd->bi_arch_number, bd->bi_boot_params);
}

首先,下面的语句指出了如何让theKernel指向入口地址,

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

它是一个函数指针
void (*theKernel)(int zero, int arch, uint params);

而hdr->ih_ep指出了内核的入口地址

接下来则是Uboot传递一些参数给内核,具体完成操作的代码如下:

    setup_start_tag (bd);

    ...

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

    ...

    setup_end_tag (bd);

其中setup_start_tag (bd)与setup_end_tag (bd)标志着开始和结束。

首先看 setup_start_tag (bd)

static void setup_start_tag (bd_t *bd)
{
    params = (struct tag *) bd->bi_boot_params;

    params->hdr.tag = ATAG_CORE;
    params->hdr.size = tag_size (tag_core);

    params->u.core.flags = 0;
    params->u.core.pagesize = 0;
    params->u.core.rootdev = 0;

    params = tag_next (params);
}

bd->bi_boot_params指出了参数存放的地址,而bd->bi_boot_params该值则是在uboot第二阶段的启动时board_init函数设置的(可以看我写的《初步了解UBOOT (2)》),然后在此地址上存放setup_start_tag的相关参数。

接着看setup_memory_tags (bd):

static void setup_memory_tags (bd_t *bd)
{
    int i;

    for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) {
        params->hdr.tag = ATAG_MEM;
        params->hdr.size = tag_size (tag_mem32);

        params->u.mem.start = bd->bi_dram[i].start;
        params->u.mem.size = bd->bi_dram[i].size;

        params = tag_next (params);
    }
}

将一些和内存相关的参数放到参数存放的地址上,该函数指出了内存的起始地址和大小,将这些信息告诉内核。其中bd->bi_dram[i].start与bd->bi_dram[i].size这两个值则是在uboot第二阶段的启动时dram_init函数中设置的(可以看我写的《初步了解UBOOT (2)》)。

再接下来看setup_commandline_tag(bd, commandline):

static void setup_commandline_tag (bd_t *bd, char *commandline)
{
    char *p;

    if (!commandline)
        return;

    /* eat leading white space */
    for (p = commandline; *p == ' '; p++);

    /* skip non-existent command lines so the kernel will still * use its default command line. */
    if (*p == '\0')
        return;

    params->hdr.tag = ATAG_CMDLINE;
    params->hdr.size =
        (sizeof (struct tag_header) + strlen (p) + 1 + 4) >> 2;

    strcpy (params->u.cmdline.cmdline, p);

    params = tag_next (params);
}

该函数是将命令行参数经过解析及相关内容放到参数存放的地址上,其中commandline = getenv (“bootargs”),即commandline等于bootargs环境变量的内容,在我的开发板中,它的值为:

bootargs=noinitrd root=/dev/nfs nfsroot=192.168.3.16:/work/nfs_root/first ip=192.168.3.123:192.168.3.16:192.168.3.1:255.255.255.0::eth0:off init=/linuxrc console=ttySAC0

bootargs环境变量与根文件系统有关,它指出内核如何去挂载根文件系统,以及挂载了根文件系统之后如何启动第一个应用程序

最后setup_end_tag (bd)函数只是标志着结束。

至此,Uboot已经将所有要传递给内核的参数按照约定的格式存放到约定的地址中。

最后一条语句

theKernel (0, bd->bi_arch_number, bd->bi_boot_params);

则是调用theKernel函数指针,进行内核的启动。至此,Uboot的启动阶段正式结束,接下来的事情就完全交接给内核,已经与Uboot无关了。

至此本人对Uboot算有一个大概的了解了,但是要完全了解Uboot的机制还有很轻松的去移植它的话,还有很大的差距,写下这些文章只是对自己的一个小小肯定,很多细节没有去深究,正如标题所示,只是让自己对Uboot有一个初步的认识,写的有错的地方希望各位能指点出来指教一下,也希望能一起交流,目前是一位新手,至于日后一定会一直深入的去研究,以后的路上我还会更加的去努力。谢谢各位阅读!

你可能感兴趣的:(u-boot,theKernel,jz2440,uboot参数传递,uboot交接)