我们都知道u-boot被缔造出来的使命是 启动内核。
那么,他是如何完成他的使命的涅!请看下面↓↓↓···
(1)我们先来分析下Linux内核镜像这个概念吧。
我们编译内核完(编译成功)会生成vmlinux,Image,zImage,再通过
uboot提供的工具mkimage,执行make uImage会生成uImage,那么他
们谁是内核镜像。如下图为这3个东东:
vmlinux在kernel根目录下:
Image和zImage和uImage在根目录下的arch/arm/boot目录下:
SO,哪个是镜像文件?哈哈,三个都是。
vmlinux:这个就是原始的未经任何处理加工的原版内核elf文件,他一般加载在
PC机这种的硬盘中,因为未加工(比较大),所以PC机有大硬盘不怕放不下,
所以PC机的上电后直接加载vmlinux内核镜像就可以运行了。比如(Ubuntu)。
Image:vmlinux经过objcopy去掉一些不需要的东西之后得到的。
zImage:Image经过压缩得到的。原则上,Image就可以直接运行了,但是,嵌
入式系统要求精简所以把Image压缩在加上压缩算法放到真正的压缩镜像前面就
得到了zImage。如图可知zImage变小了。
uImage:uImage是经过加64字节的头部信息放到zImage的前面合成得到的。
uImage是用于uboot引导启动时提供的内核镜像,所以uboot在编译成功后,
在根目录下tools/下回提供一个mkimage的工具,将该工具拷贝到系统目录下,
在内核根目录执行make uImage就可以提供zImage生成uImage。
注:uboot本来只支持uImage的启动,而编译内核只负责生成zImage,所以uboot
自己提供了mkimage工具。但是后来uboot也可以支持直接启动zImage,可通过
uboot的配置文件中设置对应的宏开关(CONFIG_ZIMAGE_BOOT)实现。
【1】通过uboot提供的读取FLASH的命令从存取内核的介质加载。
比如,SD卡,nandflash,iNand等等。
【2】通过网络从服务器下载内核镜像到DDR。比如uboot提供的nfs,tftp服务器。
(3)校验内核格式、CRC等
在uboot根目录下的common/cmd_bootm.c文件中的函数
int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
该函数就是实现uboot的命令bootm的。
我们在uboot命令行加载内核就是运行:bootm {DDR address}。
do_bootm函数刚开始定义了一些变量,然后用宏来条件编译执行了secureboot的
一些代码(主要进行签名认证),然后进行了一些一些细节部分操作。
我们上面说过,我们可以通过配置宏来命令uboot加载哪种内核镜像。所以uboot是
怎么判断的呢!
先来zImage启动:
uboot回去读取zImage的刚开始部分的信息和zImage的魔数对比是否一致。
#ifdef CONFIG_ZIMAGE_BOOT
<span style="color:#000099;">#define LINUX_ZIMAGE_MAGIC 0x016f2818 <span style="background-color: rgb(255, 255, 255);"> //魔数</span></span>
/* find out kernel image address */
if (argc < 2) {
addr = load_addr;
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);
}
<span style="color:#000066;">if (*(ulong *)(addr + 9*4) == LINUX_ZIMAGE_MAGIC) { //获取镜像中的魔数 和上面魔数对比</span>
printf("Boot with zImage\n");
addr = virt_to_phys(addr);<span style="white-space:pre"> </span>//将DDR地址转换为物理地址 (使用了MMU)
hdr = (image_header_t *)addr;
hdr->ih_os = IH_OS_LINUX;
hdr->ih_ep = ntohl(addr);<span style="white-space:pre"> </span>//修改填充zImage的头部信息结构体
memmove (&images.legacy_hdr_os_copy, hdr, sizeof(image_header_t));
/* save pointer to image header */
images.legacy_hdr_os = hdr;
images.legacy_hdr_valid = 1;
goto after_header_check;<span style="white-space:pre"> </span>//跳到该标号处
}
#endif
如果那个zImage的宏没有定义将会执行uImage或者设备树的方式。
取决于genimg_get_format (os_hdr)的结果:
switch (genimg_get_format (os_hdr)) {
case IMAGE_FORMAT_LEGACY: <span style="white-space:pre"> </span>···
case IMAGE_FORMAT_FIT:<span style="white-space:pre"> </span>···}
#define IMAGE_FORMAT_LEGACY 0x01 /* legacy image_header based format */
#define IMAGE_FORMAT_FIT 0x02 /* new, libfdt based format */
前者是uImage,后者是设备树。
(4)选择对应的OS,设置参数(machid,tag)。
switch (os) {
default: /* handled by (original) Linux case */
case IH_OS_LINUX: //我们是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:
···
因为我们是Linux,所以接下里会执行函数:
do_bootm_linux (cmdtp, flag, argc, argv, &images);
里面主要是设置给内核的传参。主要是machid和tag。
int machid = bd->bi_arch_number;
或者
s = getenv ("machid");
machid = simple_strtoul (s, NULL, 16);
就是说如果环境变量里面有则采用环境变量的,否则是配置里的。
配置头文件里面设置要配置的tag参数的宏:
CONFIG_SETUP_MEMORY_TAGS,tag_mem,传参内容是内存配置信息。
CONFIG_CMDLINE_TAG,tag_cmdline,传参内容是启动命令行参数,
也就是uboot环境变量的bootargs.
CONFIG_INITRD_TAG
CONFIG_MTDPARTITION,传参内容是iNand/SD卡的分区表。
起始tag是ATAG_CORE、结束tag是ATAG_NONE,其他的ATAG_XXX都是有效信息tag。
内核如何拿到这些tag?
uboot最终是调用theKernel函数来执行linux内核的,uboot调用这个函数
(其实就是linux内核)时传递了3个参数。这3个参数就是uboot直接传递
给linux内核的3个参数,通过寄存器来实现传参的。(第1个参数就放在r0
中,第二个参数放在r1中,第3个参数放在r2中)第1个参数固定为0,第2个
参数是机器码,第3个参数传递的就是大片传参tag的首地址。
(4)函数指针并传参(跳转到运行内核)
theKernel (0, machid, bd->bi_boot_params);
/* does not return */
好了,uboot的使命就算完成了,不过内核是否启动成功,此次启动过程后
再也没有uboot的身影了。
他走了,我们想伟大的uboot致敬3S钟。
-----------------------------------------------------------------
总结:uboot启动内核过程:
1 不惜任何手段将kernel镜像加载到DDR
2 检查是何种镜像
3 设置给内核的传参
4 运行内核
-----------------------------------------------------------------