uboot启动内核kernel的总讲

 uboot如何启动内核

本文接着上一阶段对于uboot的两个阶段的分析,继续分析uboot是如何启动内核的。

 

首先得说说内核的启动与uboot的关系:

uboot无条件的从零开始启动,但是内核的启动需要uboot的帮忙,为其提供启动参数,帮助内核实现重定位。完整的软件+硬件的嵌入式操作系统静止未上电时BootLoader、rootfs等都是以镜像的形式储存在启动介质之中的(例如nand、inand、sd卡),如静止时uboot.bin、zimage、rootfs都在SD卡中,因此需要对SD卡进行分区,再将镜像放在对应的分区之内,然后在启动时uboot就会从这个设定的分区中找到对应的镜像文件;镜像最终运行时都在DDR内存中运行,与储存介质无关;而启动过程(也就是uboot所做的)就是将SD卡中的镜像搬移至DDR内存之中,并且运行代码进行相关硬件的初始化和软件架构的建立,最终达到稳定状态的过程。

重定位:搬移sd卡内的内核镜像zimage至SDRAM中的运行的过程也叫重定位,在重定位之前uboot遵从代码的指令,从sd卡的固定分区读取内核的镜像文件,这个要求我们在烧录时指定的烧录分区和这里uboot读取的分区必须一致,不然就无法进行;其次是重定位地址,也就是内核镜像在SDRAM中运行的地址。 在uboot的第一阶段进行重定位将第二阶段(也就是整个uboot的镜像)加载至0xc3e00000地址处,这个地址就是uboot的链接地址。内核启动时也一样,将内存从SD卡读取到DDR之中,也必须放在内核的链接地址处,(譬如我们使用的内核链接地址是0x30008000)

 

三种镜像文件:linuxz   Image   zImage  uImage

Image:linuxz内核直接编译生成的二进制elf文件,相当于uboot编译之后生成的elf文件uboot,但是这个镜像对于大,所以通过arm-linux-objcopy工具将其生成为可以烧录的Image文件,这个文件约为linuxz的十分之一。

zImage:对Image进行一次压缩,生成zImage镜像文件。而这个zImage文件的头部有一段未压缩的代码,这段代码实现了zImage的自解压。

uImage:zImage加工生成的:通过mkimage脚本,在zImage前面加上64字节的校验头信息。

uboot中绝对支持的是uImage,而zImage是否支持是否支持就看x210_sd.h中是否定义了LINUX_ZIMAGE_MAGIC这个宏。

说到这里就不得不说说镜像文件的执行顺序:

1.得到镜像问价的头信息;
2.校验头信息的正确性,完整性;
3.根据头信息得到镜像的一些相关参数,例如起始地址和长度等;
4.跳转至真正的镜像文件位置执行镜像程序。

这也是一种很常见,很重要的设计理念。

启动宏观分析:

第一阶段:重定位,从启动介质中加载内核镜像到SDRAM/DDR中。

第二阶段:先校验镜像文件的头信息(boot_get_kernel函数),然后启动linux内核镜像(do_bootm_linux函数)。

下面对两个阶段进行进一步具体的分许:

第一阶段的具体分析:

加载内核到SDRAM中的命令方法:
        从启动介质sd卡中:movi read kernel 30008000
        利用网络,远程下载镜像: tftp 0x30008000 zImage 注:这里的zimage就是tftf共享文件夹中的内核镜像。

这里重定位的方法类似于uboot的重定位,将内核从规定的位置、分区进行搬移,不做重复的分析。

第二阶段的具体分析:

启动内核的命令方法: bootm 0x30008000  注:0x30008000就是内核在SDRAM中运行的地址。

对bootm命令的分析:

bootm命令的原型是do_bootm函数,这个函数在Cmd_bootm.c中:

int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
    。。。。。。。。
}

(1)首先判断传入参数的个数,如果为指定启动内核的位置的话,则从默认的地址读取镜像文件:

	if (argc < 2) {
		addr = load_addr;
		debug ("*  kernel: default image load address = 0x%08lx\n",
				load_addr);

而这个默认的地址是ox300000000。

(2)进行镜像文件类型的校验:(以zimage为例)

通过固定位置的值与一个已知魔数相比对,来确认是否是zImage镜像:

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);

通过二进制代码比对齐将zImage转化后比对,确实相同。(注:不相同时可能是大小端的原因)

(3)获取镜像的头信息并进行比较:

/* get kernel image header, start address and length */
	os_hdr = boot_get_kernel (cmdtp, flag, argc, argv,
			&images, &os_data, &os_len);

返回的os_hdr指向镜像的起始地址,同样通过输出型参数传出镜像头信息的长度等信息。

(4)根据校验得到的结果,进行一些调试信息的打印:

printf ("   XIP %s ... ", type_name);
printf ("   Loading %s ... ", type_name);
printf ("   Uncompressing %s ... ", type_name);

(5)调用do_bootm_linux()函数完成具体的内核启动工作:

do_bootm_linux (cmdtp, flag, argc, argv, &images);

索引得知do_bootm_linux()函数在bootm.c中定义。

其中传入的参数images是在前面初始化的一个全局变量,同时在前面、、读取头文件的信息之后,将镜像相关参数赋值给image。

①定义了一个很重要的参数:

ulong	ep = 0;

②机器码的再次确认:

int	machid = bd->bi_arch_number;

将在BL2阶段赋值的机器码再次赋值给machine。

③定义了一个十分重要的函数指针:

void	(*theKernel)(int zero, int arch, uint params);

④进行内核传参的具体处理,关于此部分的详细分析请移步:

⑤执行镜像执行的第三四步:

得到镜像的起始地址:

if (images->legacy_hdr_valid) {
		ep = image_get_ep (&images->legacy_hdr_os_copy);

执行镜像文件:

theKernel = (void (*)(int, int, uint))ep;

将镜像的起始地址传给函数指针theKernel。

theKernel (0, machid, bd->bi_boot_params);

最后通过调用theKernel函数指针的方式执行镜像文件。

如果uboot运行正确并成功的加载了内核的镜像会打印出调试信息:

/* we assume that the kernel is in place */
	printf ("\nStarting kernel ...\n\n");

这说明镜像的头信息校验通过了,也找到了镜像的入口地址,并执行了镜像文件。

但如果这句后串口就没输出了,说明内核并没有被成功执行,原因一般是:uboot给传参不正确(80%)、内核在DDR中的加载地址不正确。

上一篇:demo板入门


下一篇:浅析嵌入式系统之uboot详解(3)—看门狗