1、uboot启动内核的几种方式

uboot 的本质工作是引导 Linux,所以 uboot 肯定有相关的 boot(引导)命令来启动 Linux。常用的跟 boot 有关的命令有: bootz、 bootm 和 boot。

1.1 bootz

要启动 Linux,需要先将 Linux 镜像文件拷贝到 DRAM 中,如果使用到设备树的话也需要将设备树拷贝到 DRAM 中。可以从EMMC 或者 NAND 等存储设备中将 Linux 镜像和设备树文件拷贝到 DRAM,也可以通过 nfs 或者 tftp 将 Linux 镜像文件和设备树文件下载到 DRAM 中。不管用那种方法,只要能将 Linux 镜像和设备树文件存到 DRAM 中就行,然后使用 bootz 命令来启动, bootz 命令用于启动 zImage 镜像文件, bootz 命令格式如下:
bootz [addr [initrd[:size]] [fdt]]
命令 bootz 有三个参数, addr 是 Linux 镜像文件在 DRAM 中的位置, initrd 是 initrd 文件在DRAM 中的地址,如果不使用 initrd 的话使用‘-’代替即可, fdt 就是设备树文件在 DRAM 中的地址。
eg:
tftp 80800000 zImage
tftp 83000000 imx6ull.dtb
bootz 80800000 – 83000000

前边两步是用tftp将内核和dtb拷贝到内存的地址中,然后再通过bootz来启动。

1.2 bootm

bootm 和 bootz 功能类似,但是 bootm 用于启动 uImage 镜像文件。如果不使用设备树的话启动 Linux 内核的命令如下:
bootm addr
addr 是 uImage 镜像在 DRAM 中的首地址,如果要使用设备树,那么 bootm 命令和 bootz 一样,命令格式如下:
bootm [addr [initrd[:size]] [fdt]]
其中 addr 是 uImage 在 DRAM 中的首地址, initrd 是 initrd 的地址, fdt 是设备树(.dtb)文件在 DRAM 中的首地址,如果 initrd 为空的话,同样是用“-”来替代。

注:uboot为了启动linux内核,还发明了一种内核格式叫uImage。uImage是由zImage加工得到的,uboot中有一个工具,可以将zImage加工生成uImage。注意:uImage不关linux内核的事,linux内核只管生成zImage即可,然后uboot中的mkimage工具再去由zImage加工生成uImage来给uboot启动。这个加工过程其实就是在zImage前面加上64字节的uImage的头信息即可。
原则上uboot启动时应该给他uImage格式的内核镜像,但是实际上uboot中也可以支持zImage,是否支持就看是否定义了LINUX_ZIMAGE_MAGIC这个宏。所以大家可以看出:有些uboot是支持zImage启动的,有些则不支持。但是所有的uboot肯定都支持uImage启动。

1.3 boot

boot 命令也是用来启动 Linux 系统的,只是 boot 会读取环境变量 bootcmd 来启动 Linux 系统, bootcmd 是一个很重要的环境变量!其名字分为“boot”和“cmd”,也就是“引导”和“命令”,说明这个环境变量保存着引导命令,其实就是启动的命令集合,具体的引导命令内容是可以修改的比如我们要想使用 tftp 命令从网络启动 Linux 那么就可以设置 bootcmd 为“tftp 80800000 zImage; tftp 83000000 imx6ull-alientek emmc.dtb; bootz 80800000 - 83000000”,然后使 用 saveenv 将 bootcmd 保存起来。然后直接输入 boot 命令即可从网络启动 Linux 系统。

我们在第二课也讲过,如果uboot检测不到任何输入,则会执行autoboot_command,实际上就是执行bootcmd的指令,此命令由初始化环境变量的到,移植时需自定义,如下

uboot支持读取emmc数据 uboot从emmc启动_uboot


CONFIG_BOOTCOMMAND定义在includ/configs/mx6xxx.h中(此文件是移植时需要自己添加的),定义如下(此命令各个board不相同,是需要用户自己需求来的)

uboot支持读取emmc数据 uboot从emmc启动_linux_02

  1. 首先运行findfdt指令,检测board是否支持
  2. 然后切换到mmc dev 1,此处是选择emmc,而不是sd卡
  3. 扫描mmc,若未扫描到则运行netboot指令,从tftp下载
  4. 扫描到emmc,运行loadbootscript指令:loadbootscript=fatload mmc uboot支持读取emmc数据 uboot从emmc启动_uboot_03{mmcpart} ${loadaddr} ${script};此处嵌套指令较多,暂不展开,大概意思就是从外部的emmc读取boot.scr到指定的内存地址上,看是否存在这个文件,若存在则运行此文件的即可。可以把uboot中的所有操作步骤都写入到这个boot.scr放到SD卡中(mmc设备),uboot只需要读取boot.scr,然后解析、执行boot.scr中的启动脚本就可以了。当然这个boot.scr可不是文本文件,boot.scr是二进制代码,它是经过mkimage工具对原始脚本进行编译后的结果,只有经过mkimage工具处理过才可以被uboot识别。
  5. 若没有找到boot.scr文件,则运行loadimage指令:loadimage=fatload mmc uboot支持读取emmc数据 uboot从emmc启动_uboot_03{mmcpart} ${loadaddr} ${image},展开后就是loadimage=fatload mmc 1:1 0x80800000 zImage,即在emmc中找到内核镜像,存储到内存地址0x80800000中,若没有找到,则运行netboot指令从tftp上加载内核。
  6. 将内核加载到内存指定地址后,运行mmcboot指令,如下
    可以看出还加载了设备树,然后通过bootz指令来启动内核,下边我们来想想看看bootz。

