Bootstrap

uboot - 启动内核过程分析

我们都知道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)实现。

(2)uboot要启动内核,说明内核要运行,肯定要进DDR。
那么uboot提供了那些方法来加载内核镜像到DDR?uboot提供了大概2种方法:

【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 运行内核

-----------------------------------------------------------------

;