为什么我们要解析环境变量bootcmd
?
承接博文 https://blog.csdn.net/wenhao_ir/article/details/145902134 继续解析u-boot的环境变量bootcmd
。
为什么要解析u-boot的这个环境变量bootcmd
?因为如果u-boot在倒计时完后,首先执行的是就是下面这条命令:
run_command(env_get("bootcmd"), 0);
其中 env_get("bootcmd")
取出 bootcmd
环境变量的内容,并将其传递给 run_command()
执行。
所以我们有必要去解析环境变量bootcmd
的内容。
打印出u-boot的环境变量的具体内容
说明:本文使用的u-boot是博文 https://blog.csdn.net/wenhao_ir/article/details/145662136 中经过我修改移植后的u-boot。在博文 https://blog.csdn.net/wenhao_ir/article/details/145662136 中有烧写方法和它的百度网盘下载地址。
u-boot运行后,可用下面的命令单独打印出变量 bootcmd
的内容:
printenv bootcmd
这里,由于各环境变量之间有依赖关系,所以需要用下面的命令完整打印出所有环境变量的内容:
printenv
baudrate=115200
board_name=EVK
board_rev=14X14
boot_fdt=try
bootcmd=run findfdt;run findtee;mmc dev ${mmcdev};mmc dev ${mmcdev}; if mmc rescan; then if run loadbootscript; then run bootscript; else if run loadimage; then run mmcboot; else run netboot; fi; fi; else run netboot; fi
bootcmd_mfg=run mfgtool_args;if iminfo ${initrd_addr}; then if test ${tee} = yes; then bootm ${tee_addr} ${initrd_addr} ${fdt_addr}; else bootz ${loadaddr} ${initrd_addr} ${fdt_addr}; fi; else echo "Run fastboot ..."; fastboot 0; fi;
bootdelay=3
bootscript=echo Running bootscript from mmc ...; source
console=ttymxc0
emmc_ack=1
emmc_dev=1
eth1addr=00:01:3f:2d:3e:4d
ethact=ethernet@20b4000
ethprime=eth1
fastboot_dev=mmc1
fdt_addr=0x83000000
fdt_file=undefined
fdt_high=0xffffffff
fdtcontroladdr=9df6d770
findfdt=if test $fdt_file = undefined; then if test $board_name = ULZ-EVK && test $board_rev = 14X14; then setenv fdt_file imx6ulz-14x14-evk.dtb; fi; if test $board_name = EVK && test $board_rev = 9X9; then setenv fdt_file imx6ull-9x9-evk.dtb; fi; if test $board_name = EVK && test $board_rev = 14X14; then setenv fdt_file imx6ull-14x14-evk.dtb; fi; if test $fdt_file = undefined; then echo WARNING: Could not determine dtb to use; fi; fi;
findtee=if test $tee_file = undefined; then if test $board_name = ULZ-EVK && test $board_rev = 14X14; then setenv tee_file uTee-6ulzevk; fi; if test $board_name = EVK && test $board_rev = 9X9; then setenv tee_file uTee-6ullevk; fi; if test $board_name = EVK && test $board_rev = 14X14; then setenv tee_file uTee-6ullevk; fi; if test $tee_file = undefined; then echo WARNING: Could not determine tee to use; fi; fi;
image=zImage
initrd_addr=0x86800000
initrd_high=0xffffffff
ip_dyn=yes
kboot=bootz
loadaddr=0x80800000
loadbootscript=fatload mmc ${mmcdev}:${mmcpart} ${loadaddr} ${script};
loadfdt=fatload mmc ${mmcdev}:${mmcpart} ${fdt_addr} ${fdt_file}
loadimage=fatload mmc ${mmcdev}:${mmcpart} ${loadaddr} ${image}
loadtee=fatload mmc ${mmcdev}:${mmcpart} ${tee_addr} ${tee_file}
mfgtool_args=setenv bootargs console=${console},${baudrate} rdinit=/linuxrc clk_ignore_unused
mmcargs=setenv bootargs console=${console},${baudrate} root=${mmcroot}
mmcautodetect=yes
mmcboot=echo Booting from mmc ...; run mmcargs; if test ${tee} = yes; then run loadfdt; run loadtee; bootm ${tee_addr} - ${fdt_addr}; else if test ${boot_fdt} = yes || test ${boot_fdt} = try; then if run loadfdt; then bootz ${loadaddr} - ${fdt_addr}; else if test ${boot_fdt} = try; then bootz; else echo WARN: Cannot load the DT; fi; fi; else bootz; fi; fi;
mmcdev=1
mmcpart=1
mmcroot=/dev/mmcblk1p2 rootwait rw
netargs=setenv bootargs console=${console},${baudrate} root=/dev/nfs ip=dhcp nfsroot=${serverip}:${nfsroot},v3,tcp
netboot=echo Booting from net ...; ${usb_net_cmd}; run netargs; if test ${ip_dyn} = yes; then setenv get_cmd dhcp; else setenv get_cmd tftp; fi; ${get_cmd} ${image}; if test ${tee} = yes; then ${get_cmd} ${tee_addr} ${tee_file}; ${get_cmd} ${fdt_addr} ${fdt_file}; bootm ${tee_addr} - ${fdt_addr}; else if test ${boot_fdt} = yes || test ${boot_fdt} = try; then if ${get_cmd} ${fdt_addr} ${fdt_file}; then bootz ${loadaddr} - ${fdt_addr}; else if test ${boot_fdt} = try; then bootz; else echo WARN: Cannot load the DT; fi; fi; else bootz; fi; fi;
script=boot.scr
sd_dev=1
serial#=2e1181d769237caa
splashimage=0x8c000000
tee=no
tee_addr=0x84000000
tee_file=undefinedEnvironment size: 3388/8188 bytes
从中可见环境变量bootcmd
的具体内容为:
bootcmd=run findfdt;run findtee;mmc dev ${mmcdev};mmc dev ${mmcdev}; if mmc rescan; then if run loadbootscript; then run bootscript; else if run loadimage; then run mmcboot; else run netboot; fi; fi; else run netboot; fi
接下来我们需要对环境变量bootcmd
中的语句进行分析,并理出我们这里的执行流程。
对环境变量bootcmd
的内容进行分行和缩进处理
环境变量 bootcmd
的内容如下:
bootcmd=run findfdt;run findtee;mmc dev ${mmcdev};mmc dev ${mmcdev}; if mmc rescan; then if run loadbootscript; then run bootscript; else if run loadimage; then run mmcboot; else run netboot; fi; fi; else run netboot; fi
全写在一行中,对我们阅读很不友好,不妨先利用chatgpt将其进行分行和缩进处理,分行和缩进处理后如下:
bootcmd=run findfdt;run findtee;mmc dev ${mmcdev};mmc dev ${mmcdev}; # 重复执行了一次,可能是为了确保正确切换到目标 mmc 设备if mmc rescan; thenif run loadbootscript; thenrun bootscript;elseif run loadimage; thenrun mmcboot;elserun netboot;fi;fi;elserun netboot;fi;
对环境变量bootcmd
的每一条语句的解析
所有环境变量及环境变量bootcmd
的内容在上面的内容中已经给出,在开始分析环境变量bootcmd
中的各语句的意义,当知道其每条语句的意义后,也就知道了其执行流程了。
环境变量 bootcmd
的内容如下:
bootcmd=run findfdt;run findtee;mmc dev ${mmcdev};mmc dev ${mmcdev}; if mmc rescan; then if run loadbootscript; then run bootscript; else if run loadimage; then run mmcboot; else run netboot; fi; fi; else run netboot; fi
分行和缩进处理后如下:
bootcmd=run findfdt;run findtee;mmc dev ${mmcdev};mmc dev ${mmcdev}; # 重复执行了一次,可能是为了确保正确切换到目标 mmc 设备if mmc rescan; thenif run loadbootscript; thenrun bootscript;elseif run loadimage; thenrun mmcboot;elserun netboot;fi;fi;elserun netboot;fi;
以下是对 bootcmd
中每一句语句的解析:
分析前,先了解下这个 bootcmd
主要完成的任务,如下:
- 查找设备树(FDT)文件
- 查找 TEE(可信执行环境)文件
- 选择 eMMC/SD 设备
- 尝试从 MMC 设备加载 U-Boot 启动脚本
boot.scr
- 如果
boot.scr
不存在,则尝试直接加载 Linux 内核 - 如果 MMC 启动失败,则回退到网络启动
1. 运行 findfdt
以确定设备树(DTB)文件的名字
run findfdt;
findfdt
变量的内容:findfdt=if test $fdt_file = undefined; then \if test $board_name = ULZ-EVK && test $board_rev = 14X14; then \setenv fdt_file imx6ulz-14x14-evk.dtb; \fi; \if test $board_name = EVK && test $board_rev = 9X9; then \setenv fdt_file imx6ull-9x9-evk.dtb; \fi; \if test $board_name = EVK && test $board_rev = 14X14; then \setenv fdt_file imx6ull-14x14-evk.dtb; \fi; \if test $fdt_file = undefined; then \echo WARNING: Could not determine dtb to use; \fi; \ fi;
- 作用:根据
board_name
和board_rev
确定要使用的设备树文件,并存储到fdt_file
变量中。 - 具体在这里,由于环境变量
board_name
的值为board_name=EVK
,环境变量board_rev
的值为14X14
,所以环境变量fdt_file
的值被设为imx6ull-14x14-evk.dtb
,即内核的设备树文件的名字为imx6ull-14x14-evk.dtb
。
2. 运行 findtee
以确定 TEE 镜像的文件名
关于TEE 镜像是什么东西,请参考我的另一篇博文 https://blog.csdn.net/wenhao_ir/article/details/146054128
run findtee;
findtee
变量的内容:findtee=if test $tee_file = undefined; then \if test $board_name = ULZ-EVK && test $board_rev = 14X14; then \setenv tee_file uTee-6ulzevk; \fi; \if test $board_name = EVK && test $board_rev = 9X9; then \setenv tee_file uTee-6ullevk; \fi; \if test $board_name = EVK && test $board_rev = 14X14; then \setenv tee_file uTee-6ullevk; \fi; \if test $tee_file = undefined; then \echo WARNING: Could not determine tee to use; \fi; \ fi;
- 作用:根据
board_name
和board_rev
确定 TEE 镜像文件(如 OP-TEE),并存储到tee_file
变量中。 - 具体在这里,由于环境变量
board_name
的值为board_name=EVK
,环境变量board_rev
的值为14X14
,所以环境变量tee_file
的值被设为uTee-6ullevk
,
3. 选择 eMMC 设备
mmc dev ${mmcdev};
mmc dev ${mmcdev};
mmcdev=1
,表示默认使用mmc1
(可能是 eMMC)。可以用命令mmc list
查看eMMC设备在u-boot中的编号,详情见我的另一篇博文 https://blog.csdn.net/wenhao_ir/article/details/146016551 【搜索“利用MMC子系统查看和修改”】- 这里调用
mmc dev
两次,可能是为了确保设备切换正确。 - 关于这个命令的详情,见我的另一篇博文 https://blog.csdn.net/wenhao_ir/article/details/146016551
4. 重新扫描 MMC 设备
if mmc rescan; then
- 作用:检查 eMMC/SD 设备是否可用,并更新分区信息。
- 如果成功(即 MMC 设备存在),则继续执行下面的加载步骤。
- 如果失败(即 MMC 设备不可用),则跳过直接进入
netboot
。
5. 尝试加载 U-Boot 启动脚本(boot.scr)
if run loadbootscript; then run bootscript;
loadbootscript
变量的内容:loadbootscript=fatload mmc ${mmcdev}:${mmcpart} ${loadaddr} ${script};
- 尝试从 MMC 设备 加载
boot.scr
文件 到loadaddr
(0x80800000)。 - 在这里,由于
mmcdev
的值为1
,mmcpart
的值为1
,loadaddr
的值为0x80800000
,script
的内容为boot.scr
,所以这里实际上尝试把存储在eMMC的boot1(Boot partition 1)中的文件boot.scr
加载到内存的0x80800000
位置。同时由于命令是fatload
,所以boot1(Boot partition 1)中的文件系统的格式为FAT。如果加载成功,再执行环境变量bootscript
中的内容。
- 尝试从 MMC 设备 加载
bootscript
变量的内容如下:bootscript=echo Running bootscript from mmc ...; source
echo Running bootscript from mmc ...;
:这部分只是打印信息,告诉用户 “正在从 MMC 运行 bootscript…”。source
:这个命令的作用是 执行存储在 RAM 中的boot.scr
脚本。u-boot的source 命令会解析 loadaddr 处的 boot.scr 并执行其中的 U-Boot 命令。boot.scr
是一个预编译的 U-Boot 脚本,里面可能会定义更详细的启动过程,如:加载内核、加载设备树、设定 bootargs、执行bootz
或bootm
等。
- 具体在这里,由于我们并不会向boot1(Boot partition 1)中写入文件
boot.scr
,即文件boot.scr
并不存在,所以并不会执行这个分支。
6. 如果 boot.scr
不存在,则直接加载 Linux 内核
else if run loadimage; then run mmcboot;
加载内核镜像zImage到内存
loadimage
变量的内容:loadimage=fatload mmc ${mmcdev}:${mmcpart} ${loadaddr} ${image}
- 尝试从 MMC 设备加载
zImage
到loadaddr
(0x80800000)。 - 在这里,由于
mmcdev
的值为1
,mmcpart
的值为1
,loadaddr
的值为0x80800000
,image
的内容为zImage
,所以这里实际上尝试把存储在eMMC的boot1(Boot partition 1)中的内核镜像文件zImage
加载到内存的0x80800000
位置。同时由于命令是fatload
,所以boot1(Boot partition 1)中的文件系统的格式为FAT。如果加载成功,再执行环境变量mmcboot
中的内容。
- 尝试从 MMC 设备加载
mmcboot
变量的内容:mmcboot=echo Booting from mmc ...; run mmcargs; \if test ${tee} = yes; then \run loadfdt; run loadtee; \bootm ${tee_addr} - ${fdt_addr}; \else \if test ${boot_fdt} = yes || test ${boot_fdt} = try; then \if run loadfdt; then \bootz ${loadaddr} - ${fdt_addr}; \else \if test ${boot_fdt} = try; then bootz; else echo WARN: Cannot load the DT; fi; \fi; \else \bootz; \fi; \fi;
首先打印输出信息Booting from mmc ...
传递给内核的环境变量bootargs的设置(根文件系统的挂载)
然后执行环境变量mmcargs
中的内容,环境变量mmcargs
的内容为:
mmcargs=setenv bootargs console=${console},${baudrate} root=${mmcroot}
可见在这里设置了u-boot传递给内核的环境变量bootargs
的值:
①console
的值为ttymxc0
,所以内核启动后选择的当前终端为设备节点名为ttymxc0
的终端,这实际上就是IMX6ULL的串口形成的终端。又由于baudrate
的值为115200
,所以这个终端所使用的串口的波特率被设置为115200
。
②mmcroot
的值为mmcroot=/dev/mmcblk1p2 rootwait rw
,它的作用是指定 根文件系统(rootfs)的位置和挂载选项,具体解释如下:
字段解析
参数 | 作用 |
---|---|
/dev/mmcblk1p2 | 指定根文件系统所在的 设备节点,即 mmcblk1 (eMMC/SD 设备 1)的 第 2 分区(用户数据区的逻辑分区的第2个分区)。 |
rootwait | 等待根文件系统设备准备好,用于 eMMC/SD 设备启动时可能的延迟问题。 |
rw | 以读写模式(read-write)挂载根文件系统。 |
/dev/mmcblk1p2
(根文件系统路径)/dev/mmcblk1p2
代表 eMMC 或 SD 设备mmcblk1
的 用户数据区的逻辑分区的第2个分区(p2
)。- 设备名称:
mmcblk0
→ 这是Linux系统启动后第一个 eMMC/SD 设备的名字。mmcblk1
→ 这是Linux系统启动后第二个 eMMC/SD 设备的名字。
- 分区编号:
mmcblk1p1
→用户数据区的逻辑分区的第1个分区,注意不是eMMC设备的Boot partition 1分区。mmcblk1p2
→ 用户数据区的逻辑分区的第2个分区,注意不是eMMC设备的Boot partition 2分区。mmcblk1boot0
-eMMC设备的Boot partition 1分区。mmcblk1boot1
-eMMC设备的Boot partition 2分区。
关于上面这些设备名称和分区编号名是如何获取的,即eMMC各分区在Linux内核中的名字,请参看我的另一篇博文 https://blog.csdn.net/wenhao_ir/article/details/145967306 【搜索“可以使用以下命令查看eMMC的分区信息”】
-
rootwait
(等待设备就绪)- eMMC/SD 设备在 U-Boot 交权给 Linux 内核后,可能需要时间初始化。
rootwait
让内核 无限等待根文件系统设备出现(而不是直接报错)。
-
rw
(读写挂载根文件系统)rw
让 Linux 以读写模式 挂载根文件系统- 如果使用
ro
(read-only),根文件系统会以只读模式挂载,适用于只读系统(如某些嵌入式设备)。
-
注:默认情况下,Linux启动后
init
进程可能会重新挂载根文件系统,原因见博文 https://blog.csdn.net/wenhao_ir/article/details/146067376
传递给内核的环境变量bootargs设置完后,即run mmcargs
执行完后,再执行后面的代码加载设备树文件。
设置
mmcboot=echo Booting from mmc ...; run mmcargs; \if test ${tee} = yes; then \run loadfdt; run loadtee; \bootm ${tee_addr} - ${fdt_addr}; \else \if test ${boot_fdt} = yes || test ${boot_fdt} = try; then \if run loadfdt; then \bootz ${loadaddr} - ${fdt_addr}; \else \if test ${boot_fdt} = try; then bootz; else echo WARN: Cannot load the DT; fi; \fi; \else \bootz; \fi; \fi;
- 如果
tee=yes
,则先加载 TEE 并使用bootm
命令引导。由于这里tee
的值为no
,所以不会执行这个分支,所以本博文中不去仔细分析这个分支。 - 读下面的内容前,前先阅读博文 https://blog.csdn.net/wenhao_ir/article/details/146068588 了解命令
bootz
的详细情况。 - 否则,尝试加载设备树(DTB)并使用命令
bootz
启动。 - 如果 DTB 加载失败(但
boot_fdt=try
),则直接bootz
继续引导内核。注意:命令bootz
不加参数时,默认从环境变量loadaddr
中去获取内核在内存中的地址。 - 如果
boot_fdt
的值既不为yes也不为try,则不加载设备树,直接用命令bootz
启动内核。注意:命令bootz
不加参数时,默认从环境变量loadaddr
中去获取内核在内存中的地址。
具体到这里来说,由于boot_fdt
的值为try
,所以执行下面红框中的分支:
语句run loadfdt;
表示加载设备树文件到内存中,环境变量loadfdt
的内容如下:
loadfdt=fatload mmc ${mmcdev}:${mmcpart} ${fdt_addr} ${fdt_file}
在这里,由于mmcdev
的值为1
,mmcpart
的值为1
,fdt_addrr
的值为0x83000000
, fdt_file
的内容之前已经通过运行命令run findfdt
设置为了imx6ull-14x14-evk.dtb
,所以这里实际上尝试把存储在eMMC的boot1(Boot partition 1)中的设备树文件imx6ull-14x14-evk.dtb
加载到内存的为0x83000000
的位置。同时由于命令是fatload
,所以boot1(Boot partition 1)中的文件系统的格式为FAT。
这里:我们可以顺便算下内核镜像和设备树文件在内存中相隔多远:
0x83000000-0x80800000=0x2800000=Dec2800000B=2800000KB=40MB,即二者在内存中相隔了40MB。
如果设置树文件加载成功,再执行下面这条命令:
bootz ${loadaddr} - ${fdt_addr};
关于bootz命令的详解,见博文 https://blog.csdn.net/wenhao_ir/article/details/146068588
这样 bootz
会按下面的参数启动Linux内核:
- 从内存的
loadaddr(0x80800000)
位置加载zImage
。 -
表示 不使用bootz命令的第2个参数 ramdisk。- 从内存的
fdt_addr(0x83000000)
加载内核需要的设备树(DTB)文件。
7. 如果 MMC 设备启动失败,则进行网络启动
else run netboot;
fi; fi; else run netboot; fi
netboot
变量的内容:netboot=echo Booting from net ...; ${usb_net_cmd}; run netargs; \if test ${ip_dyn} = yes; then \setenv get_cmd dhcp; \else \setenv get_cmd tftp; \fi; \${get_cmd} ${image}; \if test ${tee} = yes; then \${get_cmd} ${tee_addr} ${tee_file}; \${get_cmd} ${fdt_addr} ${fdt_file}; \bootm ${tee_addr} - ${fdt_addr}; \else \if test ${boot_fdt} = yes || test ${boot_fdt} = try; then \if ${get_cmd} ${fdt_addr} ${fdt_file}; then \bootz ${loadaddr} - ${fdt_addr}; \else \if test ${boot_fdt} = try; then bootz; else echo WARN: Cannot load the DT; fi; \fi; \else \bootz; \fi; \fi;
- 尝试从网络(TFTP/NFS)加载内核和设备树,并执行启动。
- 关于上面这些从网络启动的命令的详细解释,见我的另一篇博文 https://blog.csdn.net/wenhao_ir/article/details/145902134
8.小结
- 优先使用 MMC 启动
- 如果
boot.scr
存在,则执行boot.scr
- 如果
boot.scr
不存在,则直接加载zImage
并启动 - 如果 MMC 启动失败,则使用网络启动
对我们有重要作用的信息汇总
关于内核镜像文件的说明
①内核镜像文件存储在eMMC的boot1(Boot partition 1)中,其文件名为zImage
,加载到内存中的位置为:0x80800000
位置,要求boot1(Boot partition 1)的文件系统为FAT。
关于设备树文件的说明
②设置树文件存储在eMMC的boot1(Boot partition 1)中,其文件名为imx6ull-14x14-evk.dtb
,加载到内存中的位置为:0x83000000
位置,要求boot1(Boot partition 1)的文件系统为FAT。
关于根文件系统挂载的说明
③根文件系统的挂载位置在设备节点/dev/mmcblk1p2
,mmcblk1p2
的1代表eMMC设备的编号为1,p2代表用户数据区的第2个逻辑分区。由于根文件系统的挂载是在内核启动后进行的,所以挂载位置的设备节点的名字是Linux系统中的名字。