首先有函数do_bootm,在这个函数一开始的地方,重定位boot的功能表:
/* 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;
}
******************************************************************
在boot_os[]中,有对操作系统的一些定义,我主要关注的是linux方面的:
boot_os_fn * boot_os[] = {
#ifdef CONFIG_BOOTM_LINUX
[IH_OS_LINUX] = do_bootm_linux,
#endif
...
};
对于宏定义CONFIG_BOOTM_LINUX有:#define CONFIG_BOOTM_LINUX 1;
*********************************************************************
接下来是对bootm命令的一个分类操作:
bootm的第一个参数是映像存储的地址。
Linux可以附带一个参数。此参数会被认为是一个initrd的起始地址,此时bootm命令有三个
步骤:首先解压Linux内核映像并将其复制到ram中,然后将ramdisk映像加载到ram中,最后将控制
权交给内核,并向内核传递ramdisk的位置和大小等信息.
单单用来启动Linux内核,而没有initrd时,可用如下命令:
=> bootm ${kernel_addr} //do_bootm_subcommand,在这个函数中集合了一些函数,在执行boot的时候进行默认操作
如果还有initrd,则可使用下面的命令:
=> bootm ${kernel_addr} ${ramdisk_addr}
**************************************************************************
然后就用到函数bootm_start啦,他用在一个if语句中:
if (bootm_start(cmdtp, flag, argc, argv))
return 1;
下面就来分析一下这个函数:其中有一部分代码被我省略了
static int bootm_start(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
void *os_hdr;
int ret;
memset ((void *)&images, 0, sizeof (images));//images是一个bootm_headers_t类型的全局变量。
images.verify = getenv_yesno ("verify");//从环境变量中检查是否要对镜像的数据(不是镜像头)进行校验。
lmb_init(&images.lmb);//创建一个虚拟的零大小的LMB,稍后将被合并。
/* 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);//返回指向内存中镜像头的指针
if (images.os.image_len == 0) {
puts ("ERROR: can't get kernel image!/n");
return 1;
}
/* get image parameters */
switch (genimg_get_format (os_hdr)) {//根据镜像魔数获取镜像类型
case IMAGE_FORMAT_LEGACY:
images.os.type = image_get_type (os_hdr);//镜像类型
images.os.comp = image_get_comp (os_hdr);//压缩类型
images.os.os = image_get_os (os_hdr);//操作系统类型
images.os.end = image_get_image_end (os_hdr);//当前镜像的尾地址
images.os.load = image_get_load (os_hdr);//镜像数据的载入地址
break;
default:
puts ("ERROR: unknown image format type!/n");
return 1;
}
/* find kernel entry point */
if (images.legacy_hdr_valid) {//如果镜像已经通过验证
images.ep = image_get_ep (&images.legacy_hdr_os_copy);//获取入口地址,填充images.ep 。
} else {
puts ("Could not find kernel entry point!/n");
return 1;
}
if (images.os.os == IH_OS_LINUX) {
/* find ramdisk */
ret = boot_get_ramdisk (argc, argv, &images, IH_INITRD_ARCH,
&images.rd_start, &images.rd_end);
if (ret) {
puts ("Ramdisk image is corrupt or invalid\n");
return 1;
}
}
images.os.start = (ulong)os_hdr;//指向内存中镜像的头地址
images.state = BOOTM_STATE_START;//标记引导状态
return 0;
}
从这段代码中,可以看到在bootm_start中,主要是对镜像文件进行了一些操作,用来获取镜像的一些信息,以用于接下来的步骤。
*********************************************************************************************
接下来是函数:bootm_load_os
这个函数主要判段镜像是否需要解压,并且将镜像移动到加载地址;
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) { /* 选择解压格式 */
case IH_COMP_NONE: /* 镜像没有压缩过 */
if (load == blob_start) /* 判断是否需要移动镜像 */
...
case IH_COMP_GZIP: /* 镜像采用 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");
return BOOTM_ERR_RESET;
}
...
}
}
*********************************************************************************************
在 bootm_start中用到的函数:boot_get_kernel
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;
if (argc < 2) { /* 如果没有参数则用默认加载地址 */
img_addr = load_addr;
//argc < 2 , 即直接输入 bootm 命令,此时映像存储地址为 load_addr;
//ulong load_addr = CFG_LOAD_ADDR; 即初始的映像存储地址,但在 u-boot运行期间, load_addr 的值会被环境变量所更改。
// 若 bootm 后面还带了一参数,如 bootm 30000 ,则映像的存储地址为 0x30000
// 接下来要从 nand 中读出 kernel image 时,就从地址 0x3000 开始读
debug ("* kernel: default image load address = 0x%08lx\n", load_addr);
...
}
switch (genimg_get_format ((void *)img_addr)) {
case IMAGE_FORMAT_LEGACY:
printf ("## Booting kernel from Legacy Image at %08lx ...\n", img_addr);
hdr = image_get_kernel (img_addr, images->verify); /* 获取内核 */
if (!hdr)
return NULL;
show_boot_progress (5);
/* get os_data and os_len */
switch (image_get_type (hdr)) {
case IH_TYPE_KERNEL:
*os_data = image_get_data (hdr); /* 内核入口地址 */
*os_len = image_get_data_size (hdr); /* 内核长度(不包括头部) */
break;
default:
printf ("Wrong Image Type for %s command\n", cmdtp->name);
show_boot_progress (-5);
return NULL;
}
/* 备份镜像头以防止在内核解压时被覆盖 */
memmove (&images->legacy_hdr_os_copy, hdr, sizeof(image_header_t));
images->legacy_hdr_os = hdr;
images->legacy_hdr_valid = 1; /* 标记存在入口点 */
break;
default:
printf ("Wrong Image Format for %s command\n", cmdtp->name);
show_boot_progress (-108);
return NULL;
}
debug (" kernel data at 0x%08lx, len = 0x%08lx (%ld)\n", *os_data, *os_len, *os_len);
return (void *)img_addr; /* 返回加载地址 */
}
上面函数中用到的bootm_headers_t *images;
static bootm_headers_t images; 这个静态全局变量是头信息结构。
typedef struct bootm_headers {
image_header_t *legacy_hdr_os; /* image header pointer */
image_header_t legacy_hdr_os_copy; /* header copy */
void *fit_hdr_rd; /* init ramdisk FIT image header */
int fit_noffset_rd; /* init ramdisk subimage node offset */
image_info_t os; /* os image info */
ulong ep; /* entry point of OS */
......
#define BOOTM_STATE_START (0x00000001) //开始执行bootm的一些准备动作。
#define BOOTM_STATE_LOADOS (0x00000002) //加载操作系统
#define BOOTM_STATE_RAMDISK (0x00000004) // 操作ramdisk
#define BOOTM_STATE_FDT (0x00000008) //操作FDT
#define BOOTM_STATE_OS_CMDLINE (0x00000010) // 操作commandline
#define BOOTM_STATE_OS_BD_T (0x00000020) //
#define BOOTM_STATE_OS_PREP (0x00000040) //跳转到操作系统的前的准备动作
#define BOOTM_STATE_OS_GO (0x00000080) //跳转到kernel中去
} bootm_headers_t;
1)#define IH_MAGIC 0x27051956 -->> struct image_header -->> uint32_t ih_magic
这是一个魔数,0x27051956表示这个镜像是zImage,也就是说zImage格式的镜像中,在头部的一个固定位置存放了这个数,作为格式标记,如果我们拿到了一个image,去他的那个位置去取4个字节,判断它是否等于这个魔数IH_MAGIC。则可以知道这个镜像是不是zImage。
2)image全局变量是在bootm函数中使用,目的是用来指向images,也就是用来完成启动的。zImage校验过程先确定是不是zImage,然后再修改zImage头信息,最后再用这个头信息去初始化image,然后完成了校验。
注意:uboot本身也只支持uImage方式启动的,后来有了设备树之后,就把uImage方式命名为legacy方式,fdt方式就命名为fit方式,于是乎多了#if #endif添加代码。后来移植的人又为了省事添加了zImage的方式,又为了省事,添加了#if #endif .
*********************************************************************************************
boot_get_kernel函数主要进行镜像的有效性判定、校验、计算入口地址等操作,大部分工作通过 boot_get_kernel -> image_get_kernel 完成;
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)) { //检查镜像头部的魔数是否等于 IH_MAGIC
puts ("Bad Magic Number/n");
show_boot_progress (-1);
return NULL;
}
show_boot_progress (2);
if (!image_check_hcrc (hdr)) {//检查镜像头部的校验码(hcrc为0时镜像头的校验码)
puts ("Bad Header Checksum/n");
show_boot_progress (-2);
return NULL;
}
show_boot_progress (3);
image_print_contents (hdr);//根据传入的镜像头地址,打印镜像的信息,见下面的分析。
/*
Image Name: Linux-3.0.35-2666-gbdde708
Image Type: ARM Linux Kernel Image (uncompressed)
Data Size: 4056048 Bytes = 3.9 MB
Load Address: 10008000
Entry Point: 10008000
*/
if (verify) {//是否要对镜像的数据部分进行校验
puts (" Verifying Checksum ... ");
if (!image_check_dcrc (hdr)) {
printf ("Bad Data CRC/n");
show_boot_progress (-3);
return NULL;
}
puts ("OK/n");
}
show_boot_progress (4);
if (!image_check_target_arch (hdr)) {//检查是否是IH_ARCH_ARM架构
printf ("Unsupported Architecture 0x%x/n", image_get_arch (hdr));
show_boot_progress (-4);
return NULL;
}
return hdr;
}
其中用到的重要结构体:
typedef struct image_header {
uint32_t ih_magic; /* Image Header Magic Number */
uint32_t ih_hcrc; /* Image Header CRC Checksum */
uint32_t ih_time; /* Image Creation Timestamp */
uint32_t ih_size; /* Image Data Size */
uint32_t ih_load; /* Data Load Address */
uint32_t ih_ep; /* Entry Point Address */
uint32_t ih_dcrc; /* Image Data CRC Checksum */
uint8_t ih_os; /* Operating System */
uint8_t ih_arch; /* CPU architecture */
uint8_t ih_type; /* Image Type */
uint8_t ih_comp; /* Compression Type */
uint8_t ih_name[IH_NMLEN]; /* Image Name */
} image_header_t;
genimg_get_format(bootm_start)检查img header的头4个字节,代表image的类型,有2种,legacy和FIT,这里使用的legacy,头4个字节为0x27051956。
image_get_kernel则会来计算header的crc是否正确,然后获取image的type,根据type来获取os的len和data起始地址。
最后将hdr的数据拷贝到images的legacy_hdr_os_copy,防止kernel image在解压是覆盖掉hdr数据,保存hdr指针到legacy_hdr_os中,置位legacy_hdr_valid。
*********************************************************************************************
下一个函数:boot_fn(0, argc, argv, &images),调用操作系统启动函数,引导操作系统启动。
功能是根据全局static bootm_headers_t images结构体的image_info_t os域中记录的os类型来将一个特定OS的内核引导函数入口赋给boot_fn变量。此处引导的是Linux内核,那么boot_fn就是do_bootm_linux。
boot_fn = boot_os[images.os.os]; //用来确定linux操作系统
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;
}
boot_fn(0, argc, argv, &images); //如果不出错的话,这个函数应该是不会在返回了,因为在这个函数中会将控制权交由OS的内核。对于引导Linux内核来说,这里其实就是调用do_bootm_linux。
*********************************************************************************************
下面就讲一讲函数do_bootm_linux,在 arm linux 平台中 boot_fn 函数指针指向的函数是位于 lib_arm/bootm.c 的 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);
char *commandline = getenv ("bootargs"); /* 从环境变量中获取命令参数 */
if ((flag != 0) && (flag != BOOTM_STATE_OS_GO)) /* 状态判定 */
return 1;
theKernel = (void (*)(int, int, uint))images->ep; /* 内核入口函数 */
s = getenv ("machid"); /* 从环境变量中获取机器id */
if (s) {
machid = simple_strtoul (s, NULL, 16);
printf ("Using machid 0x%x from environment\n", machid);
}
debug ("## Transferring control to Linux (at address %08lx) ...\n", (ulong) theKernel);
/* 初始化启动参数 */
setup_start_tag (bd); /* 初始化参数列表起始符 */
setup_serial_tag (¶ms); /* 初始化串口参数 */
setup_revision_tag (¶ms); /* 初始化版本参数 */
setup_revision_tag (¶ms); /* 初始化版本参数 */
setup_memory_tags (bd); /* 初始化内存参数 */
setup_commandline_tag (bd, commandline); /* 初始化命令参数 */
if (images->rd_start && images->rd_end)
setup_initrd_tag (bd, images->rd_start, images->rd_end); /* 初始化虚拟磁盘参数 */
setup_videolfb_tag ((gd_t *) gd); /* 初始化fb参数 */
setup_end_tag (bd); /* 初始化参数列表结束符 */
printf ("\nStarting kernel ...\n\n");
cleanup_before_linux (); /* 启动前清空缓存 */
/* 启动内核,满足arm架构linux内核启动时的寄存器设置条件:第一个参数为0
第二个参数为板子id需与内核中的id匹配,第三个参数为启动参数地址 */
theKernel (0, machid, bd->bi_boot_params); /* does not return */
return 1;
}