初步瞭解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有一個初步的認識,寫的有錯的地方希望各位能指點出來指教一下,也希望能一起交流,目前是一位新手,至於日後一定會一直深入的去研究,以後的路上我還會更加的去努力。謝謝各位閱讀!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章