bootm跳轉到kernel的流程


轉自 https://blog.csdn.net/ooonebook/article/details/53495021

 

一、bootm說明


bootm這個命令用於啟動一個操作系統映像。它會從映像文件的頭部取得一些信息,這些信息包括:映像文件的基於的cpu架構、其操作系統類型、映像的類型、壓縮方式、映像文件在內存中的加載地址、映像文件運行的入口地址、映像文件名等。 
緊接着bootm將映像加載到指定的地址,如果需要的話,還會解壓映像並傳遞必要有參數給內核,最后跳到入口地址進入內核。 
這里的描述參考(http://blog.chinaunix.net/uid-20799298-id-99666.html)

需要打開的宏

CONFIG_BOOTM_LINUX=y
CONFIG_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和ramdisk
bootm 0x20008000 0x21000000

(3) 加載kernel和fdt
bootm 0x20008000 - 0x22000000

(4) 加載kernel、ramdisk、fdt
bootm 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=4
argv[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的啟動流程。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM