分类: 嵌入式
2015-03-10 14:53:20
原文地址:Bootm的流程分析 作者:unix_disciple
一、在开始之前先说明一下bootm相关的东西。
1、首先说明一下,S
2、-a参数后是内核的运行地址,-e参数后是入口地址。
3、
1)如果我们没用mkimage对内核进行处理的话,那直接把内核下载到0x30008000再运行就行,内核会自解压运行(不过内核运行需要一个tag来传递参数,而这个tag建议是由bootloader提供的,在u-boot下默认是由bootm命令建立的)。
2)如果使用mkimage生成内核镜像文件的话,会在内核的前头加上了64byte的信息,供建立tag之用。bootm命令会首先判断bootm xxxx 这个指定的地址xxxx是否与-a指定的加载地址相同。
(1)如果不同的话会从这个地址开始提取出这个64byte的头部,对其进行分析,然后把去掉头部的内核复制到-a指定的load地址中去运行之
(2)如果相同的话那就让其原封不同的放在那,但-e指定的入口地址会推后64byte,以跳过这64byte的头部。
bootm 用于加载并启动 U-Boot 能辨识的操作系统映像,即 bootm 加载的映像必须是用mkimage工具打过包的映像,bootm 不能启动直接的内核映像,因为bootm必须从映像的头获取映像的一些信息,比如操作系统的类型,映像是否压缩,映像的加载地址和压缩地址等。更详细的映像头信息可以查看mkimage工具的说明。而bootm的详细用法可通过help bootm
获得。
=> help bootm
bootm [addr [arg ...]]
- boot application image stored in memory passing arguments 'arg ...'; when booting a Linux
kernel, 'arg' can be the address of an initrd image
Bootm 用于将内核映像加载到指定的地址,如果需要还要进行解压映像。然后根据操作系统和体系结构的不同给内核传递不同的内核参数,最后启动内核。
bootm 可以有两个参数,第一个参数为内核映像的地址,它可以是 RAM 地址或者 Flash 地址。第二个参数是可选参数,即initrd映像的地址,当采用Ramdisk 作为根文件系统时需要使用 bootm 的第二个参数。当需要加载 initrd 映像时,首先 U-Boot 把内核映像加载到指定地址,然后再把 Ramdisk 映像加载到指定地址,同时把 Ramdisk 映像的大小和地址告知内核。
Bootm命令后U-Boot会将控制权交给Kernel。
common/cmd_bootm.c是bootm的命令实现代码,下面结合U-Boob引导内核启动信息来分析bootm代码,下面是我的开发板的启动信息:
U-Boot 2009.11 (Jun 25 2010 - 08:28:06) DRAM: 64 MB Flash: 2 MB NAND: 64 MiB In: serial Out: serial Err: serial Net: dm9000 Hit any key to stop autoboot: 0 NAND read: device 0 offset 0x80000, size 0x500000 5242880 bytes read: OK ## Booting kernel from Legacy Image at 30008000 ... Image Name: mark Created: 2010-07-02 15:37:07 UTC Image Type: ARM Linux Kernel Image (uncompressed) Data Size: 1800624 Bytes = 1.7 MB Load Address: 30008000 Entry Point: 30008040 Verifying Checksum ... OK XIP Kernel Image ... OK OK Starting kernel ... Uncompressing Linux.................................................................................................................. done, booting the kernel. Linux version CPU: ARM920T [41129200] revision 0 (ARMv4T), cr=c0007177 CPU: VIVT data cache, VIVT instruction cache Machine: Study-S Memory policy: ECC disabled, Data cache writeback … |
cmd_bootm.c
先分析bootm命令在U-Boot中是如何添加的:
U_BOOT_CMD( bootm, CONFIG_SYS_MAXARGS, 1, do_bootm, "boot application image from memory", "[addr [arg ...]]\n - boot application image stored in memory\n" "\tpassing arguments 'arg ...'; when booting a Linux kernel,\n" "\t'arg' can be the address of an initrd image\n" #if defined(CONFIG_OF_LIBFDT) "\tWhen booting a Linux kernel which requires a flat device-tree\n" "\ta third argument is required which is the address of the\n" "\tdevice-tree blob. To boot that kernel without an initrd image,\n" "\tuse a '-' for the second argument. If you do not pass a third\n" "\ta bd_info struct will be passed instead\n" #endif #if defined(CONFIG_FIT) "\t\nFor the new multi component uImage format (FIT) addresses\n" "\tmust be extened to include component or configuration unit name:\n" "\taddr: "\taddr# "\tUse iminfo command to get the list of existing component\n" "\timages and configurations.\n" #endif "\nSub-commands to do part of the bootm sequence. The sub-commands " "must be\n" "issued in the order below (it's ok to not issue all sub-commands):\n" "\tstart [addr [arg ...]]\n" "\tloados - load OS image\n" #if defined(CONFIG_PPC) || defined(CONFIG_M68K) || defined(CONFIG_SPARC) "\tramdisk - relocate initrd, set env initrd_start/initrd_end\n" #endif #if defined(CONFIG_OF_LIBFDT) "\tfdt - relocate flat device tree\n" #endif "\tcmdline - OS specific command line processing/setup\n" "\tbdt - OS specific bd_t processing\n" "\tprep - OS specific prep before relocation or go\n" "\tgo - start OS" ); |
宏U_BOOT_CMD用于定义U-Boot命令,它的定义如下:
#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \ cmd_btl_t __u_boot_cmd ##name Struct_Section={#name,maxargs,rep,cmd,usage,help| #define Struct Section __attribute__ ((unused,section (".u_boot_cmd"))) 宏U_BOOT_CMD展开后如下所示: cmd_tlb_t __u_boot_cmd_bootm __a 见—嵌入式liinux应用开发完全手册 |
do_bootm函数分析
static boot_os_fn *boot_os[] = { #ifdef CONFIG_BOOTM_LINUX [IH_OS_LINUX] = do_bootm_linux, //由于我使用的是linux内核,编译时将会选择该函数 #endif int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]) //这里的argc和argv就是bootm命令行参数 { boot_os_fn *boot_fn; //定义跳转到操作系统执行的指针 … if (bootm_start(cmdtp, flag, argc, argv)) //调用bootm_start函数 return 1; … iflag = disable_interrupts(); //禁止中断 … ret = bootm_load_os(images.os, &load_end, 1); //调用bootm_load_os函数 … boot_fn = boot_os[images.os.os]; //根据OS类型去选择启动操作系统的函数 … boot_fn(0, argc, argv, &images); //执行do_bootm_linux函数 … } |
do_bootm函数中调用的几个重要的函数是bootm_start、bootm_load_os、do_bootm_linux,下面一一分析。
bootm_start
static bootm_headers_t images; /* pointers to os/initrd/fdt images */ static int bootm_start(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]) { void *os_hdr; … /* get kernel image header, start address and length */ os_hdr = boot_get_kernel (cmdtp, flag, argc, argv, &images, &images.os.image_start, &images.os.image_len); …. /* get image parameters */ switch (genimg_get_format (os_hdr)) { //获取image头格式类型 case IMAGE_FORMAT_LEGACY: //老的image头格式 images.os.type = image_get_type (os_hdr); //获取image的类型 images.os.comp = image_get_comp (os_hdr); //获取image的压缩类型 images.os.os = image_get_os (os_hdr); //获取image的OS类型 images.os.end = image_get_image_end (os_hdr); //获取image开始地址 images.os.load = image_get_load (os_hdr); //获取image结束地址 break; … /* find kernel entry point */ if (images.legacy_hdr_valid) { images.ep = image_get_ep (&images.legacy_hdr_os_copy); //获取image入口指针 … } ulong load_addr = CONFIG_SYS_LOAD_ADDR; /* Default Load Address */ //寻找内核映像、校验它的完整性和定位内核数据位置 static void *boot_get_kernel (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[], bootm_headers_t *images, ulong *os_data, ulong *os_len) { image_header_t *hdr; … /* find out kernel image address */ if (argc < 2) { //bootm命令后面不带参数时 img_addr = load_addr; //使用默认加载地址 debug ("* kernel: default image load address = 0x%08lx\n", load_addr); … switch (genimg_get_format ((void *)img_addr)) { //获取image头类型 case IMAGE_FORMAT_LEGACY: //早期的image头类型 printf ("## Booting kernel from Legacy Image at %08lx ...\n", img_addr); //该打印信息出现在U-Boot启动信息里 hdr = image_get_kernel (img_addr, images->verify); //获取image头 … } //校验早期格式内核映像 static image_header_t *image_get_kernel (ulong img_addr, int verify) { image_header_t *hdr = (image_header_t *)img_addr; if (!image_check_magic(hdr)) { //校验MAGIC number puts ("Bad Magic Number\n"); show_boot_progress (-1); return NULL; } show_boot_progress (2); if (!image_check_hcrc (hdr)) { //image头校验 puts ("Bad Header Checksum\n"); show_boot_progress (-2); return NULL; } show_boot_progress (3); image_print_contents (hdr); //该函数内将打印imag头信息“ Image Name: mark Created: 2010-07-02 15:37:07 UTC Image Type: ARM Linux Kernel Image (uncompressed) Data Size: 1800624 Bytes = 1.7 MB Load Address: 30008000 Entry Point: 30008040 ” if (verify) { puts (" Verifying Checksum ... "); //出现在U-Boot启动信息中 if (!image_check_dcrc (hdr)) { //校验image数据 printf ("Bad Data CRC\n"); show_boot_progress (-3); return NULL; } puts ("OK\n"); //出现在U-Boot启动信息中 } show_boot_progress (4); if (!image_check_target_arch (hdr)) { //ARM、i386、MIPS等架构检查 printf ("Unsupported Architecture 0x%x\n", image_get_arch (hdr)); show_boot_progress (-4); return NULL; } return hdr; } //打印image头信息 void image_print_contents (const void *ptr) { const image_header_t *hdr = (const image_header_t *)ptr; const char *p;
#ifdef USE_HOSTCC p = ""; #else p = " "; //打印空格 #endif //打印image名字 printf ("%sImage Name: %.*s\n", p, IH_NMLEN, image_get_name (hdr)); #if defined(CONFIG_TIMESTAMP) || defined(CONFIG_CMD_DATE) || defined(USE_HOSTCC) printf ("%sCreated: ", p); //打印image创建日期 genimg_print_time ((time_t)image_get_time (hdr)); #endif printf ("%sImage Type: ", p); //打印image类型 image_print_type (hdr); printf ("%sData Size: ", p); //打印image大小 genimg_print_size (image_get_data_size (hdr)); printf ("%sLoad Address: %08x\n", p, image_get_load (hdr)); //打印image加载地址 printf ("%sEntry Point: %08x\n", p, image_get_ep (hdr)); //打印image入口指针 … } |
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 */ image_header_t legacy_hdr_os_copy; /* header copy */ ulong legacy_hdr_valid; … #ifndef USE_HOSTCC image_info_t os; /* os image info */ ulong ep; /* entry point of OS */ ulong rd_start, rd_end;/* ramdisk start/end */ … ulong ft_len; /* length of flat device tree */ ulong initrd_start; ulong initrd_end; ulong cmdline_start; ulong cmdline_end; bd_t *kbd; #endif int verify; /* getenv("verify")[0] != 'n' */ #define BOOTM_STATE_START (0x00000001) #define BOOTM_STATE_LOADOS (0x00000002) #define BOOTM_STATE_RAMDISK (0x00000004) #define BOOTM_STATE_FDT (0x00000008) #define BOOTM_STATE_OS_CMDLINE (0x00000010) #define BOOTM_STATE_OS_BD_T (0x00000020) #define BOOTM_STATE_OS_PREP (0x00000040) #define BOOTM_STATE_OS_GO (0x00000080) int state; #ifndef USE_HOSTCC struct lmb lmb; /* for memory mgmt */ #endif } bootm_headers_t; |
bootm_load_os
static int bootm_load_os(image_info_t os, ulong *load_end, int boot_progress) { uint8_t comp = os.comp; //获取image压缩类型 ulong load = os.load; ulong blob_start = os.start; //os.start为bootm后面的地址,如果bootm后面没有参数,则默认为u-boot中加载地址 ulong blob_end = os.end; ulong image_start = os.image_start; //os.image_start为os.start+u-boot头,即内核所处内存的起始地址 ulong image_len = os.image_len; //os.image_len为内核长度 uint unc_len = CONFIG_SYS_BOOTM_LEN; … switch (comp) { case IH_COMP_NONE: //image未压缩 if (load == blob_start) { printf (" XIP %s ... ", type_name); //如果U-Boot头中的加载地址与bootm后面的地址相同,则打印包含“XIP”的字符串,分析u-boot代码发现load就是u-boot头中的加载地址,blob_start是bootm后面的地址。这种情况对应的是使用mkimage工具生成包含u-boot头时加载地址为0x30008000,入口指针为0x30008040,tftp 30008000。此种情况下load并不等于ep,即u-boot头中的加载地址和入口指针不相同。 } else { printf (" Loading %s ... ", type_name); //如果U-Boot头中的加载地址与bootm后面的地址不相同。此种情况对应的是使用mkimage工具生成的u-boot头时加载地址和入口指针都为0x30008000,tftp。此种情况下load等于ep,即u-boot头中的加载地址等于入口指针。 if (load != image_start) { //因为u-boot头中的加载地址等于入口指针,这里等于是判断u-boot头中的加载地址或入口指针是否等于内存中内核映像所处的位置,因为如果入口指针等于内核所处的内存地址,那么就不用下面的将内存中内核所处的位置的值复制到加载地址或入口指针处,然后从入口指针处执行内核程序。 memmove_wd ((void *)load, (void *)image_start, image_len, CHUNKSZ); } } *load_end = load + image_len; puts("OK\n"); break; case IH_COMP_GZIP: //image采用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: //image采用bzip2压缩 printf (" Uncompressing %s ... ", type_name); /* * If we've got less than 4 MB of malloc() space, * use slower decompression algorithm which requires * at most 2300 KB of memory. */ 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: //image采用LZMA压缩 printf (" Uncompressing %s ... ", type_name); int ret = lzmaBuffToBuffDecompress( //解压压缩过的映像 (unsigned char *)load, &unc_len, (unsigned char *)image_start, image_len); if (ret != SZ_OK) { printf ("LZMA: uncompress or overwrite error %d " "- must RESET board to recover\n", ret); show_boot_progress (-6); return BOOTM_ERR_RESET; } *load_end = load + unc_len; break; #endif /* CONFIG_LZMA */ default: printf ("Unimplemented compression type %d\n", comp); return BOOTM_ERR_UNIMPLEMENTED; } puts ("OK\n"); debug (" kernel loaded at 0x%08lx, end = 0x%08lx\n", load, *load_end); if (boot_progress) show_boot_progress (7); if ((load < blob_end) && (*load_end > blob_start)) { debug ("images.os.start = 0x%lX, images.os.end = 0x%lx\n", blob_start, blob_end); debug ("images.os.load = 0x%lx, load_end = 0x%lx\n", load, *load_end); return BOOTM_ERR_OVERLAP; } return 0; } } |
关于bootm_load_os函数,个人的理解是之所以叫它是load os,是因为一些image的起始地址和加载地址相同的话就直接运行,但是一些image的起始地址和加载地址不相同的话,要把image搬运到加载地址开始处,然后执行。
do_bootm_linux
int do_bootm_linux(int flag, int argc, char *argv[], bootm_headers_t *images) { bd_t *bd = gd->bd; char *s; int machid = bd->bi_arch_number; void (*theKernel)(int zero, int arch, uint params); #ifdef CONFIG_CMDLINE_TAG char *commandline = getenv ("bootargs"); //启动参数标记,如果在include/configs/mini2440.h中定义CONFIG_CMDLINE_TAG宏,则boottargs将会传递给内核 #endif if ((flag != 0) && (flag != BOOTM_STATE_OS_GO)) return 1; theKernel = (void (*)(int, int, uint))images->ep; // image入口地址赋值给theKernel指针 s = getenv ("machid"); if (s) { machid = simple_strtoul (s, NULL, 16); //获取机器ID printf ("Using machid 0x%x from environment\n", machid); } show_boot_progress (15); debug ("## Transferring control to Linux (at address %08lx) ...\n", (ulong) theKernel); //以下宏可以在include/configs/mini2440.h中定义,然后相应的参数便可传递给内核 #if defined (CONFIG_SETUP_MEMORY_TAGS) || \ defined (CONFIG_CMDLINE_TAG) || \ defined (CONFIG_INITRD_TAG) || \ defined (CONFIG_SERIAL_TAG) || \ defined (CONFIG_REVISION_TAG) || \ defined (CONFIG_LCD) || \ defined (CONFIG_VFD) setup_start_tag (bd); #ifdef CONFIG_SERIAL_TAG setup_serial_tag (¶ms); #endif #ifdef CONFIG_REVISION_TAG setup_revision_tag (¶ms); #endif #ifdef CONFIG_SETUP_MEMORY_TAGS setup_memory_tags (bd); #endif #ifdef CONFIG_CMDLINE_TAG setup_commandline_tag (bd, commandline); #endif #ifdef CONFIG_INITRD_TAG if (images->rd_start && images->rd_end) setup_initrd_tag (bd, images->rd_start, images->rd_end); #endif #if defined (CONFIG_VFD) || defined (CONFIG_LCD) setup_videolfb_tag ((gd_t *) gd); #endif setup_end_tag (bd); #endif /* we assume that the kernel is in place */ printf ("\nStarting kernel ...\n\n"); //在启动内核之前的打印语句,将会出现在U-BOOT启动信息中。 #ifdef CONFIG_USB_DEVICE { extern void udc_disconnect (void); udc_disconnect (); } #endif cleanup_before_linux (); theKernel (0, machid, bd->bi_boot_params); //执行内核代码 /* does not return */ return 1; } |