u-boot到内核的启动流程
uboot与Linux内核之间的参数传递
我们知道,uboot启动后已经完成了基本的硬件初始化(如:内存、串口等),接下来它的主要任务就是加载Linux内核到开发板的内存,然后跳转到Linux内核所在的地址运行。
具体是如何跳转呢?做法很简单,直接修改PC寄存器的值为Linux内核所在的地址,这样CPU就会从Linux内核所在的地址去取指令,从而执行内核代码。
在前面我们已经知道,在跳转到内核以前,uboot需要做好以下三件事情:
(1) CPU寄存器的设置
R0=0。
R1=机器类型ID;对于ARM结构的CPU,其机器类型ID可以参见 linux/arch/arm tools/ mach-types
R2=启动参数标记列表在RAM中起始基地址。
(2) CPU工作模式
必须禁止中断(IRQs和FIQs)
CPU必须为SVC模式
(3) Cache和MMU的设置
MMU必须关闭
指令 iCache可以打开也可以关闭
数据 dCache必须关闭
其中上面第一步CPU寄存器的设置中,就是通过R0,R1,R2三个参数给内核传递参数的。(ATPCS规则可以参考)
为什么要给内核传递参数呢?
在此之前,uboot已经完成了硬件的初始化,可以说已经”适应了“这块开发板。然而,内核并不是对于所有的开发板都能完美适配的(如果适配了,可想而知这个内核有多庞大,又或者有新技术发明了,可以完美的适配各种开发板),此时对于开发板的环境一无所知。所以,要想启动Linux内核,uboot必须要给内核传递一些必要的信息来告诉内核当前所处的环境。
如何给内核传递参数?
因此,uboot就把机器ID通过R1传递给内核,Linux内核运行的时候首先就从R1中读取机器ID来判断是否支持当前机器。这个机器ID实际上就是开发板CPU的ID,每个厂家生产出一款CPU的时候都会给它指定一个唯一的ID,大家可以到uboot源码的arch\arm\include\asm\mach-type.h文件中去查看。
R2存放的是块内存的基地址,这块内存中存放的是uboot给Linux内核的其他参数。这些参数有内存的起始地址、内存大小、Linux内核启动后挂载文件系统的方式等信息。很明显,参数有多个,不同的参数有不同的内容,为了让Linux内核能精确的解析出这些参数,双方在传递参数的时候要求参数在存放的时猴需要按照双方规定的格式存放。
除了约定好参数存放的地址外,还要规定参数的结构。Linux2.4.x以后的内核都期望以标记列表(tagged_list)的形式来传递启动参数。标记,就是一种数据结构;标记列表,就是挨着存放的多个标记。标记列表以标记ATAG_CORE
开始,以标记ATAG_NONE
结束。
标记的数据结构为tag,它由一个tag_header结构和一个联合(union)组成。tag_header结构表示标记的类型及长度,比如是表示内存还是表示命令行参数等。对于不同类型的标记使用不同的联合(union),比如表示内存时使用tag_ mem32,表示命令行时使用 tag_cmdline。具体代码见 arch\arm\include\asm\setup.h
struct tag {
struct tag_header hdr; // ATAG_CORE 标识tag参数的开始
//ATAG_MEM 表示内存信息
//ATAG_CMDLINE 标识命令行参数
//表示tag参数的结束
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; //命令参数信息。如linux内核,挂载文件系统方式
/*
* Acorn specific
*/
struct tag_acorn acorn;
/*
* DC21285 specific
*/
struct tag_memclk memclk;
} u;
};
uboot跳转到Linux内核
在uboot中可以使用go和bootm来跳转到内核,这两个命令的区别如下:
(1) go命令仅仅修改pc的值到指定地址
格式:go addr
(2) bootm命令是uboot专门用来启动uImage格式的Linux内核,它在修改pc的值到指定地址之前,会设置传递给Linux内核的参数,用法如下:
格式:bootm addr
uboot中bootm命令实现
bootm命令在uboot源码common/cmd_bootm.c中实现,它的功能如下:
(1)读取uImage头部,把内核拷贝到合适的地方。
(2)把参数给内核准备好。
(3)引导内核。
当我们使用我们在uboot使用bootm命令后,bootm命令会从uImage头中读取信息后,发现是Linux内核,就会调用do_bootm_linux()函数,进而调用boot_jump_linux()函数。函数的具体实现bootm.c中。
内核镜像格式vmlinuz和zImage和uImage
最后插讲下内核的不同映像格式的区别:
(1)uboot经过编译直接生成的elf格式的可执行程序是u-boot,这个程序类似于windows下的exe格式,在操作系统下是可以直接执行的。但是这种格式不能用来烧录下载。我们用来烧录下载的是u-boot.bin,这个东西是由u-boot使用arm-linux-objcopy工具进行加工(主要目的是去掉一些无用的)得到的。这个u-boot.bin就叫镜像(image),镜像就是用来烧录到iNand中执行的。
(2)linux内核经过编译后也会生成一个elf格式的可执行程序,叫vmlinux或vmlinuz,这个就是原始的未经任何处理加工的原版内核elf文件;嵌入式系统部署时烧录的一般不是这个vmlinuz/vmlinux,而是要用objcopy工具去制作成烧录镜像格式(就是u-boot.bin这种,但是内核没有.bin后缀),经过制作加工成烧录镜像的文件就叫Image(制作把78M大的精简成了7.5M,因此这个制作烧录镜像主要目的就是缩减大小,节省磁盘)。
(3)原则上Image就可以直接被烧录到Flash上进行启动执行(类似于u-boot.bin),但是实际上并不是这么简单。实际上linux的作者们觉得Image还是太大了所以对Image进行了压缩,并且在image压缩后的文件的前端附加了一部分解压缩代码。构成了一个压缩格式的镜像就叫zImage。(因为当年Image大小刚好比一张软盘(软盘有2种,1.2M的和1.44MB两种)大,为了节省1张软盘的钱于是乎设计了这种压缩Image成zImage的技术)。
(4)uboot为了启动linux内核,还发明了一种内核格式叫uImage。uImage是由zImage加工得到的,uboot中有一个工具,可以将zImage加工生成uImage。注意:uImage不关linux内核的事,linux内核只管生成zImage即可,然后uboot中的mkimage工具再去由zImage加工生成uImage来给uboot启动。这个加工过程其实就是在zImage前面加上64字节的uImage的头信息即可。
(5)原则上uboot启动时应该给他uImage格式的内核镜像,但是实际上uboot中也可以支持zImage,是否支持就看x210_sd.h中是否定义了LINUX_ZIMAGE_MAGIC这个宏。所以大家可以看出:有些uboot是支持zImage启动的,有些则不支持。但是所有的uboot肯定都支持uImage启动。
(6)如果直接在kernel底下去make uImage会提供mkimage command not found。解决方案是去uboot/tools下cp mkimage /usr/local/bin/,复制mkimage工具到系统目录下。再去make uImage即可。
通过上面的介绍了解到:
zImage下载到目标板中后,可以直接用uboot的命令go来进行直接跳转。这时候内核直接解压启动。但是无法挂载文件系统,因为go命令没有将内核需要的相关的启动参数传递给内核。传递启动参数我们必须使用命令bootm来进行跳转。
Bootm命令跳转只处理uImage的镜像。
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 351134995@qq.com