Bootz总体启动流程
当bootz命令输入后,U-Boot会执行以下操作:

启动流程详解
images全局变量
这个全局变量是一个结构体,主要容纳了OS(Linux内核)信息、设备树、启动状态机等信息,它是 Linux 启动全过程中,所有镜像、地址、长度、入口点、状态的唯一权威记录。
do_bootz函数
1 | int do_bootz(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) |
这个函数首先调用 bootz_start() 完成启动前的准备工作,如果准备失败则直接返回。然后关闭所有中断。设置 images.os.os 为 IH_OS_LINUX,也就是设置系统镜像为 Linux,表示我们要启动的是 Linux 系统!后面会用到 images.os.os 来挑选具体的启动函数。最后调用函数 do_bootm_states 来执行不同的 BOOT 阶段,这里要执行的 BOOT 阶段有: BOOTM_STATE_OS_PREP 、 BOOTM_STATE_OS_FAKE_GO 和 BOOTM_STATE_OS_GO。
bootz_start 函数
1 | static int bootz_start(cmd_tbl_t *cmdtp, int flag, int argc, |
bootz_setup() 会算出 zImage 相关的“占用区间”(用于 LMB 预留),也就是 U-Boot 接下来绝对不能踩的内存范围。第一段,进入BOOTM_STATE_START,初始化启动状态机+LMB。第二段,确定zimage在内存中的地址并写入images->ep。第三段,调用 bootz_setup 函数,此函数会判断当前的系统镜像文件是否为 Linux 的镜像文件,并且会打印出镜像相关信息。第五段会调用bootm_find_images函数,这个函数主要是查找设备树(dtb)文件,找到以后就将设备树的起始地址和长度分别写到 images 的 ft_addr 和 ft_len 成员变量中。我们使用 bootz 启动 Linux 的时候已经指明了设备树在 DRAM 中的存储地址,因此 images.ft_addr=0X83000000,长度根据具体的设备树文件而定,比如我现在使用的设备树文件长度为 0X8C81,因此 images.ft_len=0X8C81。
do_bootm_states 函数
函数 do_bootm_states 根据不同的 BOOT 状态执行不同的代码段,通过如下代码来判断 BOOT 状态:
states & BOOTM_STATE_XXX
在 do_bootz 函数中会用到 BOOTM_STATE_OS_PREP 、 BOOTM_STATE_OS_FAKE_GO和 BOOTM_STATE_OS_GO 这三个 BOOT 状态, bootz_start 函数中会用到 BOOTM_STATE_START 这个 BOOT 状态。本质上这个函数只是个状态机。
在BOOTM_STATE_START状态下,会初始化启动状态机、初始化bootstage并作通用合法性检查。BOOTM_STATE_OS_PREP状态下,会关闭中断、清理cache、关闭MMU、确保CPU模式正常、最终确认 FDT / initrd / bootargs 已就位。BOOTM_STATE_OS_FAKE_GO可以理解为彩排,调试 / 追踪启动流程,而不真的把控制权交给 OS。BOOTM_STATE_OS_GO才是真正的跳转,一般会准备寄存器,确认CPU状态并且函数指针跳转。
结语
通过对 bootz 启动 Linux 内核流程的分析,可以看出 U-Boot 并不是简单地“跳转到内核”,而是通过一套完整而严谨的启动状态机,将内核镜像、设备树、内存布局以及 CPU 状态逐步整理到 Linux 所期望的初始运行环境中。