在上一篇中分析到u-Boot啟動Linux內(nèi)核的函數(shù)do_bootm_linux,這一篇則著重分析,U-boot是如果一步一步啟動內(nèi)核的。
我們可以看到在,start_armboot()函數(shù)的最后,在一個無限循環(huán)中調(diào)用了函數(shù)main_loop(),該函數(shù)在common/main.c文件中被定義,我們可以看到下面的一段代碼:
#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0) s = getenv ("bootdelay"); //得到環(huán)境變量中bootdelay bootdelay = s ? (int)simple_strtol(s, NULL, 10) : CONFIG_BOOTDELAY;
debug ("### main_loop entered: bootdelay=%d\n\n", bootdelay);
| 如果定義了CONFIG_BOOTDELAY,則在沒有CONFIG_BOOTDELAY秒中,串口上沒有輸入,則會進行自動的引導Linux內(nèi)核。也就是執(zhí)行bootcmd命令。
#ifdef CONFIG_BOOTCOUNT_LIMIT //啟動次數(shù)的限制功能,如果到達一定次數(shù),將不能啟動u-boot. if (bootlimit && (bootcount > bootlimit)) {//檢查是否超出啟動次數(shù)限制 printf ("Warning: Bootlimit (%u) exceeded. Using altbootcmd.\n", (unsigned)bootlimit); s = getenv ("altbootcmd");//啟動延時 } else #endif /* CONFIG_BOOTCOUNT_LIMIT */ s = getenv ("bootcmd");// 獲得啟動參數(shù)
debug ("### main_loop: bootcmd=\"%s\"\n", s ? s : "<UNDEFINED>"); // 這里如果bootdelay大于0,并且中間沒有被中斷的話,執(zhí)行命令行參數(shù) if (bootdelay >= 0 && s && !abortboot (bootdelay)) { # ifdef CONFIG_AUTOBOOT_KEYED int prev = disable_ctrlc(1); /* disable Control C checking */ # endif # ifndef CONFIG_SYS_HUSH_PARSER run_command (s, 0); //運行啟動的命令行,例如 可以使用tftp命令 # else parse_string_outer(s, FLAG_PARSE_SEMICOLON | FLAG_EXIT_FROM_LOOP); # endif
| 到這里我們就可以看到是怎么調(diào)用設(shè)置的命令行參數(shù)的,在這里還要使用到bootm命令,先來看看bootm命令的實現(xiàn),在common/cmd_bootm.c
#define CONFIG_BOOTM_LINUX 1 #define CONFIG_BOOTM_NETBSD 1 #define CONFIG_BOOTM_RTEMS 1
#ifdef CONFIG_BOOTM_LINUX extern boot_os_fn do_bootm_linux; #endif #ifdef CONFIG_BOOTM_NETBSD static boot_os_fn do_bootm_netbsd; #endif
| 可以看出如果定義了CONFIG_BOOTM_LINUX這個宏的話,就會使用外部文件定義的do_bootm_linux函數(shù),在arm體系結(jié)構(gòu)中,就是在lib_arm/bootm.c文件中,可以從lib_arm/bootm.c文件中的59行看到do_bootm_linux()的定義。其中第64行聲明了這樣一個函數(shù)指針theKernel
void (*theKernel)(int zero, int arch, uint params);
| 看看它的名字和參數(shù)的命名我們也可以猜到這個其實就是內(nèi)核的入口函數(shù)的指針了。幾個參數(shù)的命名也說明了下文提到的ARM Linux內(nèi)核啟動要求的第一條,因為根據(jù)ACPS(ARM/Thumb Procedure Call Standard)的規(guī)定,這三個參數(shù)就是依次使用r0,r1和r2來傳遞的。接下來第73行就是給這個函數(shù)指針賦值:
theKernel = (void (*)(int, int, uint))images->ep;
| 可以看到theKernel被賦值為images->ep,這個image指使用tools/mkimage工具程序制作uImage時加在linux.bin.gz前面的一個頭部,而ep結(jié)構(gòu)體成員保存的就是使用mkimage時指定的-e參數(shù)的值,即內(nèi)核的入口點(Entry Point)。知道了images->ep的意義之后,給theKernel賦這個值也就是理所當然的了。 image是bootm_headers結(jié)構(gòu)體的指針,可以在inlcude/image.h文件中看到這個結(jié)構(gòu)體的定義如下:
typedef struct bootm_headers { ............................
int fit_noffset_fdt;/* FDT blob subimage node offset */ #endif
#ifndef USE_HOSTCC image_info_t os; /* os image info */ ulong ep; /* entry point of OS */
ulong rd_start, rd_end;/* ramdisk start/end */ ...............
}
| 最后是對內(nèi)核入口函數(shù)的調(diào)用,發(fā)生在第128行:
theKernel (0, machid, bd->bi_boot_params);
| 調(diào)用的時候?qū)?shù)進行賦值,r0=0,r1=bd->bi_arch_number,r2=bd-> bi_boot_params,一個都不少。至此U-Boot的使命完成,開始進入ARM Linux的世界。 要知道哪個地址是啟動內(nèi)核,哪個地址啟動文件系統(tǒng),要分析common/cmd_bootm.c中的函數(shù) do_bootm,因為引導kernel就是bootm這條命令的工作,do_bootm是命令bootm的執(zhí)行函數(shù)現(xiàn)在我們來分析一下common/cmd_bootm.c中的函數(shù)do_bootm,這是bootm命令的處理函數(shù).do_bootm()函數(shù)中的很多功能都是分成了函數(shù)的形式,而在以前的版本中沒有這么有結(jié)構(gòu)層次,這里我們也只是分析對引導Linux內(nèi)核有作用的部分,因為這是一個在common文件夾下的文件,也就意味著,在引導別的操作系統(tǒng)時也會用到這個函數(shù),而不單單是Linux操作系統(tǒng).
int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]) { ulong iflag; ulong load_end = 0; int ret; boot_os_fn *boot_fn; #ifndef CONFIG_RELOC_FIXUP_WORKS static int relocated = 0; /* relocate boot function table */ if (!relocated) { int i; for (i = 0; i < ARRAY_SIZE(boot_os); i++) if (boot_os[i] != NULL) boot_os[i] += gd->reloc_off; relocated = 1; } #endif /* determine if we have a sub command */ if (argc > 1) { char *endp;
simple_strtoul(argv[1], &endp, 16); if ((*endp != 0) && (*endp != ':') && (*endp != '#')) return do_bootm_subcommand(cmdtp, flag, argc, argv); }
if (bootm_start(cmdtp, flag, argc, argv)) //提取mkimage生成的文件頭部,放到bootm_headers_t結(jié)構(gòu)體中 return 1;
iflag = disable_interrupts();
#if defined(CONFIG_CMD_USB) usb_stop(); #endif
#ifdef CONFIG_AMIGAONEG3SE /* * We've possible left the caches enabled during * bios emulation, so turn them off again */ icache_disable(); dcache_disable(); #endif
ret = bootm_load_os(images.os, &load_end, 1); //加載操作系統(tǒng)的關(guān)鍵部分 確定使用的地址 if (ret < 0) { //出錯處理 if (ret == BOOTM_ERR_RESET) do_reset (cmdtp, flag, argc, argv); if (ret == BOOTM_ERR_OVERLAP) { if (images.legacy_hdr_valid) { if (image_get_type (&images.legacy_hdr_os_copy) == IH_TYPE_MULTI) puts ("WARNING: legacy format multi component " "image overwritten\n"); } else { puts ("ERROR: new format image overwritten - " "must RESET the board to recover\n"); show_boot_progress (-113); do_reset (cmdtp, flag, argc, argv); } } if (ret == BOOTM_ERR_UNIMPLEMENTED) { if (iflag) enable_interrupts(); show_boot_progress (-7); return 1; } } lmb_reserve(&images.lmb, images.os.load, (load_end - images.os.load));
if (images.os.type == IH_TYPE_STANDALONE) {//獨立的應(yīng)用程序 if (iflag) enable_interrupts(); /* This may return when 'autostart' is 'no' */ bootm_start_standalone(iflag, argc, argv); return 0; } show_boot_progress (8);
#ifdef CONFIG_SILENT_CONSOLE //這里處理Linux操作系統(tǒng) if (images.os.os == IH_OS_LINUX) fixup_silent_linux(); //該函數(shù)中處理bootarg參數(shù) #endif boot_fn = boot_os[images.os.os]; if (boot_fn == NULL) { if (iflag) enable_interrupts(); printf ("ERROR: booting os '%s' (%d) is not supported\n", genimg_get_os_name(images.os.os), images.os.os); show_boot_progress (-8); return 1; }
arch_preboot_os(); /*下面的函數(shù),繼續(xù)引導內(nèi)核的鏡像,復制image header 到全局變量header; 檢查header的魔數(shù),檢查數(shù),header和image中的這兩個。確定image的體系結(jié)構(gòu)和類型(KERNEL or MULTI),關(guān)閉中斷,加載image到header中的加載地址*/ boot_fn(0, argc, argv, &images); //調(diào)用do_bootm_linux()函數(shù) show_boot_progress (-9); #ifdef DEBUG puts ("\n## Control returned to monitor - resetting...\n"); #endif do_reset (cmdtp, flag, argc, argv);
return 1; }
| 下面我們看一下bootm_load_os()函數(shù)
static int bootm_load_os(image_info_t os, ulong *load_end, int boot_progress) { uint8_t comp = os.comp; ulong load = os.load; ulong blob_start = os.start; ulong blob_end = os.end; ulong image_start = os.image_start; ulong image_len = os.image_len; uint unc_len = CONFIG_SYS_BOOTM_LEN;
const char *type_name = genimg_get_type_name (os.type);
switch (comp) { //判斷image的壓縮類型 case IH_COMP_NONE: if (load == blob_start) { printf (" XIP %s ... ", type_name); } else { printf (" Loading %s ... ", type_name); //如果在Image head中加載的地址和bootm命令參數(shù)2指定的地址相同,則不需要復制,直接執(zhí)行 if (load != image_start) { memmove_wd ((void *)load, (void *)image_start, image_len, CHUNKSZ); } } *load_end = load + image_len; puts("OK\n"); break; case IH_COMP_GZIP: printf (" Uncompressing %s ... ", type_name); if (gunzip ((void *)load, unc_len, (uchar *)image_start, &image_len) != 0) { puts ("GUNZIP: uncompress, out-of-mem or overwrite error " "- must RESET board to recover\n"); if (boot_progress) show_boot_progress (-6); return BOOTM_ERR_RESET; } *load_end = load + image_len; break; #ifdef CONFIG_BZIP2 case IH_COMP_BZIP2: //判斷是什么類型的壓縮類型 printf (" Uncompressing %s ... ", type_name); int i = BZ2_bzBuffToBuffDecompress ((char*)load, &unc_len, (char *)image_start, image_len, CONFIG_SYS_MALLOC_LEN < (4096 * 1024), 0); if (i != BZ_OK) { printf ("BUNZIP2: uncompress or overwrite error %d " "- must RESET board to recover\n", i); if (boot_progress) show_boot_progress (-6); return BOOTM_ERR_RESET; } *load_end = load + unc_len; break; #endif /* CONFIG_BZIP2 */ #ifdef CONFIG_LZMA case IH_COMP_LZMA: printf (" Uncompressing %s ... ", type_name);
.................... return 0; }
| 如果image header中指示的加載地址和bootm命令中參數(shù)2指定的地址不相同,則表示要從image header中指示的加載地址處把image data copy到bootm命令中參數(shù)2指定的地址處,然后再執(zhí)行。
bootm命令是用來引導經(jīng)過u-boot的工具mkimage打包后的kernel image的。
mkimage的用法 uboot源代碼的tools/目錄下有mkimage工具,這個工具可以用來制作不壓縮或者壓縮的多種可啟動映象文件。 mkimage在制作映象文件的時候,是在原來的可執(zhí)行映象文件的前面加上一個0x40字節(jié)的頭,記錄參數(shù)所指定的信息,這樣uboot才能識別這個映象是針對哪個CPU體系結(jié)構(gòu)的,哪個OS的,哪種類型,加載內(nèi)存中的哪個位置, 入口點在內(nèi)存的那個位置以及映象名是什么?到這里整個U-Boot是如何啟動Linux內(nèi)核的,基本上也就清楚了,特別是如何向Linux內(nèi)核傳送的參數(shù)。
PS:下面是“ARM Linux Kernel Boot Requirements”,這篇文章中介紹的,引導Linux內(nèi)核啟動的必須要滿足的幾個條件:
* CPU register settings //這里也就是我們的theKernel中的作用 o r0 = 0. o r1 = machine type number. o r2 = physical address of tagged list in system RAM. * CPU mode o All forms of interrupts must be disabled (IRQs and FIQs.) o The CPU must be in SVC mode. (A special exception exists for Angel.) * Caches, MMUs o The MMU must be off. o Instruction cache may be on or off. o Data cache must be off and must not contain any stale data. * Devices o DMA to/from devices should be quiesced. * The boot loader is expected to call the kernel image by jumping directly to the first instruction of the kernel image.
|
|