uboot 的本质工作是引导 Linux,所以 uboot 肯定有相关的 boot(引导)命令来启动 Linux。常用的跟 boot 有关的命令有: bootz、 bootm 和 boot。
要启动 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来启动。
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启动。
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的指令,此命令由初始化环境变量的到,移植时需自定义,如下
CONFIG_BOOTCOMMAND定义在includ/configs/mx6xxx.h中(此文件是移植时需要自己添加的),定义如下(此命令各个board不相同,是需要用户自己需求来的)
不管是 bootz 还是 bootm 命令,在启动 Linux 内核的时候都会用到一个重要的全局变量:images, images 在文件 cmd/bootm.c 中有如下定义
images 是 bootm_headers_t 类型的全局变量, bootm_headers_t 是个 boot 头结构体,在文件include/image.h 中的定义如下(删除未使用预编译内容,未定义USE_HOSTCC)
全局变量 images 会在 bootz 命令的执行中频繁使用到,相当于 Linux 内核启动的“灵魂”。
bootz 命令的执行函数为 do_bootz,在文件 cmd/bootm.c 中有如下定义:
bootz_srart 函数也定义在文件 cmd/bootm.c 中,函数内容如下:
代码如下
函数 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函数进行内核启动。
首先设置images结构体初值为全0,然后读取参数“verify”信息,保存到images.verify,若不存在此参数,则为-1。boot_start_lmb函数未使用到相应变量,不考虑。然后将images.state设置为start。
代码如下
之前讲过uboot可以引导多种os,因此这里就是确定要引导的os是哪种类型,然后返回对应的函数指针,由于在do_bootz中设置过 images.os.os = IH_OS_LINUX;因此此处就是返回了do_bootm_linux的函数指针。
传入的是内核镜像在内存中的起始地址,我们都知道镜像的头部是一个结构体,表示了镜像的一些基本信息,此处就是校验头部魔术书否为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中,如下
首先执行BOOTM_STATE_OS_PREP,调用boot_prep_linux函数,BOOTM_STATE_OS_FAKE_GO实际上并未使用,因此不考虑,最后执行boot_jump_linux函数跳转
此函数主要就是处理环境变量bootargs,将其存储到kernel设备树的chosen节点中。运行bootcmd指令时,会通过mmcboot指令来设置bootargs参数,命令如下mmcargs=setenv bootargs console= ttymxc0, 115200 root= /dev/mmcblk1p2 rootwait rw,流程如下(几个函数较为简单,此处不展开讲述)
由于上述讲解中,有部分函数前后穿插再加上本人文笔有限,可能讲的不是很清楚,现在按照启动的顺序画个思维导图方便理解