uboot 的启动流程在网上有很多大佬记录,但是了对于像我这样的新手就有些困难了,而我也不做 uboot 相关的工作,所以没必去研究代码,这里我特意整理了一下,以流程图的形式展现代码执行的流程,方便快速了解 uboot 是怎么启动的,此笔记就不进行代码分析了,主要记录 uboot 启动流程中所执行的函数已经函数所在的文件,需要了解函数中的代码实现,可以结合 uboot 源码和正点原子的开发手册或者其他博客。
注意: uboot 运行过程中都是以单线程执行的,所以分析启动流程的时候相对多线程好理解。流程中有些函数名和文件位置可能不一样,但是不要慌,就这样慢慢的找下去就可以快速了解到自己的工程是怎么启动的了
二、SOC 启动流程
uboot 只是一个启动引导向,最终的目的是启动 linux 那么即使不使用 uboot 也可以用其他的引导向,但是目前主流都是使用的 uboot,所以这里对uboot的执行函数进行了整理,方便大家好阅读 uboot 的工程源码,在了解uboot之前,需要了解一下芯片的都做啥了。
看到这个笔记的小伙伴们,应该都知道,系统的启动方式有很多种,比如 SD、mmc、norflash、nandflash等,那么我们 uboot 就可以存在其中一个硬件设备中,芯片是怎么知道 uboot 在那里又是怎么去执行 uboot 代码的?
半导体厂商在制作芯片的时候,会在芯片内部的 ROM 中植入一小段程序,上电后芯片会先执行内部的代码,然后判断我们是以什么方式启动,并在对应的设备中找到 uboot 程序,最终启动 linux 系统,当然芯片内部的这段代码还是比较麻烦的,并且厂家也不会公布这段代码,这里我就不做过多介绍了,需要的小伙伴可以去了解一下。
芯片内部的 SRAM 是比较小的,不足以跑复杂的程序,所以当芯片找到 uboot 程序后,会执行 uboot 的一小段程序,这小段程序叫做uboot SPL,他的主要目的就是初始化芯片使用的外部 RAM 然后将剩余的 uboot 放到外部的内存中运行,提高芯片的运行能力,具体可以了解这位大佬的博客:u-boot (3) —— spl
到这里差不多了,接下来可以了解 uboot 的启动流程了。
三、uboot 入口
在分析程序之前,都会从入口函数开始,从上图可知uboot 的入口是 u-boot.lds 链接脚本开始的。可能会有小伙变怎有疑问,我是怎么知道最先执行的 u-boot.lds 链接脚本,其实在了解一个工程之前,会先从 makefile 开始,只是我 uboot 中的makefile 比较复杂,我还有些不了明白,这里就不献丑了,有需要的小伙伴可以先看大佬的分析,所以从makefile文件中知道,最先执行的是 u-boot.lds 链接脚本。
-
u-boot.lds
分析 uboot 顶层 Makefile 时,得知 uboot 的启动是从链接脚本 u-boot.lds 文件开始的,所以我们需要找到 u-boot.lds 的文件位置,如果没有编译的话,最初的链接脚本在 arch/arm/cpu/ 路径下,但是这个不是最终使用的链接脚本,在编译时会在 uboot 的根目录下生成 u-boot.lds 文件,所以在编译过程中使用的是根目录下的连接脚本。链接脚本中描述了 uboot 的段的内存使用地址,以及中断向量表的地址,可以结合 uboot 根目录下的 u-boot.map 文件进行分析,这里就不详细介绍了。
-
_start
打开链接脚本后,会看到 ENTRY(_start) 声明的入口函数 _start ,而函数 _start 在 arch/arm/lib/vectors.S 文件中,此函数的作用是声明一些中断函数,当上电启动时会跳转到 reset 复位函数。 -
reset
reset 函数在文件 arch/arm/cpu/armv7/start.S 文件中,不同的芯片文件位置不同,我使用的芯片是armv7架构的,在 reset 函数中有 save_boot_params 、cpu_init_cp15 、 cpu_init_crit 、 _main 函数- save_boot_params 也在 start.S 文件中,主要是设置 CPU 的为SVC模式。
- cpu_init_cp15 也在文件 start.S 中,主要作用是设置 CP15 相关的内容,比如关闭 MMU 啥的。
- cpu_init_crit 也在文件 start.S 中,cpu_init_crit 内部仅仅是调用了函数 lowlevel_init。
-
lowlevel_init
lowlevel_init函数在文件 arch/arm/cpu/armv7/lowlevel_init.S 中,主要用于设置堆栈以调用C函数执行进一步的初始化,lowlevel_init 函数中调用了 s_init 函数。 -
s_init
s_init 函数在 arch/arm/cpu/armv7/xxx/soc.c 文件中,有的芯片型号中没有 soc.c 文件,而 s_init 函数没有什么作用,就可以不用了解了 。
三、uboot 外设初始化
此流程主要是完成 uboot 工作的基本条件,并初始一些外设,代码很多,初次学习最好不要直接对函数进行具体的分析,先了解框架。
-
_main
_main 函数定义在文件 arch/arm/lib/crt0.S 中,在 _main 函数主要有 board_init_f 、 relocate_code 、relocate_vectors 、 c_runtime_cpu_setup 、 board_init_r 函数 -
board_init_f
board_init_f 函数在文件 common/board_f.c 中,如下图所示:board_init_f 函数中会执行 init_sequence_f 表中的函数,主要有两个工作
- 初始化一系列外设,比如串口、定时器,或者打印一些消息等。
- 初始化 gd 的各个成员变量,uboot 会将自己重定位到 DRAM 最后面的地址区域,也就是将自己拷贝到 DRAM 最后面的内存区域中。
其中 serial_init 函数初始串口后,我们就可以使用 printf 函数打印日志,打印后便会在控制台中看到相应的信息,和C语言中的用法一样,
display_options 函数中会打印 uboot 的版本信息等,具体的函数实现只能后后面需要的时候自行了解了。
-
relocate_code
relocate_code 函数在文件 arch/arm/lib/relocate.S 中,主要作用是用于代码拷贝。 -
relocate_vectors
relocate_vectors 函数在文件 arch/arm/lib/relocate.S 中,主要作用是用于重定位向量表。 -
c_runtime_cpu_setup
c_runtime_cpu_setup 函数在文件 arch/arm/cpu/armv7/start.S 中 -
board_init_r
board_init_r 函数在文件 common/board_r.c 中,主要作用是完成 board_init_f 没有初始化的外设,以及一些后续工作。也会执行 init_sequence_r 表中的函数,在函数最后会调用 run_main_loop 函数。 -
run_main_loop
函数 run_main_loop 也在文件 common/board_r.c 中,此函数主要是在死循环中调用 main_loop() 函数
四、uboot 命令执行
-
main_loop()
main_loop 函数在文件 common/main.c 中,在函数中主要执行 autoboot_command 和 cli_loop 函数。 -
autoboot_command
autoboot_command 函数在 common/autoboot.c 中,其中会通过 Abortboot 函数判断在控制台打印的倒计时结束之前是否有按键按下,如果存在按键按下时,会执行 run_command_list 函数进入 uboot 系统。反之会返回到 main_loop 函数中执行 cli_loop 函数
注意:run_command_list 函数也在 cli.c 文件中,只是流程图不好直观的表示出来。 -
cli_loop
cli_loop 在文件 common/cli.c 中,主要作用是执行相应的命令操作,在 cli_simple_loop 函数存在一个死循环,用于接收控制台的命,并处理相应的命令工作。 -
cli_simple_run_command
cli_simple_run_command 函数在 common/cli_simple.c 文件中,主要作用是执行相应的命令操作,从图中可以看出,不论是正常启动 linux 或 进入uboot系统,最终都会执行此函数,在函数中会调用 find_cmd 查找命令,调用 cmd_call 执行命令操作。 -
find_cmd
find_cmd 函数在 common/command.c 文件中,主要作用是在映射表中查找相应的命令是否存在,命令通过宏 U_BOOT_CMD 进行定义的。 -
find_call
find_call 函数在 common/command.c 文件中,主要作用是调用 find_cmd 中查找到的 do_xxx 函数,最终执行相应的命令操作。 -
do_xxx
do_xxx 函数在 cmd 目录下,作用就是命令操作的实现函数,比如启动函数 bootz 或 bootm ,所以从图中可知,不论是正常启动 linux 还是在 uboot 中通过命令启动 linux 原理都是一样的,最终也是执行 bootz 或 bootm 命令。
五、bootm 启动 Linux 内核
注意:这里我就没有画对应的流程图了,因为在正点原子的教材中有相应的流程图,所以我这里就直接引用了。关于启动linux 的流程我也没有仔细分析,只是大体看了一下,此笔记的主要原因是我好奇 uboot 都做了些什么,学习驱动开发是否有必要去学习 uboot 中的驱动开发。
通过对 uboot 流程的启动分析,发现 uboot 中的驱动主要根据自己在启动阶段的去求是实现驱动即可,因为在启动 linux 的时候,会在对外设驱动进行实现,达到同一管理,并且在 linux 启动后 uboot 就没有作用了,想在再次进入uboot,执行重新启动。
-
bootm
bootm 命令的执行函数为 do_bootm,在文件 cmd/bootm.c 中,do_bootm 最后调用的就是函数 do_bootm_states -
do_bootm_states
do_bootm_states 函数定义在文件 common/bootm.c 中,函数会根据不同的 BOOT 状态执行不同的代码段。 -
bootm_start
bootm_start 函数在 common/bootm.c 文件中,作用是清空 images 结构体,获取 uboot 的环境变量 verify 的值 -
bootm_find_os
bootm_find_os 函数,函数在 common/bootm.c 文件中,在函数中会调用 boot_get_kernel,
boot_get_kernel 会根据 bootm 传过来的参数去获取 uImage(镜像)的存储地址,如果 bootm 没有参数就使用全局变量 load_addr,最后会调用 image_get_kernel 函数进行 kernel 格式校验。 -
bootm_find_other
bootm_find_other 函数common/bootm.c 文件中,主要作用是获取 ramdisk 或者设备树信息。 -
bootm_disable_interrupts
bootm_disable_interrupts 的作用是函数禁用中断。 -
do_bootm_linux
do_bootm_linux 函数在 arch/arm/lib/bootm.c 文件中,次函数就是最终启动 Linux 内核的函数。
到此 uboot 的启动流程也算完成,有什么不对的地方望大佬指出,我会积极学习。
参考链接
u-boot (3) —— spl:https://blog.csdn.net/zhoutaopower/article/details/123133291