[uboot] uboot啟動kernel篇(二)——bootm跳轉到kernel的流程

來源:互聯網
上載者:User
一、bootm說明

bootm這個命令用於啟動一個作業系統映像。它會從映像檔案的頭部取得一些資訊,這些資訊包括:映像檔案的基於的cpu架構、其作業系統類型、映像的類型、壓縮方式、映像檔案在記憶體中的載入地址、映像檔案啟動並執行入口地址、映像檔案名稱等。
緊接著bootm將映像載入到指定的地址,如果需要的話,還會解壓映像並傳遞必要有參數給核心,最後跳到入口地址進入核心。
這裡的描述參考(http://blog.chinaunix.net/uid-20799298-id-99666.html)

需要開啟的宏

CONFIG_BOOTM_LINUX=yCONFIG_CMD_BOOTM=y
二、bootm使用方式

在《uboot啟動kernel篇(一)——Legacy-uImage & FIT-uImage》中我們知道了uImage有兩種格式。 Legacy-uImage
對於Legacy-uImage,我們需要另外載入ramdisk和fdt到RAM上面。
執行的命令如下

假設Legacy-uImage的載入地址是0x20008000,ramdisk的載入地址是0x21000000,fdt的載入地址是0x22000000(1) 只載入kernel的情況下bootm 0x20008000(2) 載入kernel和ramdiskbootm 0x20008000 0x21000000(3) 載入kernel和fdtbootm 0x20008000 - 0x22000000(4) 載入kernel、ramdisk、fdtbootm 0x20008000 0x21000000 0x22000000
FIT-uImage
對於FIT-uImage,kernel鏡像、ramdisk鏡像和fdt都已經打包到FIT-uImage的鏡像中了。
執行的命令如下
假設FIT-uImage的載入地址是0x30000000,啟動kernel的命令如下:bootm 0x30000000
三、bootm執行流程

建議先參考《[uboot] (第六章)uboot流程——命令列模式以及命令處理介紹》。 對應U_BOOT_CMD
我們找到bootm命令對應的U_BOOT_CMD如下:
cmd/bootm.c

U_BOOT_CMD(    bootm,  CONFIG_SYS_MAXARGS, 1,  do_bootm,    "boot application image from memory", bootm_help_text);
do_bootm參數說明
通過《[uboot] (第六章)uboot流程——命令列模式以及命令處理介紹》,當執行bootm命令時,do_bootm會被調用,參數如下:
當執行‘bootm 0x20008000 0x21000000 0x22000000’int do_bootm(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])cmdtp:傳遞的是bootm的命令表項指標,也就是_u_boot_list_2_cmd_2_bootm的指標argc=4argv[0]="bootm", argv[1]=0x20008000, arv[2]=0x21000000, argv[3]=0x22000000
do_bootm實現
do_bootm實現如下:
/*******************************************************************//* bootm - boot application image from image in memory *//*******************************************************************/int do_bootm(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]){    /* determine if we have a sub command */    argc--; argv++;    if (argc > 0) {        char *endp;        simple_strtoul(argv[0], &endp, 16);        /* endp pointing to NULL means that argv[0] was just a         * valid number, pass it along to the normal bootm processing         *         * If endp is ':' or '#' assume a FIT identifier so pass         * along for normal processing.         *         * Right now we assume the first arg should never be '-'         */        if ((*endp != 0) && (*endp != ':') && (*endp != '#'))            return do_bootm_subcommand(cmdtp, flag, argc, argv);    }        // 以上會判斷是否有子命令,這裡我們不管        // 到這裡,參數中的bootm參數會被去掉,        // 也就是當'bootm 0x20008000 0x21000000 0x22000000'時        // argc=3, argv[0]=0x20008000 , argv[1]=0x21000000, argv[2]=0x22000000        // 當‘bootm 0x30000000’時        // argc=1, argv[0]=0x30000000    return do_bootm_states(cmdtp, flag, argc, argv, BOOTM_STATE_START |        BOOTM_STATE_FINDOS | BOOTM_STATE_FINDOTHER |        BOOTM_STATE_LOADOS |        BOOTM_STATE_OS_PREP | BOOTM_STATE_OS_FAKE_GO |        BOOTM_STATE_OS_GO, &images, 1);        // 最終對調用到do_bootm_states,在do_bootm_states中執行的操作如states標識所示:        // BOOTM_STATE_START        // BOOTM_STATE_FINDOS         // BOOTM_STATE_FINDOTHER         // BOOTM_STATE_LOADOS         // BOOTM_STATE_OS_PREP         // BOOTM_STATE_OS_FAKE_GO         // BOOTM_STATE_OS_GO}

所以bootm的核心是do_bootm_states,以全域變數bootm_headers_t images作為do_bootm_states的參數。
下面詳細說明這個函數。 四、do_bootm_states軟體流程 1、資料結構說明 bootm_headers_t
bootm_headers_t用來表示bootm啟動kernel的一些資訊的結構體,其中包括了os/initrd/fdt images的資訊。
bootm會根據參數以及參數指向的鏡像來填充這個結構題裡面的成員。
最終再使用這個結構體裡面的資訊來填充kernel啟動資訊並且到跳轉到kernel中。
在uboot中使用了一個全域的bootm_headers_t images。

typedef struct bootm_headers {    /*     * Legacy os image header, if it is a multi component image     * then boot_get_ramdisk() and get_fdt() will attempt to get     * data from second and third component accordingly.     */    image_header_t  *legacy_hdr_os;     /* image header pointer */  // Legacy-uImage的鏡像頭    image_header_t  legacy_hdr_os_copy; /* header copy */ // Legacy-uImage的鏡像頭備份    ulong       legacy_hdr_valid; // Legacy-uImage的鏡像頭是否存在的標記#if IMAGE_ENABLE_FIT    const char  *fit_uname_cfg; /* configuration node unit name */ // 配置節點名    void        *fit_hdr_os;    /* os FIT image header */ // FIT-uImage中kernel鏡像頭    const char  *fit_uname_os;  /* os subimage node unit name */ // FIT-uImage中kernel的節點名    int     fit_noffset_os; /* os subimage node offset */ // FIT-uImage中kernel的節點位移    void        *fit_hdr_rd;    /* init ramdisk FIT image header */ // FIT-uImage中ramdisk的鏡像頭    const char  *fit_uname_rd;  /* init ramdisk subimage node unit name */ // FIT-uImage中ramdisk的節點名    int     fit_noffset_rd; /* init ramdisk subimage node offset */ // FIT-uImage中ramdisk的節點位移    void        *fit_hdr_fdt;   /* FDT blob FIT image header */ // FIT-uImage中FDT的鏡像頭    const char  *fit_uname_fdt; /* FDT blob subimage node unit name */ // FIT-uImage中FDT的節點名    int     fit_noffset_fdt;/* FDT blob subimage node offset */ // FIT-uImage中FDT的節點位移#endif    image_info_t    os;     /* os image info */ // 作業系統資訊的結構體    ulong       ep;     /* entry point of OS */ // 作業系統的入口地址    ulong       rd_start, rd_end;/* ramdisk start/end */ // ramdisk在記憶體上的起始地址和結束位址    char        *ft_addr;   /* flat dev tree address */ // fdt在記憶體上的地址    ulong       ft_len;     /* length of flat device tree */ // fdt在記憶體上的長度    ulong       initrd_start; //     ulong       initrd_end; //     ulong       cmdline_start; //     ulong       cmdline_end; //     bd_t        *kbd; //     int     verify;     /* getenv("verify")[0] != 'n' */ // 是否需要驗證    int     state; // 狀態標識,用於標識對應的bootm需要做什麼操作,具體看下面2.#ifdef CONFIG_LMB    struct lmb  lmb;        /* for memory mgmt */#endif} bootm_headers_t;
2、狀態說明 BOOTM_STATE_START
#define BOOTM_STATE_START (0x00000001)
開始執行bootm的一些準備動作。 BOOTM_STATE_FINDOS
#define BOOTM_STATE_FINDOS (0x00000002)
尋找作業系統鏡像 BOOTM_STATE_FINDOTHER
#define BOOTM_STATE_FINDOTHER (0x00000004)
尋找作業系統鏡像外的其他鏡像,比如FDT\ramdisk等等 BOOTM_STATE_LOADOS
#define BOOTM_STATE_LOADOS (0x00000008)
載入作業系統 BOOTM_STATE_RAMDISK
#define BOOTM_STATE_RAMDISK (0x00000010)
操作ramdisk BOOTM_STATE_FDT
#define BOOTM_STATE_FDT (0x00000020)
操作FDT BOOTM_STATE_OS_CMDLINE
#define BOOTM_STATE_OS_CMDLINE (0x00000040)
操作commandline BOOTM_STATE_OS_BD_T
#define BOOTM_STATE_OS_BD_T (0x00000080) BOOTM_STATE_OS_PREP
#define BOOTM_STATE_OS_PREP (0x00000100)
跳轉到作業系統的前的準備動作 BOOTM_STATE_OS_FAKE_GO
#define BOOTM_STATE_OS_FAKE_GO (0x00000200) /* ‘Almost’ run the OS */
偽跳轉,一般都能直接跳轉到kernel中去 BOOTM_STATE_OS_GO
#define BOOTM_STATE_OS_GO (0x00000400)
跳轉到kernel中去 3、軟體流程說明

do_bootm_states根據states來判斷要執行的操作。 主要流程簡單說明如下:
bootm的準備動作
BOOTM_STATE_START 擷取kernel資訊
BOOTM_STATE_FINDOS 擷取ramdisk和fdt的資訊
BOOTM_STATE_FINDOTHER 載入kernel到對應的位置上(有可能已經就在這個位置上了)
BOOTM_STATE_LOADOS 重新導向ramdisk和fdt(不一定需要)
BOOTM_STATE_RAMDISK、BOOTM_STATE_FDT 執行跳轉前的準備動作
BOOTM_STATE_OS_PREP 設定啟動參數,跳轉到kernel所在的地址上
BOOTM_STATE_OS_GO

在這些流程中,起傳遞作用的是bootm_headers_t images這個資料結構,有些流程是解析鏡像,往這個結構體裡寫資料。
而跳轉的時候,則需要使用到這個結構體裡面的資料。 軟體代碼如下
common/bootm.c

int do_bootm_states(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[],            int states, bootm_headers_t *images, int boot_progress){    boot_os_fn *boot_fn;    ulong iflag = 0;    int ret = 0, need_boot_fn;    images->state |= states;       // 把states放到bootm_headers_t images內部    /*     * Work through the states and see how far we get. We stop on     * any error.     */        // 判斷states是否需要BOOTM_STATE_START動作,也就是bootm的準備動作,需要的話則調用bootm_start    if (states & BOOTM_STATE_START)        ret = bootm_start(cmdtp, flag, argc, argv);        // 判斷states是否需要BOOTM_STATE_FINDOS動作,也就是擷取kernel資訊,需要的話在調用bootm_find_os    if (!ret && (states & BOOTM_STATE_FINDOS))        ret = bootm_find_os(cmdtp, flag, argc, argv);    ·   // 判斷states是否需要BOOTM_STATE_FINDOTHER動作,也就是擷取ramdisk和fdt等其他鏡像的資訊,需要的話則調用bootm_find_other    if (!ret && (states & BOOTM_STATE_FINDOTHER)) {        ret = bootm_find_other(cmdtp, flag, argc, argv);        argc = 0;   /* consume the args */    }        /* 這裡要重點注意,前面的步驟都是在解析uImage鏡像並填充bootm_headers_t images */        /* 也就是說解析uImage的部分在此之前 */        /* 而後續則是使用bootm_headers_t images 裡面的內容來進行後續動作*/    /* Load the OS */        // 判斷states是否需要BOOTM_STATE_LOADOS動作,也就是載入作業系統的動作,需要的話則調用bootm_load_os    if (!ret && (states & BOOTM_STATE_LOADOS)) {        ulong load_end;        iflag = bootm_disable_interrupts();        ret = bootm_load_os(images, &load_end, 0);        if (ret == 0)            lmb_reserve(&images->lmb, images->os.load,                    (load_end - images->os.load));        else if (ret && ret != BOOTM_ERR_OVERLAP)            goto err;        else if (ret == BOOTM_ERR_OVERLAP)            ret = 0;#if defined(CONFIG_SILENT_CONSOLE) && !defined(CONFIG_SILENT_U_BOOT_ONLY)        if (images->os.os == IH_OS_LINUX)            fixup_silent_linux();#endif    }        // 是否需要重新導向ramdinsk,do_bootm流程的話是不需要的    /* Relocate the ramdisk */#ifdef CONFIG_SYS_BOOT_RAMDISK_HIGH    if (!ret && (states & BOOTM_STATE_RAMDISK)) {        ulong rd_len = images->rd_end - images->rd_start;        ret = boot_ramdisk_high(&images->lmb, images->rd_start,            rd_len, &images->initrd_start, &images->initrd_end);        if (!ret) {            setenv_hex("initrd_start", images->initrd_start);            setenv_hex("initrd_end", images->initrd_end);        }    }#endif        // 是否需要重新導向fdt,do_bootm流程的話是不需要的#if IMAGE_ENABLE_OF_LIBFDT && defined(CONFIG_LMB)    if (!ret && (states & BOOTM_STATE_FDT)) {        boot_fdt_add_mem_rsv_regions(&images->lmb, images->ft_addr);        ret = boot_relocate_fdt(&images->lmb, &images->ft_addr,                    &images->ft_len);    }#endif    /* From now on, we need the OS boot function */    if (ret)        return ret;        // 擷取對應作業系統的啟動函數,存放到boot_fn中    boot_fn = bootm_os_get_boot_func(images->os.os);    need_boot_fn = states & (BOOTM_STATE_OS_CMDLINE |            BOOTM_STATE_OS_BD_T | BOOTM_STATE_OS_PREP |            BOOTM_STATE_OS_FAKE_GO | BOOTM_STATE_OS_GO);    if (boot_fn == NULL && need_boot_fn) {        if (iflag)            enable_interrupts();        printf("ERROR: booting os '%s' (%d) is not supported\n",               genimg_get_os_name(images->os.os), images->os.os);        bootstage_error(BOOTSTAGE_ID_CHECK_BOOT_OS);        return 1;    }    /* Call various other states that are not generally used */    if (!ret && (states & BOOTM_STATE_OS_CMDLINE))        ret = boot_fn(BOOTM_STATE_OS_CMDLINE, argc, argv, images);    if (!ret && (states & BOOTM_STATE_OS_BD_T))        ret = boot_fn(BOOTM_STATE_OS_BD_T, argc, argv, images);        // 跳轉到作業系統前的準備動作,會直接調用啟動函數,但是標識是BOOTM_STATE_OS_PREP    if (!ret && (states & BOOTM_STATE_OS_PREP))        ret = boot_fn(BOOTM_STATE_OS_PREP, argc, argv, images);    /* Check for unsupported subcommand. */    if (ret) {        puts("subcommand not supported\n");        return ret;    }        // BOOTM_STATE_OS_GO標識,跳轉到作業系統中,並且不應該再返回了    /* Now run the OS! We hope this doesn't return */    if (!ret && (states & BOOTM_STATE_OS_GO))        ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_GO,                images, boot_fn);    /* Deal with any fallout */err:    if (iflag)        enable_interrupts();    if (ret == BOOTM_ERR_UNIMPLEMENTED)        bootstage_error(BOOTSTAGE_ID_DECOMP_UNIMPL);    else if (ret == BOOTM_ERR_RESET)        do_reset(cmdtp, flag, argc, argv);    return ret;}

主要用到如下依次幾個函數來實現: bootm_start bootm_find_os bootm_find_other bootm_load_os bootm_os_get_boot_func boot_fn(BOOTM_STATE_OS_PREP, argc, argv, images); boot_selected_os boot_selected_os(argc, argv, BOOTM_STATE_OS_GO,images, boot_fn); 4、bootm_start & bootm_find_os & bootm_find_other

主要負責解析環境變數、參數、uImage,來填充bootm_headers_t images這個資料結構。
最終目的是實現bootm_headers_t images中的這幾個成員

typedef struct bootm_headers {    image_info_t    os;     /* os image info */    ulong       ep;     /* entry point of OS */    ulong       rd_start, rd_end;/* ramdisk start/end */    char        *ft_addr;   /* flat dev tree address */    ulong       ft_len;     /* length of flat device tree */    ulong       initrd_start;    ulong       initrd_end;    ulong       cmdline_start;    ulong       cmdline_end;    bd_t        *kbd;    int     verify;     /* getenv("verify")[0] != 'n' */#ifdef CONFIG_LMB    struct lmb  lmb;        /* for memory mgmt */#endif}
bootm_start
實現verify和lmb bootm_find_os
實現os和ep。
也就是說,不管是Legacy-uImage還是FIT-uImage,最終解析出來要得到的都是這兩個成員。
放在《uboot啟動kernel篇(三)——解析uImage的kernel鏡像》中具體說明。 bootm_find_other
實現rd_start, rd_end,ft_addr和initrd_end。
也就是說,不管是Legacy-uImage還是FIT-uImage,最終解析出來要得到的都是這兩個成員。
放在《uboot啟動kernel篇(四)——解析uImage的fdt》和《uboot啟動kernel篇(五)——解析uImage的ramdisk》中具體說明。 5、bootm_load_os

簡單說明一下,在bootm_load_os中,會對kernel鏡像進行load到對應的位置上,並且如果kernel鏡像是被mkimage壓縮過的,那麼會先經過解壓之後再進行load。(這裡要注意,這裡的壓縮和Image壓縮成zImage並不是同一個,而是uboot在Image或者zImage的基礎上進行的壓縮。。。)

static int bootm_load_os(bootm_headers_t *images, unsigned long *load_end,             int boot_progress){    image_info_t os = images->os;    ulong load = os.load; // kernel要載入的地址    ulong blob_start = os.start;    ulong blob_end = os.end;    ulong image_start = os.image_start; // kernel實際存在的位置    ulong image_len = os.image_len; // kernel的長度    bool no_overlap;    void *load_buf, *image_buf;    int err;    load_buf = map_sysmem(load, 0);    image_buf = map_sysmem(os.image_start, image_len);// 調用bootm_decomp_image,對image_buf的鏡像進行解壓縮,並load到load_buf上    err = bootm_decomp_image(os.comp, load, os.image_start, os.type,                 load_buf, image_buf, image_len,                 CONFIG_SYS_BOOTM_LEN, load_end);

結果上述步驟之後,kernel鏡像就被load到對應位置上了。 6、bootm_os_get_boot_func

bootm_os_get_boot_func用於擷取到對應作業系統的啟動函數,被儲存到boot_fn 中。
如下:

int do_bootm_states(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[],            int states, bootm_headers_t *images, int boot_progress){...    boot_fn = bootm_os_get_boot_func(images->os.os);...}boot_os_fn *bootm_os_get_boot_func(int os){    return boot_os[os];// 根據作業系統類型獲得到對應的操作函數}static boot_os_fn *boot_os[] = {...#ifdef CONFIG_BOOTM_LINUX    [IH_OS_LINUX] = do_bootm_linux,#endif}

可以看出最終啟動linux的核心函數是do_bootm_linux。
另外幾個函數最終也是調用到boot_fn,對應linux也就是do_bootm_linux,所以這裡不在說明了。
下面繼續說明一下do_bootm_linux的流程 五、do_bootm_linux

arch/arm/lib/bootm.c

int do_bootm_linux(int flag, int argc, char * const argv[],           bootm_headers_t *images){    /* No need for those on ARM */    if (flag & BOOTM_STATE_OS_BD_T || flag & BOOTM_STATE_OS_CMDLINE)        return -1;        // 當flag為BOOTM_STATE_OS_PREP,則說明只需要做準備動作boot_prep_linux    if (flag & BOOTM_STATE_OS_PREP) {        boot_prep_linux(images);        return 0;    }        // 當flag為BOOTM_STATE_OS_GO ,則說明只需要做跳轉動作         if (flag & (BOOTM_STATE_OS_GO | BOOTM_STATE_OS_FAKE_GO)) {        boot_jump_linux(images, flag);        return 0;    }    boot_prep_linux(images); // 以全域變數bootm_headers_t images為參數傳遞給boot_prep_linux    boot_jump_linux(images, flag);// 以全域變數bootm_headers_t images為參數傳遞給boot_jump_linux    return 0;}

boot_prep_linux用於實現跳轉到linux前的準備動作。
而boot_jump_linux用於跳轉到linux中。
都是以全域變數bootm_headers_t images為參數,這樣就可以直接擷取到前面步驟中得到的kernel鏡像、ramdisk以及fdt的資訊了。 boot_prep_linux
首先要說明一下LMB的概念。LMB是指logical memory blocks,主要是用於表示記憶體的保留地區,主要有fdt的地區,ramdisk的地區等等。
boot_prep_linux主要的目的是修正LMB,並把LMB填入到fdt中。
實現如下:

static void boot_prep_linux(bootm_headers_t *images){    char *commandline = getenv("bootargs");    if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len) {#ifdef CONFIG_OF_LIBFDT        debug("using: FDT\n");        if (image_setup_linux(images)) {            printf("FDT creation failed! hanging...");            hang();        }#endif    }

這裡沒有深入學習image_setup_linux,等後續有需要的話再進行深入。 boot_jump_linux
以arm為例:
arch/arm/lib/bootm.c

static void boot_jump_linux(bootm_headers_t *images, int flag){    unsigned long machid = gd->bd->bi_arch_number; // 從bd中擷取machine-id,machine-id在uboot啟動流程的文章中有說明過了    char *s;    void (*kernel_entry)(int zero, int arch, uint params); // kernel入口函數,也就是kernel的入口地址,對應kernel的_start地址。    unsigned long r2;    int fake = (flag & BOOTM_STATE_OS_FAKE_GO); // 偽跳轉,並不真正地跳轉到kernel中    kernel_entry = (void (*)(int, int, uint))images->ep;          // 將kernel_entry設定為images中的ep(kernel的入口地址),後面直接執行kernel_entry也就跳轉到了kernel中了        // 這裡要注意這種跳轉的方法    debug("## Transferring control to Linux (at address %08lx)" \        "...\n", (ulong) kernel_entry);    bootstage_mark(BOOTSTAGE_ID_RUN_OS);    announce_and_cleanup(fake);        // 把images->ft_addr(fdt的地址)放在r2寄存器中    if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len)        r2 = (unsigned long)images->ft_addr;    else        r2 = gd->bd->bi_boot_params;    if (!fake) {            kernel_entry(0, machid, r2);        // 這裡通過調用kernel_entry,就跳轉到了images->ep中了,也就是跳轉到kernel中了,具體則是kernel的_start符號的地址。        // 參數0則傳入到r0寄存器中,參數machid傳入到r1寄存器中,把images->ft_addr(fdt的地址)放在r2寄存器中        // 滿足了kernel啟動的硬體要求    }}

到這裡,經過kernel_entry之後就跳轉到kernel環境中了。有興趣可以看一下kernel的啟動流程。

================================================================================
kernel啟動流程文章
[kernel 啟動流程] 前篇——vmlinux.lds分析
[kernel 啟動流程] (第一章)概述
[kernel 啟動流程] (第二章)第一階段之——設定SVC、關閉中斷
[kernel 啟動流程] (第三章)第一階段之——proc info的擷取
[kernel 啟動流程] (第四章)第一階段之——dtb的驗證
[kernel 啟動流程] (第五章)第一階段之——臨時核心頁表的建立
[kernel 啟動流程] (第六章)第一階段之——開啟MMU
[kernel 啟動流程] (第七章)第一階段之——跳轉到start_kernel

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.