為什么我們要解析環境變量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系統中的名字。