一、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