#頭條創作挑戰賽#
文章目錄
- 2.1、程式執行流程圖
- 2.2、u-boot.lds——Uboot的入口函數
- 2.3、board_init_f——闆級前置初始化
- 2.4、relocate_code重定向
- 2.4.1 為什麼需要重定向呢?
- 2.4.2 Uboot是如何重定向的?
- 2.4.3 Uboot重定向作用
- 2.5、board_init_r——闆級後置初始化
- 2.6、main_loop——Uboot主循環
- 2.6.1 bootdelay_process
- 2.6.2 autoboot_command
- 2.6.3 cli_loop
- 2.7 參考文章:
上一篇文章:(一)uboot基礎了解 下一篇文章:(三)Uboot驅動模型
同大多數的Bootloader一樣,uboot的啟動過程也分為BL1、BL2兩個階段,分别對應着SPL和Uboot。
SPL(BL1階段):負責開發闆的基礎配置和裝置初始化,并且搬運Uboot到記憶體中,由彙編代碼和少量的C語言實作
Uboot(BL2階段):主要負責初始化外部裝置,引導Kernel啟動,由純C語言實作。
我們這篇文章,主要介紹Uboot(BL2階段)的啟動流程,BL1階段啟動流程的詳細分析,可以見我的後續文章。想要深入了解的,可以好好研究下!
2.1、程式執行流程圖
我們先總體來看一下Uboot的執行步驟,這裡以EMMC作為啟動媒體,進行分析!
無論是哪種啟動媒體,基本流程都相似,我們這就往下看!
打開圖檔,結合文檔、圖檔、代碼進行了解!
2.2、u-boot.lds——Uboot的入口函數
u-boot.lds:是uboot工程的連結腳本檔案,對于工程的編譯和連結有非常重要的作用,決定了uboot的組裝,并且u-boot.lds連結檔案中的ENTRY(_start)指定了uboot程式的入口位址。
如果不知道u-boot.lds放到在哪裡,可以通過find -name u-boot.lds查找,根目錄要進入到uboot的源碼的位置哦!
如果查找結果有很多,結合自己的闆子資訊,确定自己使用的u-boot.lds。
當然,準确的方法是檢視Makefile檔案,分析出來u-boot.lds所生成的位置。
在u-boot.lds的檔案中,可以看到.text段,存放的就是執行的文本段。截取部分代碼段如下:
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = 0x00000000; @起始位址
. = ALIGN(4); @四位元組對齊
.text :
{
*(.__image_copy_start) @映像檔案複制起始位址
*(.vectors) @異常向量表
arch/arm/cpu/armv7/start.o (.text*) @啟動函數
}
......
}
- ENTRY(_start):程式的入口函數,_start在arch/arm/lib/vectors.S中定義.globl _start
- SECTIONS定義了段,包括text文本段、data資料段、bss段等。
- __image_copy_start在System.map和u-boot.map中均有定義
- arch/arm/cpu/armv7/start.o對應檔案arch/arm/cpu/armv7/start.S,該檔案中定義了main函數的入口。
Tip:上面隻進行大概分析,有彙編經驗的朋友,可以詳細進行分析!
2.3、board_init_f——闆級前置初始化
跟随上文的程式執行流程圖,我們看board_init_f這個函數。其位于common/board_f.c。
void board_init_f(ulong boot_flags)
{
gd->flags = boot_flags;
gd->have_console = 0;
if (initcall_run_list(init_sequence_f))
hang();
}
static const init_fnc_t init_sequence_f[] = {
setup_mon_len,
...
log_init,
arch_cpu_init, /* basic arch cpu dependent setup */
env_init, /* initialize environment */
...
reloc_fdt,
reloc_bootstage,
reloc_bloblist,
setup_reloc,
...
}
board_init_f(),其最核心的内容就是調用了init_sequence_f初始化序列,進行了一系列初始化的工作。
主要包括:序列槽、定時器、裝置樹、DM驅動模型等,另外還包括global_data結構體相關對象的變量。
詳細分析,可以看文末的參考文章[1]
我們需要注意的一點就是,在初始化隊列末尾,執行了幾個reloc_xxx的函數,這幾個函數實作了Uboot的重定向功能。
2.4、relocate_code重定向
重定向技術,可以說也算是Uboot的一個重點了,也就是将uboot自身鏡像拷貝到ddr上的另外一個位置的動作。
2.4.1 為什麼需要重定向呢?
一般需要重定向的條件如下:
- uboot存儲在隻讀存儲器上,比如ROM、Nor flash,需要将代碼拷貝到DDR上,才能完整運作Uboot。
- 為Kernel騰空間,Kernel一般會放在DDR的地段位址上,是以要把Uboot重定向到頂端位址,避免沖突。
2.4.2 Uboot是如何重定向的?
Uboot的重定向有如下幾個步驟:
- 對relocate進行空間劃分
- 計算uboot代碼空間到relocate的位置的偏移
- relocate舊的global_data到新的global_data空間上
- relocate Uboot
- 修改relocate後的全局變量的label
- relocate中斷向量表
運作大緻流程:
arch/arm/lib/crt0.S檔案内,主要實作了:
ENTRY(_main)
bl board_init_f
@@ 在board_init_f裡面實作了
@@ (1)對relocate進行空間規劃
@@ (2)計算uboot代碼空間到relocation的位置的偏移
@@ (3)relocate舊的global_data到新的global_data的空間上
ldr sp, [r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp */
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
ldr r9, [r9, #GD_BD] /* r9 = gd->bd */
sub r9, r9, #GD_SIZE /* new GD is below bd */
@@ 把新的global_data位址放在r9寄存器中
adr lr, here
ldr r0, [r9, #GD_RELOC_OFF] /* r0 = gd->reloc_off */
add lr, lr, r0
@@ 計算傳回位址在新的uboot空間中的位址。b調用函數傳回之後,就跳到了新的uboot代碼空間中。
ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
@@ 把uboot的新的位址空間放到r0寄存器中,作為relocate_code的參數
b relocate_code
@@ 跳轉到relocate_code中,在這裡面實作了
@@ (1)relocate舊的uboot代碼空間到新的空間上去
@@ (2)修改relocate之後全局變量的label
@@ 注意,由于上述已經把lr寄存器重定義到uboot新的代碼空間中了,是以傳回之後,就已經跳到了新的代碼空間了!!!!!!
bl relocate_vectors
@@ relocate中斷向量表
- setup_reloc——重定向位址檢視(仿真有關)
在這裡我們說明一下board_init_f裡面的setup_reloc初始化函數
static int setup_reloc(void)
{
if (gd->flags & GD_FLG_SKIP_RELOC) {
debug("Skipping relocation due to flag\n");
return 0;
}
#ifdef CONFIG_SYS_TEXT_BASE
#ifdef ARM
gd->reloc_off = gd->relocaddr - (unsigned long)__image_copy_start;
#elif defined(CONFIG_M68K)
/*
* On all ColdFire arch cpu, monitor code starts always
* just after the default vector table location, so at 0x400
*/
gd->reloc_off = gd->relocaddr - (CONFIG_SYS_TEXT_BASE + 0x400);
#elif !defined(CONFIG_SANDBOX)
gd->reloc_off = gd->relocaddr - CONFIG_SYS_TEXT_BASE;
#endif
#endif
memcpy(gd->new_gd, (char *)gd, sizeof(gd_t));
debug("Relocation Offset is: %08lx\n", gd->reloc_off);
if (is_debug_open()) {
printf("Relocating to %08lx, new gd at %08lx, sp at %08lx\n",
gd->relocaddr, (ulong)map_to_sysmem(gd->new_gd),
gd->start_addr_sp);
}
return 0;
}
由于,Uboot進行了重定向,是以按照正常的位址仿真的話,我們可能通路到錯誤的記憶體空間,通過setup_reloc的Relocating to %08lx列印,我們可以得到重定向後的位址,友善我們仿真。
Uboot的重定向也有相當大的一部分知識點,上面也僅僅是簡單介紹了relocate的基本步驟和流程,後續看大家需要,如果大家想了解,我再補上這一部分。
2.4.3 Uboot重定向作用
總之,Uboot重定向之後,把Uboot整體搬運到了高端記憶體區,為Kernel的加載提供空間,避免記憶體踐踏。
2.5、board_init_r——闆級後置初始化
我們接着跟着流程圖往下看,重定向之後,Uboot運作于新的位址空間,接着我們執行board_init_r,主要作為Uboot運作的最後初始化步驟。
board_init_r這個函數,同樣位于common/board_f.c,主要用于初始化各類外設資訊
void board_init_r(gd_t *new_gd, ulong dest_addr)
{
if (initcall_run_list(init_sequence_r))
hang();
/* NOTREACHED - run_main_loop() does not return */
hang();
}
static init_fnc_t init_sequence_r[] = {
initr_reloc,
initr_reloc_global_data,
board_init, /* Setup chipselects */
initr_dm,
initr_mmc,
...
run_main_loop
}
與board_init_f相同,同樣有一個init_sequence_r初始化清單,包括:initr_dmDM模型初始化,initr_mmcMMC驅動初始化,等等。
最終,uboot就運作到了run_main_loop,進而執行main_loop這個函數。
2.6、main_loop——Uboot主循環
該函數為Uboot的最終執行函數,無論是加載kernel還是uboot的指令行體系,均由此實作。
void main_loop(void)
{
const char *s;
bootstage_mark_name(BOOTSTAGE_ID_MAIN_LOOP, "main_loop");
if (IS_ENABLED(CONFIG_VERSION_VARIABLE))
env_set("ver", version_string); /* set version variable */
cli_init();
if (IS_ENABLED(CONFIG_USE_PREBOOT))
run_preboot_environment_command();
if (IS_ENABLED(CONFIG_UPDATE_TFTP))
update_tftp(0UL, NULL, NULL);
s = bootdelay_process();
if (cli_process_fdt(&s))
cli_secure_boot_cmd(s);
autoboot_command(s);
cli_loop();
panic("No CLI available");
}
env_set:設定環境變量,兩個參數分别為name和value
cli_init:用于初始化hash shell的一些變量
run_preboot_environment_command:執行預定義的環境變量的指令
bootdelay_process:加載延時處理,一般用于Uboot啟動後,有幾秒的倒計時,用于進入指令行模式。
cli_loop:指令行模式,主要作用于Uboot的指令行互動。
2.6.1 bootdelay_process
記得對照文章開始的執行流程圖哦!
詳細解釋标注于代碼中…
const char *bootdelay_process(void)
{
char *s;
int bootdelay;
bootcount_inc();
s = env_get("bootdelay"); //先判斷是否有bootdelay環境變量,如果沒有,就使用menuconfig中配置的CONFIG_BOOTDELAY時間
bootdelay = s ? (int)simple_strtol(s, NULL, 10) : CONFIG_BOOTDELAY;
if (IS_ENABLED(CONFIG_OF_CONTROL)) //是否使用裝置樹進行配置
bootdelay = fdtdec_get_config_int(gd->fdt_blob, "bootdelay",
bootdelay);
debug("### main_loop entered: bootdelay=%d\n\n", bootdelay);
if (IS_ENABLED(CONFIG_AUTOBOOT_MENU_SHOW))
bootdelay = menu_show(bootdelay);
bootretry_init_cmd_timeout();
#ifdef CONFIG_POST
if (gd->flags & GD_FLG_POSTFAIL) {
s = env_get("failbootcmd");
} else
#endif /* CONFIG_POST */
if (bootcount_error())
s = env_get("altbootcmd");
else
s = env_get("bootcmd"); //擷取bootcmd環境變量,用于後續的指令執行
if (IS_ENABLED(CONFIG_OF_CONTROL))
process_fdt_options(gd->fdt_blob);
stored_bootdelay = bootdelay;
return s;
}
2.6.2 autoboot_command
詳細解釋标注于代碼中…
void autoboot_command(const char *s)
{
debug("### main_loop: bootcmd=\"%s\"\n", s ? s : "<UNDEFINED>");
if (stored_bootdelay != -1 && s && !abortboot(stored_bootdelay)) {
bool lock;
int prev;
lock = IS_ENABLED(CONFIG_AUTOBOOT_KEYED) &&
!IS_ENABLED(CONFIG_AUTOBOOT_KEYED_CTRLC);
if (lock)
prev = disable_ctrlc(1); /* disable Ctrl-C checking */
run_command_list(s, -1, 0);
if (lock)
disable_ctrlc(prev); /* restore Ctrl-C checking */
}
if (IS_ENABLED(CONFIG_USE_AUTOBOOT_MENUKEY) &&
menukey == AUTOBOOT_MENUKEY) {
s = env_get("menucmd");
if (s)
run_command_list(s, -1, 0);
}
}
我們看一下判斷條件stored_bootdelay != -1 && s && !abortboot(stored_bootdelay
- stored_bootdelay:為環境變量的值,或者menuconfig設定的值
- s:為環境變量bootcmd的值,為後續運作的指令
- abortboot(stored_bootdelay):主要用于判斷是否有按鍵按下。如果按下,則不執行bootcmd指令,進入cli_loop 指令行模式;如果不按下,則執行bootcmd指令,跳轉到加載Linux啟動。
2.6.3 cli_loop
void cli_loop(void){ bootstage_mark(BOOTSTAGE_ID_ENTER_CLI_LOOP);#ifdef CONFIG_HUSH_PARSER parse_file_outer(); /* This point is never reached */ for (;;); //死循環#elif defined(CONFIG_CMDLINE) cli_simple_loop();#else printf("## U-Boot command line is disabled. Please enable CONFIG_CMDLINE\n");#endif /*CONFIG_HUSH_PARSER*/}
如上代碼,程式隻執行parse_file_outer來處理使用者的輸入、輸出資訊。
好啦,基本到這裡,我們已經對Uboot的啟動流程了然于胸了吧!
當然,更深層次的不建議去深入了解,有時間可以慢慢去研究。
大家有疑問,可以評論區交流…
2.7 參考文章:
更多文章,可以關注我的公~号:【嵌入式藝術】哦,一起讨論嵌入式技術!