原文地址:https://blog.csdn.net/qq_28992301/article/details/51873201
U-boot引导内核流程分析
1. 加载内核
当U-boot完成重定位和初始化外设后,它将正式进入工作状态,可以加载内核镜像到DDR的链接地址中了,具体的地址也可以通过bootcmd这个环境变量来指定,内核镜像有两种加载方式:
- 一种是通过tftp将镜像文件直接引导入DDR中内核的链接地址(对于s5pv210来说是30008000),这种方法很适合调试
- 另一种是从存储介质中的特定扇区读取,这个扇区可以通过分区表来确定(关于分区表以后再续)
2.判断镜像
- 加载内核到DDR的链接地址后,U-boot将读取镜像的头信息,然后在头信息的特定位置找到MAGIC_NUM,由此来判断镜像的种类。这里就要说一下内核镜像的种类了,内核镜像主要分为zImage和uImage
- zImage:当内核编译完成之后首先生成的是一个elf文件,此文件类似于windows下的exe,是可以在操作系统下运行的,故里面有许多的信息;可是我们的kernel是个裸机程序,不需要在操作系统下运行,删除这些信息后体积便能缩为1/10,这就成了我们熟悉的.bin文件,但是因为一些原因这个文件名字起成了Image…而不是Image.bin….;其实这个Image文件已经能用了,但是由于一些历史原因,Image一般要经过压缩变成zImage再使用,这个zImage文件比Image文件更小,它是自解压的,它的头部是一段信息和解压程序,解压程序可以把后面真正的代码自动解压出来,并不需要U-boot的协助
- uImage:uImage是U-boot自己发明的一种内核镜像格式,是在zImage的基础上在加上一段信息和一段校验头,共64字节。那么将zImage加工为uImage的工具在哪呢,在U-boot根目录下/tools中的mkimage程序,只需将其复制到/usr/local/bin/,编译内核时输入make uImage,然后就能产生uImage了
- 判断完镜像种类后U-boot将对镜像头进行校验,然后再次读取头信息,从头信息的特定位置找到这个镜像的各种信息(镜像长度,镜像种类,入口地址)
4.相关环境变量
引导内核有关的环境变量有bootcmd和bootargs。之所以要有这两个环境变量其实是为了灵活,为了在U-boot不重新编译的情况下可以用不同的方式启动
- 关于bootcmd,其实是U-boot内部好几个命令组成的命令集,负责引导内核的操作,以x210板载的bootcmd为例,
bootcmd=movi read kernel 30008000;bootm 30008000;
这其实是两句命令,第一句的意思是从inand的kernel分区读取内容到DDR内的地址30008000,第二句的意思是使用bootm启动命令到DDR的地址30008000处执行内核。其中,这个30008000是由内核的链接地址所确定的 - 关于bootargs,其实是U-boot传递给内核的参数,以x210板载的bootargs为例,
bootargs=console=ttySAC2,115200 root=dev/mmcblk0p2 rw init=/linuxrc rootfstype=ext3
,其中,console=ttySAC2,115200
意思是引导后的控制台使用串口2,波特率115200;root=/dev/mmcblk0p2 rw
意思是根文件系统在SD卡端口0设备(inand)第二分区,并且可读可写;init=/linuxrc意思是linux的进程1(init)进程的路径;rootfstype=ext3
意思是根文件系统的类型是ext3
5.传参及引导
- 最后把入口地址(ep)转化为一个函数指针theKernel = (void (*)(int, int, uint))ep,然后通过函数指针去执行镜像(即执行内核的第一句代码)。至此一去不复返,U-boot彻底结束了它的使命
void (*theKernel)(int zero, int arch, uint params);//定义了一个函数指针
//中间代码略过
theKernel = (void (*)(int, int, uint))ep;//把入口地址赋给函数指针
//中间代码略过
theKernel (0, machid, bd->bi_boot_params);//跳到内核入口执行内核,再也不返回
执行前传递给内核三个参数:
- 0:这个参数固定为0
- 机器码:只有uboot的机器码和内核镜像中的机器码相同时,内核才能被正确启动,机器码的值第一顺序备选是环境变量machid,如果环境变量里面没有,则第二顺序备选是gd->bd->bi_arch_num(x210_sd.h中硬编码配置的)
- bd->bi_boot_params:这是全局变量结构体gd中的硬件信息结构体bd中的变量bi_boot_params,它的值为U-boot传给kernel的众多参数数据结构——tag的首地址。各个tag里面分别有许多有用的信息,比如环境变量bootargs的各种参数就在tag里,一般来说有关tag的代码是不用动的,只要考虑那些用来创建tag的宏(x210.h内)就行了
6.补充:新版uboot与内核的传参与引导
近年来的内核在启动时还需要dts文件,于是比较新的uboot都实现了传递dtb的功能,为了使能设备树,需要在编译U-boot的时候在config文件中加入:#define CONFIG_OF_LIBFDT
- 一般分为三种情况:
- 利用U-boot的命令,在引导kernel时将dts传入。这种方式需要将dtb的地址写到uboot中(一般是环境变量),比如:首先将kernel载入内存,然后用
fdt addr ${fdtaddr}
命令将dtb载入内存,最后使用bootz ${loadaddr} ${initrdaddr} ${fdtaddr}
来引导内核,(其中initrd是临时文件系统,嵌入式中用得极少)实际使用时用“-”代替:bootz ${loadaddr} - ${fdtaddr}
。总之,U-boot中的命令和环境变量是很灵活的,可以随意组合 - 将dts和kernel打包为pImage。这种方式无需将dtb的地址写到uboot中(但uboot中要实现读pImage头部的功能),uboot可以去pImage的头部信息处读取到dtb的地址,然后传给传递给kernel
- 启用kernel中”ARM_APPENDED_DTB”选项,该选项的意思是将dtb和kernel打包在一起,如此一来kernel启动时会去紧挨着它的地方寻找dtb,这样就不需要uboot来传递dtb地址了
- 利用U-boot的命令,在引导kernel时将dts传入。这种方式需要将dtb的地址写到uboot中(一般是环境变量),比如:首先将kernel载入内存,然后用
7.引导时可能出现的问题
最后强调一下,如果U-boot确认无误可以启动起来,而kernel的启动却出现了问题,那么一般是三种情况
- 对于老版本的U-boot,有大概率是U-boot传给kernel参数的时候出了问题,着重注意一下创建tag的宏有没有正常定义,如CONFIG_CMDLINE_TAG、CONFIG_MTDPARTITION等
- kernel的链接地址与加载地址不符,链接地址可以通过kernel的head.S获知,详见kernel启动汇编阶段分析
- kernel的自解压地址与链接地址不符,zImage这类kernel格式必须把自己解压到自己的链接地址,自解压地址在kernel源码目录arch/arm/mach-xxxx/Makefile.boot文件中
- 环境变量未被正常设置