进入bootm命令分析之前,先来看看README里面的几段话,简单翻译之
制作Linux映像 ============
使用uboot时,内核通常生成的文件"zImage"或"bzImage"是没用的。较新一些的内核原码会 生成"uImage", 这个可以为uboot使用。 "uImage"全用了一个工具"tools/mkimage"来封装压缩后的映像文件,在其头部添加一 些信息以及crc32校验等。
我们需要做如下几件事情:
* 制作一个标准的内核映像文件"vmlinux"(这个是ELF格式的) * 将其转换为二进制映像 arm-linux-objcopy -O binarty \ -R .note -R .comment \ -S vmlinux linux.bin * 压缩这个二进制映像 gzip -9 linux.bin * 封装这个已压缩的映像 mkimage -A arm -O linux -T kernel -C gzip \ -a 0 -e 0 -n "Linux Kernel Image" \ -d linux.bin.gz uImage
For i.MX51 BBG Board:
$ ~/myandroid/bootable/bootloader/uboot-imx/tools/mkimage -A arm -O
linux -T kernel -C none -a 0x90008000 -e 0x90008000 -n "Android Linux
Kernel" -d ./zImage ./uImage
During boot, when uboot try to
load above "uImage", it will know to load it (without image head added
by mkimage tool) into 0x90008000 (specified by "-a"), and then jump to
0x90008000 (specified by "-e") to execute it. "-C none" means no
compression when generating "uImage". This is because the original
zImage is already a compressed one.
mkimage 也可以用来制作ramdisk映像。 mkimage 在映像的头部添加了64字节的信息,用来指明映像文件用于的体系结构,操作系统, 映像类型, 压缩方式,入口地址,时间戳,crc32校验等。
mkimage 一般用于两种情况: 为映像添加头信息 和 列出文件的头信息 tools/mkimage -l image -l ==> 列出映像的头信息
添加头信息时 tools/mkimage -A arch -O os -T type -C comp -a addr -e ep \ -n name -d data_file image -A ==> set architecture to 'arch' 体系结构 -O ==> set operating system to 'os' 操作系统 -T ==> set image type to 'type' 映像类型 -C ==> set compression type 'comp' 压缩方式 -a ==> set load address to 'addr' (hex) 加载地址 -e ==> set entry point to 'ep' (hex) 入口地址 -n ==> set image name to 'name' 映像文件名 -d ==> use image data from 'datafile' 制作映像的时间
---------------------------------------------- bootm ===== 这个命令用于启动一个操作系统映像。它会从映像文件的头部取得一些信息,这些信息包括: 映像文件的基于的cpu架构、其操作系统类型、映像的类型、压缩方式、映像文件在内存中的加载地址、 映像文件运行的入口地址、映像文件名等。 紧接着bootm将映像加载到指定的地址,如果需要的话,还会解压映像并传递必要有参数给内核,最后 跳到入口地址进入内核。
bootm的第一个参数是映像存储的地址。
例如Linux,可以附带一个参数。此参数会被认为是一个initrd的起始地址,此时bootm命令有三个 步骤:首先解压Linux内核映像并将其复制到ram中,然后将ramdisk映像加载到ram中,最后将控制 权交给内核,并向内核传递ramdisk的位置和大小等信息.
单单用来启动Linux内核,而没有initrd时,可用如下命令: => bootm ${kernel_addr}
如果还有initrd,则可使用下面的命令: => bootm ${kernel_addr} ${ramdisk_addr}
使用时确保地址参数正确。
当待启动的映像文件已经被加载于RAM时(例如用tftp下载到sdram上),需要对内存部局更加小心。 需要确保映像文件(可能是已被压缩的映像文件)加载的地址不会与解压后的内核位置重叠。例如,如果 将一个ramdisk映像加载于内存的低地址,则在Linux内核加载时可能会覆盖它。这将导致未知的系统 崩溃。
上面几段是README里的内容,介绍了内核映像的制作及bootm命令。下面来看具体代码
do_bootm
static bootm_headers_t images; 这个静态全局变量就是头信息结构了。注意这里是 bootm_headers_t而不是image_header_t.
typedef struct bootm_headers { image_header_t *legacy_hdr_os; /* image header pointer */ image_header_t legacy_hdr_os_copy; /* header copy */ ulong legacy_hdr_valid; int verify; /* getenv("verify")[0] != 'n' */ struct lmb *lmb; /* for memory mgmt */ } bootm_headers_t;
mem_start = getenv_bootm_low(); //获得内存sdram的起始地址 1. 取环境变量中的boom_low 2. 没有boom_low,取则配置时的CFG_SDRAM_BASE 3. 没有CFG_SDRAM_BASE则取 gd->bd->bi_dram[0].start 4. 若还没有,则取 0
mem_size = getenv_bootm_size(); //获得内存sdram的大小 1. 取环境变量中的bootm_size 2. 若没有bootm_size,则取 gd->bd->bi_dram[0].size
lmb_add(&lmb, (phys_addr_t)mem_start, mem_size); //
os_hdr = boot_get_kernel (cmdtp, flag, argc, argv, &images, &os_data, &os_len);
/****************************************************************************** ******* 进入 boot_get_kernel ******************************************** *******************************************************************************/
if (argc < 2) { img_addr = load_addr; debug ("* kernel: default image load address = 0x%08lx\n", load_addr); } else { img_addr = simple_strtoul(argv[1], NULL, 16); debug ("* kernel: cmdline image address = 0x%08lx\n", img_addr); } //argc < 2, 即直接输入bootm命令,此时映像存储地址为load_addr; 定义处有 //ulong load_addr = CFG_LOAD_ADDR; 即初始的映像存储地址,但在u-boot运行期间,load_addr的 //值会被环境变量所更改。 //若bootm后面还带了一参数,如bootm 30000,则映像的存储地址为0x30000 //接下来要从nand中读出kernel image时,就从地址0x3000开始读
img_addr = genimg_get_image (img_addr); // 从存储介质中将image读出,img_addr是前面获得的映像存储地址。这个函数体内被CONFIG_HAS_DATAFLASH // 包围,是指kernel存于atmel的数据flash中.由于我这里只有nand,所以这个函数相当于空。 // 在运行bootm命令之前,一般使用tftp or nand read.jffs2 将kernel image 先读到sdram中,所以此时 // kernel image 已经在sdram中了。
genimg_get_format ((void *)img_addr)) //判断imgae的格式,通过判断头信息中的幻数来确断格式,格式有三种 // IMAGE_FORMAT_LEGACY // IMAGE_FORMAT_FIT 这里不支持,在预定义里就没有定义 CONFIG_FIT这项 // IMAGE_FORMAT_INVALID 无效的格式
hdr = image_get_kernel (img_addr, images->verify); // 检查头信息,函数内做了以下几件事 // image_check_magic 再次检查幻数 // image_check_hcrc 头信息校验 // image_print_contents (hdr); 打印头信息 // image_check_dcrc 数据校验,这里是校验kernel实际的数据 // image_check_target_arch 检查运行的cpu架构
switch (image_get_type (hdr)) { case IH_TYPE_KERNEL: *os_data = image_get_data (hdr); *os_len = image_get_data_size (hdr); break; case IH_TYPE_MULTI: image_multi_getimg (hdr, 0, os_data, os_len); break; default: printf ("Wrong Image Type for %s command\n", cmdtp->name); show_boot_progress (-5); return NULL; } // 这里先检查映像的类型,有kernel, ramdisk, multi, firmware, script等, // 根据不同的类型来获得真正的image data(即不包括头信息的imgae)的起始地址(os_data) 和大小(os_len)
memmove (&images->legacy_hdr_os_copy, hdr, sizeof(image_header_t));
// 再次来看下images结构
typedef struct bootm_headers { image_header_t *legacy_hdr_os; /* image header pointer */ image_header_t legacy_hdr_os_copy; /* header copy */ ulong legacy_hdr_valid; int verify; /* getenv("verify")[0] != 'n' */ struct lmb *lmb; /* for memory mgmt */ } bootm_headers_t; // 可知上面的memmove实际上是将头信息都保存到images的 legacy_hdr_os_copy成员中
images->legacy_hdr_os = hdr; // 保存kernel image头信息的地址
images->legacy_hdr_valid = 1; // 映像文件是有效的
return (void *)img_addr; //最后返回映像文件的地址
/****************************************************************************** ******* 退出 boot_get_kernel ******************************************** *******************************************************************************/ //小结:从上面的分析可以看出 boot_get_kernel 的作用就是找到kernel image,并通过头信息的检查 //来判断其是否有效。最后返回kernel image的地址给do_bootm。
// 继续do_bootm中
switch (genimg_get_format (os_hdr)) { //boot_get_kernel中做过一次,检查幻数,判断image case IMAGE_FORMAT_LEGACY: type = image_get_type (os_hdr); // 映像类型 comp = image_get_comp (os_hdr); // 压缩方式 os = image_get_os (os_hdr); // 操作系统类型
image_end = image_get_image_end (os_hdr); // 结束地址 load_start = image_get_load (os_hdr); // 头信息中定义的加载地址 break;
image_start = (ulong)os_hdr; // 映像起始地址,包括头信息 load_end = 0; type_name = genimg_get_type_name (type); // 映像名
iflag = disable_interrupts(); // 关中断,这里为空,中断未使能
switch (comp) { //根据压缩方式来选择将要执行的动作 case IH_COMP_NONE: // image未压缩 if (load_start == (ulong)os_hdr) { // 头信息中的加载地址是否等于现在所在的地址 printf (" XIP %s ... ", type_name); } else { printf (" Loading %s ... ", type_name);
memmove_wd ((void *)load_start, // 若不等的话,还要先将image复制到 (void *)os_data, os_len, CHUNKSZ); // 头信息指定的加载地址处 } load_end = load_start + os_len; // 加载后,整个image的结束地址 puts("OK\n"); break; case IH_COMP_GZIP: // gzip的压缩方式 printf (" Uncompressing %s ... ", type_name); if (gunzip ((void *)load_start, unc_len, // 先将image全部解压到头信息中指定 (uchar *)os_data, &os_len) != 0) { //的加载地址处 puts ("GUNZIP: uncompress or overwrite error " "- must RESET board to recover\n"); show_boot_progress (-6); do_reset (cmdtp, flag, argc, argv); }
load_end = load_start + os_len; // 加载后,整个image的结束地址 break;
if ((load_start < image_end) && (load_end > image_start)) { 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); } } // load_start load_end 是头信息中预定的 // image_start image_end 是映像文件在内存中放的位置 // 这里主要判断存放地址和加载地址是否有重叠,因为重叠会造成未知的系统崩溃
lmb_reserve(&lmb, load_start, (load_end - load_start));
switch (os) { default: /* handled by (original) Linux case */ case IH_OS_LINUX: do_bootm_linux (cmdtp, flag, argc, argv, &images); break;
case IH_OS_NETBSD: do_bootm_netbsd (cmdtp, flag, argc, argv, &images); break;
case IH_OS_RTEMS: do_bootm_rtems (cmdtp, flag, argc, argv, &images); break; } // 上面可以很清楚的看到,根据头信息中定义的操作系统类型,进入相应的入口中 // 这里Linux就是 do_bootm_linux(cmdtp, flag, argc, argv, &images)
do_bootm_linux() 位于 lib_arm/bootm.c 中
void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[], bootm_headers_t *images) { ulong initrd_start, initrd_end; ulong ep = 0; bd_t *bd = gd->bd; char *s; int machid = bd->bi_arch_number; void (*theKernel)(int zero, int arch, uint params); int ret;
#ifdef CONFIG_CMDLINE_TAG char *commandline = getenv ("bootargs"); // 启动时的命令行参数 #endif
/* find kernel entry point */ if (images->legacy_hdr_valid) { ep = image_get_ep (&images->legacy_hdr_os_copy); // 获得内核入口地址,即头地址+0x40, 跳过头信息 } else { puts ("Could not find kernel entry point!\n"); goto error; } theKernel = (void (*)(int, int, uint))ep; // 终于看到theKernel了,给函数指针赋地址值
s = getenv ("machid"); // 目标板的ID, 如smdk2410为193 if (s) { machid = simple_strtoul (s, NULL, 16); printf ("Using machid 0x%x from environment\n", machid); }
// 检查是否还有ramdisk,如果有的话,则将其加载到指定地址 ret = boot_get_ramdisk (argc, argv, images, IH_ARCH_ARM, &initrd_start, &initrd_end); if (ret) // 只有当有ramdisk,且加载ramdisk失败时才返回1 goto error;
// taglist 向内核传递的参数的设置 // 这里先去掉预编译,加载memory 和 command line 参数
setup_start_tag (bd); setup_memory_tags (bd); setup_commandline_tag (bd, commandline);
setup_end_tag (bd);
/* we assume that the kernel is in place */ printf ("\nStarting kernel ...\n\n");
/* * 在进入内核前要做几件事 * 关中断 关I/D-cache */ cleanup_before_linux ();
/* * 进入内核,注意传递的几个参数 * 1. 0 * 2. mach id * 3. 参数taglist地址 */ theKernel (0, machid, bd->bi_boot_params); /* does not return */ return;
error: do_reset (cmdtp, flag, argc, argv); return; }
/* * 重点来看一下taglist, 因为这个之前在vivi中没有。vivi中使用的是param struct * 来传递参数的。新的内核都推荐用taglist来传递参数 */ static struct tag *params; struct tag { struct tag_header hdr; union { struct tag_core core; struct tag_mem32 mem; struct tag_videotext videotext; struct tag_ramdisk ramdisk; struct tag_initrd initrd; struct tag_serialnr serialnr; struct tag_revision revision; struct tag_videolfb videolfb; struct tag_cmdline cmdline;
/* * Acorn specific */ struct tag_acorn acorn;
/* * DC21285 specific */ struct tag_memclk memclk; } u; };
+=============+==================================================+ <= bd->bi_boot_params | tag_header | hdr.tag = ATAG_CORE | | hdr | hdr.size = tag_size(tag_core) | +-------------+------------------------------------------------------------------+ | | u.core.flags = 0 | | union u | u.core.pagesize = 0; | | | u.core.rootdev = 0; | +=============+==================================================================+ | tag_header | hdr.tag = ATAG_MEM | | hdr | hdr.size = tag_size(tag_mem32) | +-------------+------------------------------------------------------------------+ | | u.mem.start = bd->bi_dram[i].start | | union u | u.mem.size = bd->bi_dram[i].size | | | | +=============+==================================================================+ | tag_header | hdr.tag = ATAG_CMDLINE | | hdr | hdr.size = (sizeof(struct tag_header) + strlen(p) + 1 + 4) >> 2; | +-------------+------------------------------------------------------------------+ | | | | union u | strcpy(u.cmdline.cmdline, p) | | | | +=============+==================================================================+ | tag_header | hdr.tag = ATAG_NONE | | hdr | hdr.size = 0 | +-------------+------------------------------------------------------------------+ | | | | union u | | | | | +=============+==================================================================+
|