2、bootz详解

不管是 bootz 还是 bootm 命令,在启动 Linux 内核的时候都会用到一个重要的全局变量:images, images 在文件 cmd/bootm.c 中有如下定义

uboot支持读取emmc数据 uboot从emmc启动_uboot支持读取emmc数据_05


images 是 bootm_headers_t 类型的全局变量, bootm_headers_t 是个 boot 头结构体,在文件include/image.h 中的定义如下(删除未使用预编译内容,未定义USE_HOSTCC

uboot支持读取emmc数据 uboot从emmc启动_uboot支持读取emmc数据_06


uboot支持读取emmc数据 uboot从emmc启动_设备树_07


全局变量 images 会在 bootz 命令的执行中频繁使用到,相当于 Linux 内核启动的“灵魂”。

2.1 do_bootz函数

bootz 命令的执行函数为 do_bootz,在文件 cmd/bootm.c 中有如下定义:

uboot支持读取emmc数据 uboot从emmc启动_linux_08

  • 594行:过滤掉bootz参数,只保留后边的几个地址(bootz kernel_addr - dtb_addr,过滤掉bootz)
  • 596行:调用bootz_start,后边讲解
  • 603行:调用函数 bootm_disable_interrupts 关闭中断(汇编操作cpsr寄存器实现,关闭了IFQ和IRQ,不展开讲解)
  • 605行:设置 images.os.os 为 IH_OS_LINUX,也就是设置系统镜像为 Linux,表示我们要启动的是 Linux 系统!后面会用到 images.os.os 来挑选具体的启动函数
  • 606行:调用函数 do_bootm_states 来执行不同的 BOOT 阶段,这里要执行的 BOOT 阶段有: BOOTM_STATE_OS_PREP 、BOOTM_STATE_OS_FAKE_GO 和BOOTM_STATE_OS_GO

2.2 bootz_start

bootz_srart 函数也定义在文件 cmd/bootm.c 中,函数内容如下:

uboot支持读取emmc数据 uboot从emmc启动_linux_09

  • 559行:执行do_bootm_states函数,执行 BOOTM_STATE_START 阶段,后边章节讲解
  • 563~571行:设置 images 的 ep 成员变量,也就是系统镜像的入口点,使用 bootz 命令启动系统的时候就会设置系统在 DRAM 中的存储位置,这个存储位置就是系统镜像的入口点,因此 images->ep=0X80800000。若未传入参数,则使用一个默认值
  • 573行:调用 bootz_setup 函数,此函数会判断当前的系统镜像文件是否为 Linux 的镜像文件,并且会打印出镜像相关信息, bootz_setup 函数后边章节讲解
  • 577行:为使用,空函数
  • 583行:调用函数 bootm_find_images 查找 ramdisk 和设备树(dtb)文件,但是我们没有用到 ramdisk,因此此函数在这里仅仅用于查找设备树(dtb)文件,并打印出设备树地址
2.2.1 do_bootm_states

代码如下

uboot支持读取emmc数据 uboot从emmc启动_linux_10


uboot支持读取emmc数据 uboot从emmc启动_uboot支持读取emmc数据_11


uboot支持读取emmc数据 uboot从emmc启动_uboot支持读取emmc数据_12


uboot支持读取emmc数据 uboot从emmc启动_设备树_13


uboot支持读取emmc数据 uboot从emmc启动_uboot_14


函数 do_bootm_states 根据不同的 BOOT 状态执行不同的代码段,通过states &BOOTM_STATE_XXX来进行判断,在bootz_start中首先执行了BOOTM_STATE_START,604~605行根据状态START执行了bootm_start函数。

执行完bootm_start之后,运行到656行,若执行失败,则直接退出。成功则执行bootm_os_get_boot_func函数,根据os的类型找到相应的启动函数指针。

第698行判断到要启动os后,调用boot_selected_os函数进行内核启动。

2.2.1.1 bootm_start

uboot支持读取emmc数据 uboot从emmc启动_设备树_15

首先设置images结构体初值为全0,然后读取参数“verify”信息,保存到images.verify,若不存在此参数,则为-1。boot_start_lmb函数未使用到相应变量,不考虑。然后将images.state设置为start。

2.2.1.2 bootm_os_get_boot_func

代码如下

uboot支持读取emmc数据 uboot从emmc启动_设备树_16


uboot支持读取emmc数据 uboot从emmc启动_uboot支持读取emmc数据_17


之前讲过uboot可以引导多种os,因此这里就是确定要引导的os是哪种类型,然后返回对应的函数指针,由于在do_bootz中设置过 images.os.os = IH_OS_LINUX;因此此处就是返回了do_bootm_linux的函数指针。

2.2.1.3 boot_selected_os

uboot支持读取emmc数据 uboot从emmc启动_设备树_18


实际上也是调用传入的函数指针do_bootm_linux

2.2.2 bootz_setup

uboot支持读取emmc数据 uboot从emmc启动_linux_19


传入的是内核镜像在内存中的起始地址,我们都知道镜像的头部是一个结构体,表示了镜像的一些基本信息,此处就是校验头部魔术书否为linux镜像,若校验通过则打印出内核的起始地址和镜像大小信息,如下

uboot支持读取emmc数据 uboot从emmc启动_linux_20

2.2.3 bootm_find_images

uboot支持读取emmc数据 uboot从emmc启动_环境变量_21

  • 231~238行:未使用ramdisk
  • 239~248行:查找内核设备树,由于我们在启动时已经填写了设备树的内存中的位置,因此此处就是images.ft_addr的赋值操作

2.3 do_bootm_linux

前边讲解了bootz_start之后会继续调用do_bootm_states来执行接下来的几个阶段:BOOTM_STATE_OS_PREP、BOOTM_STATE_OS_FAKE_GO、BOOTM_STATE_OS_GO,而且看代码其实是执行do_bootm_linux函数,因此此处讲解下此函数,函数在 arch/arm/lib/bootm.c中,如下

uboot支持读取emmc数据 uboot从emmc启动_设备树_22


首先执行BOOTM_STATE_OS_PREP,调用boot_prep_linux函数,BOOTM_STATE_OS_FAKE_GO实际上并未使用,因此不考虑,最后执行boot_jump_linux函数跳转

2.3.1 boot_prep_linux

uboot支持读取emmc数据 uboot从emmc启动_设备树_23


uboot支持读取emmc数据 uboot从emmc启动_linux_24


此函数主要就是处理环境变量bootargs,将其存储到kernel设备树的chosen节点中。运行bootcmd指令时,会通过mmcboot指令来设置bootargs参数,命令如下mmcargs=setenv bootargs cnotallow= ttymxc0, 115200 root= /dev/mmcblk1p2 rootwait rw,流程如下(几个函数较为简单,此处不展开讲述)

uboot支持读取emmc数据 uboot从emmc启动_环境变量_25

2.3.2 boot_jump_linux

uboot支持读取emmc数据 uboot从emmc启动_uboot支持读取emmc数据_26


uboot支持读取emmc数据 uboot从emmc启动_设备树_27

  • 274~292:针对64位arm,暂不考虑
  • 293行:变量 machid 保存机器 ID,如果不使用设备树的话这个机器 ID 会被传递给 Linux内核, Linux 内核会在自己的机器 ID 列表里面查找是否存在与 uboot 传递进来的 machid 匹配的项目,如果存在就说明 Linux 内核支持这个机器,那么 Linux 就会启动!如果使用设备树的话这个 machid 就无效了,设备树存有一个“兼容性”这个属性, Linux 内核会比较“兼容性”属性的值(字符串)来查看是否支持这个机器。
  • 295行:函数 kernel_entry,看名字“内核_进入”,说明此函数是进入 Linux 内核的!此函数有三个参数: zero, arch, params,第一个参数 zero 同样为 0;第二个参数为机器 ID; 第三个参数 ATAGS 或者设备树(DTB)首地址, ATAGS 是传统的方法,用于传递一些命令行信息啥的,如果使用设备树的话就要传递设备树(DTB)。
  • 299行:获取 kernel_entry 函数,函数 kernel_entry 并不是 uboot 定义的,而是 Linux 内核定义的, Linux 内核镜像文件的第一行代码就是函数 kernel_entry,而 images->ep 保存着 Linux内核镜像的起始地址,起始地址保存的正是 Linux 内核第一行代码地址
  • 313行:announce_and_cleanup函数,如下
    打印出starting_kernel … 信息,并做一些清理工作,例如一些cache的清理
  • 315~318:设置r2变量为设备树的地址,用于第传递启动内核时的第三个参数,此处取名为r2,也是指代函数的第三个参数
  • 328行:调用内核的启动函数,此处一去不返!uboot的任务圆满完成!

3、uboot启动内核流程图解

由于上述讲解中,有部分函数前后穿插再加上本人文笔有限,可能讲的不是很清楚,现在按照启动的顺序画个思维导图方便理解

uboot支持读取emmc数据 uboot从emmc启动_设备树_28