uboot 的啟動流程在網上有很多大佬記錄,但是了對於像我這樣的新手就有些困難了,而我也不做 uboot 相關的工作,所以沒必去研究程式碼,這裡我特意整理了一下,以流程圖的形式展現程式碼執行的流程,方便快速瞭解 uboot 是怎麼啟動的,此筆記就不進行程式碼分析了,主要記錄 uboot 啟動流程中所執行的函數已經函數所在的檔案,需要了解函數中的程式碼實現,可以結合 uboot 原始碼和正點原子的開發手冊或者其他部落格。
注意: uboot 執行過程中都是以單執行緒執行的,所以分析啟動流程的時候相對多執行緒好理解。流程中有些函數名和檔案位置可能不一樣,但是不要慌,就這樣慢慢的找下去就可以快速瞭解到自己的工程是怎麼啟動的了
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 的入口是 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 函數
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 工作的基本條件,並初始一些外設,程式碼很多,初次學習最好不要直接對函數進行具體的分析,先了解框架。
_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 表中的函數,主要有兩個工作
其中 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() 函數
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 命令。
注意:這裡我就沒有畫對應的流程圖了,因為在正點原子的教材中有相應的流程圖,所以我這裡就直接參照了。關於啟動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