U-boot引导内核流程分析

时间:2021-02-05 16:37:19

U-boot引导内核流程分析

1. 加载内核

当U-boot完成重定位和初始化外设后,它将正式进入工作状态,可以加载内核镜像到DDR的链接地址中了,具体的地址也可以通过bootcmd这个环境变量来指定,内核镜像有两种加载方式:

  • 一种是通过tftp将镜像文件直接引导入DDR中内核的链接地址(对于s5pv210来说是30008000),这种方法很适合调试
  • 另一种是从存储介质中的特定扇区读取,这个扇区可以通过分区表来确定(关于分区表以后再续)

2.判断镜像

  • 加载内核到DDR的链接地址后,U-boo将读取镜像的头信息,然后在头信息的特定位置找到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地址了

7.引导时可能出现的问题

最后强调一下,如果U-boot确认无误可以启动起来,而kernel的启动却出现了问题,那么一般是三种情况

  1. 对于老版本的U-boot,有大概率是U-boot传给kernel参数的时候出了问题,着重注意一下创建tag的宏有没有正常定义,如CONFIG_CMDLINE_TAG、CONFIG_MTDPARTITION等
  2. kernel的链接地址与加载地址不符,链接地址可以通过kernel的head.S获知,详见kernel启动汇编阶段分析
  3. kernel的自解压地址与链接地址不符,zImage这类kernel格式必须把自己解压到自己的链接地址,自解压地址在kernel源码目录arch/arm/mach-xxxx/Makefile.boot文件中
  4. 环境变量未被正常设置