cortex-a8 uboot系列:第十一章 uboot源码分析 uboot如何启动内核1
0赞一、Uboot和内核到底是什么(从系统启动角度看)
1.Uboot
Uboot的本质是一个复杂的裸机程序。
2.内核
操作系统内核本身也是一个裸机程序,和uboot、其他裸机程序并没有什么本质区别。
但是和其他裸机程序的区别:操作系统运行起来后在软件上分为内核层和应用层,分层后两层的权限不同,内核访问和设备操作的管理上更加精细(内核可以随意访问各种硬件,而应用程序只能被限制的访问硬件和内存地址)。
Uboot的镜像是u-boot.bin,linux系统镜像是zImage,这两个文件都是两个裸机程序bin文件镜像。
二、SD卡中的分区
一个完整的软件+硬件的嵌入式系统,未上电时,bootloader、kernel、rootfs等必须的软件都以镜像的形式(.bin的形式),存储在启动介质中(x210是iNand/SD卡)。运行时,都是在DDR内存中运行,与存储介质无关。这两个状态都是稳定状态。
还有一个状态是动态过程,从静止态到运行态的过程,也就是启动过程。
动态启动过程就是从启动介质(以下以SD卡为例)逐步搬移到DDR内存,并且运行启动代码进行相关的硬件初始化和软件结构的建立,最终达到运行时稳定状态。
静止时u-boot.bin,zImage,rootfs都在SD卡中,他们不能随意存放在SD卡的任意位置,因此需要对SD卡进行一个分区,然后将各种镜像各自存在各自的分区中,这样在启动过程中就知道去什么位置查找uboot、内核等。(uboot和kernel中的分区表必须一致,同时和SD卡的实际使用的分区要一致)。
三、运行时必须要先加载到DDR中链接地址处
Uboot在第一阶段中进行重定位时将第二阶段(整个uboot镜像)加载到DDR的0xc3e00000地址处,这个地址就是uboot的链接地址。
内核也有类似要求,uboot启动内核时要将内存从SD卡读取放到DDR中(其实就是重定位的过程),不能随意放置,必须放在内核的链接地址处,否则不能启动内核。如使用的内核链接地址是0x30008000,那么uboot拷贝kernel代码时,就要将kernel拷贝到地址0x30008000的DDR中去。
四、内核启动需要必要的启动参数
Uboot是无条件启动的,从零开始启动。
内核是不能开机自动完全从零开始启动的,内核启动要别人帮忙。Uboot要帮助内核实现重定位(从SD卡到DDR),以及给内核提供必要的启动参数。如dram的起始地址以及大小,所用串口以及波特率,根文件系统位置等等。
uboot启动kernel时,会调用kernel的入口函数,此时要给kernel传递三个参数。这三个参数是固定的:
r0:固定为0
r1:机器码
r2:启动参数在ddr中的位置
五、启动内核
uboot要启动内核,分为两个步骤:第一步是将内核镜像从启动介质中加载到DDR中,第二步是去DDR中启动内核镜像。(内核代码没有考虑重定位,因为内核知道会有一个bootloader将内核代码加载到DDR链接地址处,所以内核直接从链接地址处运行)
1.加载内核到DDR
静态内核镜像放在哪里?
有两种情况:
第一种:SD卡/iNand/Nand/Norflash等:raw分区
常规启动时各种镜像都在SD卡中,因此uboot只需要从SD卡的kernel分区读取内核镜像到DDR即可。
读取要使用uboot的命令来读取(x210的iNand版本是movi命令,x210的Nand版本就是Nand命令)
如读取kernel到内存0x30008000。
movi read kernel 0x30008000
这里的kernel指的是uboot中的kernel分区(就是uboot中规定的SD卡中的一个区域范围,这个区域范围被设计来存放kernel镜像,就是所谓的kernel分区)
第二种:tftp、nfs等网络下载方式从远端服务器获取镜像
uboot还支持远程启动,也就是内核镜像不烧录到开发板的SD卡中,而是放在主机的服务器中,然后需要启动时uboot通过网络从服务器中下载镜像到开发板的DDR中。
首先在主机上搭建tftp服务器,然后将要下载的文件拷贝到tftp设置的传输目录中。
开发板的uboot中,使用tftp命令将远程的文件通过网络下载下来。
tftp命令格式
tftpDDR地址远程下载的文件名
不管哪种方式,最终结果要的是内核镜像到DDR中特定地址即可,不管内核镜像是怎么到DDR中的。以上2种方式各有优劣。产品出厂时会设置为从SD卡中启动;tftp下载远程启动这种方式一般用来开发。
镜像要放在DDR的什么地址
内核一定要放在链接地址处。链接地址要去内核源代码的链接脚本或者makefile中去查找。X210中是0x30008000。
2.执行内核
uboot将kernel拷贝到DDR中kernel的链接地址处后,直接调用kernel的入口函数,即可启动内核。
uboot使用命令bootm来启动内核。
六、zImage和uImage的区别联系
1.bootm命令对应do_bootm函数(common/cmd_bootm.c)
CONFIG_SECURE_BOOT宏表示安全启动,对程序做了更安全的校验,对于一般应用,这个是不需要的。
CONFIG_ZIMAGE_BOOT宏很重要,这个宏控制条件编译支持zImage格式的内核启动的一段代码。
2.vmlinuz和zImage和uImage
uboot经过编译直接生成的elf格式的可执行程序是u-boot,这个程序是可执行,类似于windows下的exe格式,在linux下可直接执行。但是这种格式不能用来烧录下载。用来烧录下载的是u-boot.bin。这个是由u-boot使用arm-linux-objcopy工具来进行转化(主要是去掉无用的信息)。这个u-boot.bin就叫镜像(image),镜像就是用来烧录到启动介质中让CPU去取指执行的。
Linux内核经过编译后也会生成一个elf格式的可执行程序,叫vmlinux或vmlinuz,这个是原始的未经任何处理加工的原版内核elf文件。嵌入式系统部署时烧录的一般不是这个vmlinux/vmlinuz,而是要用objcopy工具去制作成烧录镜像格式(就是u-boot.bin这种,但是内核没有.bin后缀),制作出来的镜像文件就叫Image(这个镜像就比elf格式的文件要小很多)。
原则上Image就可以直接烧录到启动介质中,但是实际上linux的开发者认为Image的大小还是太大,所以对Image进行了压缩,并且在Image压缩后的文件的前端附加了一部分解压缩代码。构成了压缩格式的镜像就叫zImage。
Uboot为了启动linux内核,还发明了一种内核格式叫uImage,uImage是由zImag加工得到的,uboot中有一个工具,可以将zImage转换成uImage。注意:uImage不关linux内核的事,linux内核只管生成zImage即可,然后uboot中的mkimage工具再去由zImage转换成uImage来给uboot启动。这个转换过程其实就是在zImage的前面加上64字节的uImage的头信息即可。
原则上uboot启动时应该给他uImage格式的内核镜像,但是实际上uboot中也可以支持zImage启动,是否支持就看x210_sd.h中是否定义了CONFIG_ZIMAGE_BOOT这个宏。
这也就是,有些uboot支持zImage启动,有些则不支持。但是所有的uboot肯定都支持uImage启动。
对于mkimage工具,在uboot下的tools目录下,由mkimage.c和mkimage.h生成mkimage,通过mkimage.c就可以知道添加的64字节头信息是什么内容。
上面这一段代码就是提供uboot的zImage启动。当判断镜像是zImage,对其进行校验,校验完后,直接跳转到after_header_check符号处,后面的其他镜像就不校验了。
LINUX_ZIMAGE_MAGIC是一个定义的魔数,这个数等于0x016f2818,表示这个镜像是一个zImage。也就是zImage格式的镜像中在头部的一个固定位置存放了这个数作为格式标记,如果有一个image,去这个位置读取4个字节的数据,判断是否为0x016f2818,是的话,说明是zImage,否则不是。
命令bootm地址(0x30008000),所以do_bootm的argc=2,argv[0]=bootm,argv[1]=0x30008000。但是有argc小于2的情况,这个时候启动地址就是默认的地址,追寻,这个地址就是内存的基地址0x30000000。所以对于bootm命令,可以不带参数执行。不带参数,就默认为启动地址为0x30000000。
从镜像的37字节开始的4个字节数据读取出来,判断是否为0x016f2818,从而可以判断出这个镜像是不是zImage。
image_header_t(include/image.h)这个数据结构式uboot启动内核使用的一个标准启动数据结构,zImage头信息也是一个image_header_t,但是在实际启动之前需要进行一些改造。
images(bootm_headers结构体,include/image.h)全局变量是在do_bootm函数中使用,用来完成启动过程的固定。zImage的校验过程就是先确认是不是zImage,确认后再修改zImage的头信息到合适,修改后用头信息去初始化images这个全局变量,然后就完成了校验。
编译kernel时,
直接make,生成zImage
make uImage,生成uImage,但是会用到mkimage工具,而这个工具在linux源码中是没有的,因此会直接报错。
需要将uboot的tools目录中的mkimage拷贝至$PATH的目录下,就可以了。
此时在make uImage,就会在arch/arm/boot目录下生成uImage文件了。
将该文件通过tftp下载带开发板的DDR,然后使用bootm命令运行。
黄色部分就是uboot解析uImage打印的信息。