2017年11月04日 21:05:59 ldzq_sue 阅读数 112
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/ldzq_sue/article/details/78446016
注:带颜色的文本是转发者自己添加的,可能有不当之处,请指出!博主文章不错,未经博主同意转发,抱歉!
在main_loop函数中倒计时结束后就执行 bootcmd 命令跳转到 do_bootm函数引导内核启动。
/*
* Legacy format image header,
* all data in network byte order (aka natural aka bigendian).
*/
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;
/*
* Legacy and FIT format headers used by do_bootm() and do_bootm_
* routines.
*/
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;
#if defined(CONFIG_FIT)
const char *fit_uname_cfg; /* configuration node unit name */
void *fit_hdr_os; /* os FIT image header */
const char *fit_uname_os; /* os subimage node unit name */
int fit_noffset_os; /* os subimage node offset */
void *fit_hdr_rd; /* init ramdisk FIT image header */
const char *fit_uname_rd; /* init ramdisk subimage node unit name */
int fit_noffset_rd; /* init ramdisk subimage node offset */
#if defined(CONFIG_PPC)
void *fit_hdr_fdt; /* FDT blob FIT image header */
const char *fit_uname_fdt; /* FDT blob subimage node unit name */
int fit_noffset_fdt;/* FDT blob subimage node offset */
#endif
#endif
int verify; /* getenv("verify")[0] != 'n' */
struct lmb *lmb; /* for memory mgmt */
} bootm_headers_t;
这两个结构体都是用来存储镜像的头信息的,image_header 仅用于 Legacy 方式启动的镜像(魔数ih_magic,大小,入口地址等),而 bootm_headers 用于 Legacy 或 设备树(FDT)方式启动的镜像(对于Legacy来说只是存放了一个头指针结构体指针和头指针结构体变量和Legacy的是否有效的一个值:legacy_hdr_valid;)。这里只分析 Legacy 方式启动的镜像,在 image_header 中需要注意这几个成员:
uint32_t ih_magic; /* Image Header Magic Number */
uint32_t ih_ep; /* Entry Point Address */
ih_magic 内存储的是镜像的魔数,用来给uboot判断是什么格式的镜像(zImage、uImage等)
先看九鼎添加的这一段do_bootm函数中用zImage启动的代码:
#ifdef CONFIG_ZIMAGE_BOOT
#define LINUX_ZIMAGE_MAGIC 0x016f2818
/* find out kernel image address */
if (argc < 2) {
addr = load_addr; //0x30000000
debug ("* kernel: default image load address = 0x%08lx\n",
load_addr);
} else {
addr = simple_strtoul(argv[1], NULL, 16);
debug ("* kernel: cmdline image address = 0x%08lx\n", img_addr);
}
如果 argc<2,也就是没有传参的情况,uboot 使用默认的 kernel 加载地址,如果有传参,就会使用传递的地址。load_addr 是在之前用宏定义赋值的一个 unsigned long 变量。
#define LINUX_ZIMAGE_MAGIC 0x016f2818 //这个魔数是4个字节
if (*(ulong *)(addr + 9*4) == LINUX_ZIMAGE_MAGIC) {
printf("Boot with zImage\n");
addr = virt_to_phys(addr);
hdr = (image_header_t *)addr; //地址转换
hdr->ih_os = IH_OS_LINUX; //赋值
hdr->ih_ep = ntohl(addr); //赋值,将执行命令bootm 0x30008000后的第二个参数,也就是uImage或zImage内核的存放地址,传给对应内核(uImage或zImage)image_header_t结构体的指针hdr指向的元素ih_ep。这个元素存放的就是内核(uImage或zImage)启动地址即入口地址:镜像执行时的第一句代码的地址,
从 kernel 的起始地址后的36个字节,也就是第37-40字节中存储的是镜像的魔数,如果等于 LINUX_ZIMAGE_MAGIC 就说明这个镜像是 zImage 的镜像。
之后进行了一个虚拟地址到物理地址的转换,然后将 addr 类型转换为 image_header_t,之后赋值,hdr->ih_os 代表镜像的系统,hdr->ih_ep 代表镜像的入口(entry point)。ntohl函数是用来转换网络字节序到主机字节序的,与大小端有关,追了几层都是__开头的函数,系统调用的函数,一般不用管。
memmove (&images.legacy_hdr_os_copy, hdr, sizeof(image_header_t)); //将对应的镜像(uImage或zImage)的头信息结构体 image_header_t的指针hdr对应的此结构体中的元素复制到bootm_headers或bootm_headers_t 结构体类型变量 images中的元素legacy_hdr_os_copy中,(代码中有定义:static bootm_headers_t images;),至此这个结构体中的元素legacy_hdr_os_copy的赋值结束。
/* save pointer to image header *//
images.legacy_hdr_os = hdr;//给结构体bootm_headers_t中的元素legacy_hdr_os赋值,此值是对应内核(zImage 或uImage)头信息结构体 image_header_t的指针hdr,此处赋值的是zImage内核头信息的指针。
images.legacy_hdr_valid = 1; //设置内核zImage是有效的,头信息填充完了。可以使用此内核了,
上面两句代码的作用是把 image_header_t 的信息复制到 bootm_headers_t 中。
goto after_header_check;
头信息校验完毕,跳转到 after_header_check 标号执行引导代码
}
#endif
之后再看下uboot中自带的检查镜像头信息的一部分,和九鼎添加的这段代码差不多,只是封装更好,基本都调用函数来从头信息中获取镜像的信息来完成检查的操作。
/* get kernel image header, start address and length */
os_hdr = boot_get_kernel (cmdtp, flag, argc, argv,
&images, &os_data, &os_len);
if (os_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:
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;
---
跳转到 after_header_check 标号,Legacy方式就是一个switch语句,根据镜像的系统来进入到对应的引导内核启动的函数中。
after_header_check:
os = hdr->ih_os;
#endif
switch (os) {
default: /* handled by (original) Linux case */
case IH_OS_LINUX:
#ifdef CONFIG_SILENT_CONSOLE
fixup_silent_linux();
#endif
do_bootm_linux (cmdtp, flag, argc, argv, &images);
break;
这里进入到 do_bootm_linux 函数引导内核启动。
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;
这里定义的几个变量,ep 是镜像入口,最后会赋值给 theKernel ,theKernel 所在的地址就是内核启动的第一句代码,还会接受 uboot 给他传递的几个参数。
#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); //即ep = image_get_ep (hdr);将内核头信息结构体image_header_t的指针hdr传入函数 image_get_ep()中,这个函数的作用是用头信息结构体image_header_t的指针hdr,取出结构体中的元素ih_ep的值赋值给ep变量。因为这个元素的值就是对应内核入口地址,函数名 image_get_ep (hdr)中的ep是可变的,也就是说函数名是可变的,一旦函数名确定,譬如说image_get_xxx (hdr),则这个函数的意思就是从指针hdr对应的结构体取出一个元素,这个元素的特点是ih_xxx,这个元素的前面三个字符ih_是固定的,后面的字符xxx是根据变量名的定义时的改变而改变。
#if defined(CONFIG_FIT)
} else if (images->fit_uname_os) {
ret = fit_image_get_entry (images->fit_hdr_os,
images->fit_noffset_os, &ep);
if (ret) {
puts ("Can't get entry point property!\n");
goto error;
}
#endif
} else {
puts ("Could not find kernel entry point!\n");
goto error;
}
这里用来找到 kernel 的入口,Legacy 方式就直接用 image_get_ep 函数获取之前在 do_bootm 函数中得到的ep,但是这里有点问题,我用SI找不到image_get_ep 这个函数的定义,只找得到这个函数被调用,很奇怪。后来在uboot/include/image.h 文件中找到了这个函数的定义,这个函数是用宏定义的,和U_BOOT_CMD宏类似。此宏在该文件uboot/include/image.h的第290~300行。
#define image_get_hdr_l(f) \ static inline uint32_t image_get_##f(image_header_t *hdr) \ { \ return uimage_to_cpu (hdr->ih_##f); \ } image_get_hdr_l (ep); //如果使用:image_get_ep 意思是函数返回一个image_header头指针hdr执行ih_ep 的值。 |
追进 uimage_to_cpu 函数后发现就是 ntohl 函数,效果就是将 hdr->ih_ep 进行关于大小端的转换后返回这个值。
theKernel = (void (*)(int, int, uint))ep;
这里就是将 ep 赋值给 theKernel,theKernel 将是内核启动的第一句代码的地址。
s = getenv ("machid");
if (s) {
machid = simple_strtoul (s, NULL, 16);
printf ("Using machid 0x%x from environment\n", machid);
}
获取 machid,之后会作为参数传递给内核进行比对。如果不同就不能启动。
ret = boot_get_ramdisk (argc, argv, images, IH_ARCH_ARM,
&initrd_start, &initrd_end);
if (ret)
goto error;
这里和 ramdisk (虚拟内存盘?)有关,在函数定义处发现大部分代码和FIT有关,与Legacy方式关系有限。
show_boot_progress (15);
debug ("## Transferring control to Linux (at address %08lx) ...\n",
(ulong) theKernel);
#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) || \
defined (CONFIG_MTDPARTITION)
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 (initrd_start && initrd_end)
setup_initrd_tag (bd, initrd_start, initrd_end);
#endif
#if defined (CONFIG_VFD) || defined (CONFIG_LCD)
setup_videolfb_tag ((gd_t *) gd);
#endif
#ifdef CONFIG_MTDPARTITION
setup_mtdpartition_tag();
#endif
setup_end_tag (bd);
#endif
这一段代码和 uboot 向内核传参有关,uboot 向内核的传参方式是 tag 传参,tag 是 linux中定义的一种数据结构,uboot也定义了相同的数据结构以便于向 kernel 传参。
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;
struct tag_mtdpart mtdpart_info; } u; };
struct tag_header { u32 size; u32 tag; }; |
这个 tag 数据结构中定义了两个成员,一个是 tag_header 结构体,其中的 tag 成员用来表示有效信 息(比如 tag为 ATAG_CORE 就是起始,ATAG_NONE 就是结束,其他的 ATAG_XX 就是表示下面的 联合体中具体是哪一个结构体)。还有就是这个 tag 结构体没有定义一个具体的变量,在操作时是事 先定义的 tag* 类型的指针 params 来操作的。 |
这一段代码中的 setup_xxx_tag 函数实现方式、作用都非常类似,都是首先给 hdr 赋值 tag参数的名称和大小,随后给联合体中写入之前存放板子参数信息的bd变量中的值。
/* we assume that the kernel is in place */
printf ("\nStarting kernel ...\n\n");
#ifdef CONFIG_USB_DEVICE
{
extern void udc_disconnect (void);
udc_disconnect ();
}
#endif
cleanup_before_linux ();
这个是在启动内核之前清 cache。
theKernel (0, machid, bd->bi_boot_params); //使用函数指针调用函数theKernel,启动内核函数了,这个函数指针指向的函数并不是我们平时所说的函数名,有函数名的函数是有函数体的函数,譬如int add(int a, int b),它对应的函数指针是int(*p)(int, int),
给指针p 赋值:p = add,调用时使用p(3, 5)即可执行函数add(3, 5)的效果。但是这里的内核函数theKernel的指针指向的是一个内存的地址,即内核的入口地址,并没有所谓的函数体,但是入口地址处以后的代码就可以认为就是内核函数指针theKernel指向的函数体,函数指针调用传参时不是按照定义函数时的参数:int a, int b传入的,因为theKernel没有函数名和函数体,所以也就没有办法定义一个内核函数指针时参考函数名后的参数类型及参数个数,于是内核就自己根据启动内核的需要:机器码machid,机器信息,这里是以tag方式存储的,所以要将tag结构体的首地址当在内核参数传入,类型吗?就是 两个int 和一个uint 类型,于是定义指针时就定义成:void (*theKernel)(int zero, int arch, uint params);然后给指针赋值: theKernel = (void (*)(int, int, uint))ep;然后就使用函数指针的方式调用函数:theKernel (0, machid, bd->bi_boot_params);
/* does not return */
return;
跳转到 theKernel,附带三个参数,第一个是0,第二个是机器码,第三个是一系列 tag 结构体的首地址(即params = (struct tag *) bd->bi_boot_params;params->hdr.tag = ATAG_CORE 的结构体的地址)。在这里跳转到 theKernel 后就正式进入到内核了,所以这里的注释写的 does not return ,不会返回了。
error:
do_reset (cmdtp, flag, argc, argv);
return;
}
到这里,uboot 已经基本解析完毕,还剩下关于设备树启动方式的分析,这个以后再说吧。
回顾总结整个 uboot 启动过程,从 Makefile 开始配置编译uboot,随后从 start.S 开始启动 跳转到start_armboot 又到main_loop 最后到达 do_bootm 最终引导启动内核。这次对 uboot 代码的分析,一方面是锻炼我阅读代码的能力,另一方面是加强对整个 uboot 启动的理解,还有就是在这个过程中理解 uboot 代码的实现思路,uboot 整个代码的可移植性非常强,几乎考虑到了所有方面,大量的使用条件编译来增强其可移植性。uboot 中命令集的巧妙实现方式、对字符数组的解析、使用函数指针来跳转等技巧也使我受益匪浅。
接下来就准备开始移植 uboot 了。