进入 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 | |
| | |
+=============+==================================================================+