uboot启动流程详细分析(基于i.m6ull)
uboot介紹
uboot就是一段引導(dǎo)程序,在加載系統(tǒng)內(nèi)核之前,完成硬件初始化,內(nèi)存映射,為后續(xù)內(nèi)核的引導(dǎo)提供一個(gè)良好的環(huán)境。uboot是bootloader的一種,全稱為universal boot loader。
一、uboot的makefile
1.1 makefile整體解析過(guò)程
為了生成u-boot.bin這個(gè)文件,首先要生成構(gòu)成u-boot.bin的各個(gè)庫(kù)文件、目標(biāo)文件。為了各個(gè)庫(kù)文件、目標(biāo)文件就必須進(jìn)入各個(gè)子目錄執(zhí)行其中的Makefile。由此,確定了整個(gè)編譯的命令和順序。
1.2 makefile整體編譯過(guò)程
- 首先,根據(jù)各個(gè)庫(kù)文件、目標(biāo)文件出現(xiàn)的先后順序,依次進(jìn)入各個(gè)子目錄編譯從而生成這些目標(biāo)
- 然后,回到頂層目錄,繼續(xù)執(zhí)行頂層Makefile的總目標(biāo),最后生成u-boot.bin。
uboot的編譯分為兩步:配置、編譯。
(1)第一步:配置,執(zhí)行make pangu_basic_defconfig進(jìn)行配置,生成.config文件
(2)第二步:編譯,執(zhí)行make進(jìn)行編譯,生成u-boot.bin。
二、uboot啟動(dòng)流程
- uboot分為 uboot-spl 和 uboot 兩個(gè)組成部分。
uboot啟動(dòng)分三個(gè)階段
BL0
ROM上的固化程序(Boot Rom)
BL1(u-boot-spl)
- 初始化部分時(shí)鐘(和SDRAM相關(guān))
- 初始化DDR(外部SDRAM)
- 從存儲(chǔ)介質(zhì)上(比如SD\eMMC\nand flash)將BL2鏡像加載到SDRAM上
- 驗(yàn)證BL2鏡像的合法性
- 跳轉(zhuǎn)到BL2鏡像所在的地址上
- 初始化部分硬件,包括時(shí)鐘、內(nèi)存等等
- 加載內(nèi)核到內(nèi)存上
- 加載文件系統(tǒng)、atags或者dtb到內(nèi)存上
- 根據(jù)操作系統(tǒng)啟動(dòng)要求正確配置好一些硬件
啟動(dòng)操作系統(tǒng)
2.1 uboot的鏈接文件(u-boot.lds)
鏈接文件的作用
指定代碼段和數(shù)據(jù)段、只讀數(shù)據(jù)段在內(nèi)存中的存放地址;(地址具體為i.m6ull , 其他芯片可能不是 0X87800000)
-
u-boot.map 是 uboot 的映射文件,看到某個(gè)文件或者函數(shù)鏈接到了哪個(gè)地址,
-
__image_copy_start 為 0X87800000,而.text 的起始地址也是0X87800000。
-
vectors 段保存中斷向量表,vectors 段的起始地址也是 0X87800000,說(shuō)明整個(gè) uboot 的起始地址就是 0X87800000,
-
這也是為什么我們裸機(jī)例程的鏈接起始地址選擇 0X87800000 了,目的就是為了和 uboot 一致。
指定代碼的入口地址;
- 連接文件中找到程序的入口點(diǎn):_start, 其中_start 在文件 arch/arm/lib/vectors.S 。
2.2 uboot啟動(dòng)流程
第一階段(uboot-spl , 入口是上述lds文件中分析的_start)
- SPL是Secondary Program Loader的簡(jiǎn)稱,第二階段程序加載器,這里所謂的第二階段是相對(duì)于SOC中的Boot ROM來(lái)說(shuō)的
Boot ROM會(huì)通過(guò)檢測(cè)啟動(dòng)方式來(lái)加載第二階段bootloader。uboot已經(jīng)是一個(gè)bootloader了,那么為什么還多一個(gè)uboot spl呢?
-
這個(gè)主要原因是對(duì)于一些SOC來(lái)說(shuō),它的內(nèi)部SRAM可能會(huì)比較小,小到無(wú)法裝載下一個(gè)完整的uboot鏡像,那么就需要spl,它主要負(fù)責(zé)初始化外部RAM和環(huán)境,并加載真正的uboot鏡像到外部RAM(DDR)中來(lái)執(zhí)行。
-
所以由此來(lái)看,SPL應(yīng)該是一個(gè)非常小的loader程序,可以運(yùn)行于SOC的內(nèi)部SRAM中,它的主要功能就是加載真正的uboot并運(yùn)行之。
2.2.1. 進(jìn)入_start函數(shù):
_start:#ifdef CONFIG_SYS_DV_NOR_BOOT_CFG.word CONFIG_SYS_DV_NOR_BOOT_CFG #endifb resetldr pc, _undefined_instructionldr pc, _software_interruptldr pc, _prefetch_abortldr pc, _data_abortldr pc, _not_usedldr pc, _irqldr pc, _fiq有一條跳轉(zhuǎn)指令b reset跳轉(zhuǎn)到reset函數(shù)處去執(zhí)行
注意,spl的流程在reset中就應(yīng)該被結(jié)束,也就是說(shuō)在reset中,就應(yīng)該轉(zhuǎn)到到BL2,也就是uboot中了。
2.2.2. 進(jìn)入reset函數(shù)
reset:/* Allow the board to save important registers */b save_boot_params @進(jìn)入reset第一步跳轉(zhuǎn)到save_boot_params save_boot_params_ret: @ save_boot_params 內(nèi)部通過(guò)cpsr 設(shè)置cpu為SVC模式,關(guān)閉FIQ和IRQ中斷/** disable interrupts (FIQ and IRQ), also set the cpu to SVC32 mode,* except if in HYP mode already*/mrs r0, cpsrand r1, r0, #0x1f @ mask mode bitsteq r1, #0x1a @ test for HYP modebicne r0, r0, #0x1f @ clear all mode bitsorrne r0, r0, #0x13 @ set SVC modeorr r0, r0, #0xc0 @ disable FIQ and IRQmsr cpsr,r0/* the mask ROM code should have PLL and others stable */ #ifndef CONFIG_SKIP_LOWLEVEL_INITbl cpu_init_cp15 @ 跳轉(zhuǎn)到cpu_init_cp15 ,初始化協(xié)處理器CP15,從而禁用MMU和TLB。bl cpu_init_crit @ 跳轉(zhuǎn)到cpu_init_crit ,進(jìn)行一些關(guān)鍵的初始化動(dòng)作,也就是平臺(tái)級(jí)和板級(jí)的初始化 #endifbl _main @ 跳轉(zhuǎn)到_main ,加載BL2以及跳轉(zhuǎn)到BL2的主體部分進(jìn)入reset函數(shù),首先設(shè)置cpu為SVC模式,關(guān)閉中斷。然后跳轉(zhuǎn)到cpu_init_cp15 ,初始化協(xié)處理器CP15,從而禁用MMU和TLB。跳轉(zhuǎn)到cpu_init_crit ,進(jìn)行一些關(guān)鍵的初始化動(dòng)作,也就是平臺(tái)級(jí)和板級(jí)的初始化。最后跳轉(zhuǎn)到**_main**,加載BL2以及跳轉(zhuǎn)到BL2的主體部分
- 關(guān)閉中斷。
uboot引導(dǎo)linux起到的過(guò)程中本身就是一個(gè)完成的過(guò)程,不需要中斷機(jī)制。
2.2.3 cpu_init_cp15函數(shù)
ENTRY(cpu_init_cp15)/** Invalidate L1 I/D*/mov r0, #0 @ set up for MCRmcr p15, 0, r0, c8, c7, 0 @ invalidate TLBsmcr p15, 0, r0, c7, c5, 0 @ invalidate icachemcr p15, 0, r0, c7, c5, 6 @ invalidate BP arraymcr p15, 0, r0, c7, c10, 4 @ DSBmcr p15, 0, r0, c7, c5, 4 @ ISB @@ 這里只需要知道是對(duì)CP15處理器的部分寄存器清零即可。 @@ 將協(xié)處理器的c7\c8清零等等,各個(gè)寄存器的含義請(qǐng)參考《ARM的CP15協(xié)處理器的寄存器》/** disable MMU stuff and caches*/mrc p15, 0, r0, c1, c0, 0bic r0, r0, #0x00002000 @ clear bits 13 (--V-)bic r0, r0, #0x00000007 @ clear bits 2:0 (-CAM)orr r0, r0, #0x00000002 @ set bit 1 (--A-) Alignorr r0, r0, #0x00000800 @ set bit 11 (Z---) BTB #ifdef CONFIG_SYS_ICACHE_OFFbic r0, r0, #0x00001000 @ clear bit 12 (I) I-cache #elseorr r0, r0, #0x00001000 @ set bit 12 (I) I-cache #endifmcr p15, 0, r0, c1, c0, 0 @@ 通過(guò)上述的文章的介紹,我們可以知道cp15的c1寄存器就是MMU控制器 @@ 上述對(duì)MMU的一些位進(jìn)行清零和置位,達(dá)到關(guān)閉MMU和cache的目的,具體的話去看一下上述文章吧。ENDPROC(cpu_init_cp15)cpu_init_cp15 用來(lái)設(shè)置 CP15 相關(guān)的內(nèi)容,完成啟動(dòng)ICACHE,關(guān)閉DCACHE,關(guān)閉MMU和TLB 。
- 關(guān)閉MMU
,MMU是用于虛擬地址向物理地址進(jìn)行映射的一個(gè)結(jié)構(gòu)。在 uboot階段操作的就直接是 物理地址,所以不需要轉(zhuǎn)換。 - 啟動(dòng)ICACHE(指令),關(guān)閉DCACHE(數(shù)據(jù))
啟動(dòng)指令CACHE課可以加快指令讀取的速度,但是數(shù)據(jù)CACHE 必須 要關(guān)閉,因?yàn)樗旧硎且粋€(gè)CPU的二級(jí)緩存,在運(yùn)行程序的時(shí)候可能會(huì)往里面去取數(shù)據(jù),但是此時(shí)ram里面的數(shù)據(jù)可能并沒(méi)有存入到里面,這就可能導(dǎo)致讀取到錯(cuò)誤的數(shù)據(jù)。
2.2.4 cpu_init_crit函數(shù)(包含lowlevel_init函數(shù))
ENTRY(cpu_init_crit)/** Jump to board specific initialization...* The Mask ROM will have already initialized* basic memory. Go here to bump up clock rate and handle* wake up conditions.*/b lowlevel_init @ go setup pll,mux,memory ENDPROC(cpu_init_crit)cpu_init_crit函數(shù)的內(nèi)容是跳轉(zhuǎn)到lowlevel_init函數(shù)。
2.2.5. lowlevel_init 函數(shù)(平臺(tái)級(jí)和板級(jí)的初始化)
lowlevel_init主要完成平臺(tái)級(jí)和板級(jí)的初始化
在lowlevel_init中,我們要實(shí)現(xiàn)如下:
- 檢查一些復(fù)位狀態(tài)
- 關(guān)閉看門(mén)狗
- 系統(tǒng)時(shí)鐘的初始化
- 內(nèi)存、DDR的初始化
- 串口初始化(可選)
- Nand flash的初始化
在im6ull中
此時(shí)初始化SP指向 內(nèi)存空間為IRAM(內(nèi)部ram ,OCRAM 128K ,0x00900000),(初始化內(nèi)存空間,為第二階段準(zhǔn)備ram)
2.2.6. _main函數(shù)
** _main函數(shù)的主要工作是 **
- 設(shè)置c語(yǔ)言的運(yùn)行環(huán)境
- 設(shè)置sp和gd的中間環(huán)境
- 重定義代碼
- 設(shè)置最終的環(huán)境
-
因?yàn)楹竺媸荂語(yǔ)言環(huán)境,首先是設(shè)置堆棧
-
初始化gd(下圖中g(shù)lobal date,內(nèi)部ram) , 進(jìn)行清零(同上內(nèi)部ram)
-
調(diào)用 board_init_f 函數(shù)(將SP指針從內(nèi)部IRAM,轉(zhuǎn)移到外部DDR),主要用來(lái)初始化 DDR,定時(shí)器,完成代碼拷貝等等
-
調(diào)用函數(shù) relocate_code,也就是代碼重定位函數(shù),此函數(shù)負(fù)責(zé)將 uboot 拷貝到新的地方去
-
調(diào)用函數(shù) relocate_vectors,對(duì)中斷向量表做重定位
-
清除 BSS 段 , 。
bss段不占用空間,都是未初始化的全局變量或者已經(jīng)初始化為零的變量,本來(lái)就是零,直接清零就好。不清零的話未初始化的變量可能會(huì)存在未知的數(shù)值。 -
設(shè)置函數(shù) board_init_r 的兩個(gè)參數(shù) , 調(diào)用 board_init_r 函數(shù) ,
-
board_init_r 函數(shù)打印一些列的信息到串口,然后會(huì)進(jìn)入main_loop() 。main_loop會(huì)進(jìn)行倒計(jì)時(shí),如果此時(shí)按下回車就會(huì)進(jìn)入uboot的shell交互界面,否則就會(huì)自動(dòng)引導(dǎo)啟動(dòng)OS系統(tǒng)。
2.2.7. board_init_f 函數(shù)
- uboot 會(huì)將自己重定位到 DRAM(DDR) 最后面的地址區(qū)域,也就是將自己拷貝到 DRAM 最后面的內(nèi)存區(qū)域中。這么做的目的是給 Linux 騰出空間,防止 Linuxkernel 覆蓋掉 uboot,將 DRAM 前面的區(qū)域完整的空出來(lái)。
- 在拷貝之前肯定要給 uboot 各部分分配好內(nèi)存位置和大小,比這些信息都保存在 gd 的成員變量中(從板子配置文件里讀取),因此要對(duì) gd 的這些成員變量做初始化。最終形成一個(gè)完整的內(nèi)存“分配圖”,在后面重定位 uboot 的時(shí)候就會(huì)用到這個(gè)內(nèi)存“分配圖”(外部)。
- 注:上電后芯片內(nèi)部Boot ROM把uboot搬移到DRAM頭部(0x87800000),重定位則再搬移到DDR后部
2.2.8. relocate_code 函數(shù)
- relocate_code 函數(shù)是用于代碼拷貝
重定位就是 uboot 將自身拷貝到 DRAM 的另一個(gè)地放去繼續(xù)運(yùn)行(DRAM 的高地址處)。我們知道,一個(gè)可執(zhí)行的 bin 文件,其鏈接地址和運(yùn)行地址要相等,也就是鏈接到哪個(gè)地址,在運(yùn)行之前就要拷貝到哪個(gè)地址去。現(xiàn)在我們重定位以后,運(yùn)行地址就和鏈接地址不同了,這樣尋址的時(shí)候不會(huì)出問(wèn)題嗎?
代碼動(dòng)態(tài)重定位原理
分析問(wèn)題產(chǎn)生原因
r0 = gd->relocaddr = 0x9ff47000 , uboot重定位后的首地址
r1 = 0x87800000 源地址的首地址
r2 = 0x8785dc6c 源地址的結(jié)束地址
r4 = 0x9ff46000 - 0x87800000 = 0x18747000 偏移量
拷貝是從r1復(fù)制往r0粘貼 , 一次兩個(gè)32位
當(dāng)r1等于r2,拷貝完成
當(dāng)簡(jiǎn)單粗暴的將uboot從0x87800000拷貝到0x9ff47000 ,程序運(yùn)行時(shí)地址和連接地址不同,發(fā)生錯(cuò)誤。uboot解決方法是使用位置無(wú)關(guān)碼,借用 .rel.dyn 段
使用位置無(wú)關(guān)碼解重定位后和連接地址不同問(wèn)題的原理
舉例: board_init 函數(shù)會(huì)調(diào)用 rel_test,rel_test 會(huì)調(diào)用全局變量 rel_a
源代碼
反匯編代碼
8785dcf8 <rel_a>: 8785dcf8: 00000000 andeq r0, r0, r0878042b4 <rel_test>: 878042b4: e59f300c ldr r3, [pc, #12] ; 878042c8 <rel_test+0x14> 878042b8: e3a02064 mov r2, #100 ; 0x64 878042bc: e59f0008 ldr r0, [pc, #8] ; 878042cc <rel_test+0x18> 878042c0: e5832000 str r2, [r3] 878042c4: ea00d64c b 87839bfc <printf> 878042c8: 8785dcf8 ; <UNDEFINED> instruction: 0x8785dcf8 878042cc: 87842aaf strhi r2, [r4, pc, lsr #21]從反匯編代碼中分析:
- 想要找到 rel_a 的地址,首先 r3 = pc + 12 = 0x878042b4 + 8 + 12 = 0x878042c8 。(由ARM 流水線決定 pc = 當(dāng)前地址 + 8)
- 之后 r3 在0x878042c8 中存儲(chǔ)數(shù)據(jù)為0x8785dcf8,即為rel_a地址
- 這里沒(méi)直接讀取rel_a , 而是借助0x878042c8 。 0x878042c8 就是Label
重定位后,地址變化
9ffa4cf8 <rel_a>: 9ffa4cf8: 00000000 andeq r0, r0, r09ff4b2b4<rel_test>: 9ff4b2b4: e59f300c ldr r3, [pc, #12] ; 878042c8 <rel_test+0x14> 9ff4b2b8: e3a02064 mov r2, #100 ; 0x64 9ff4b2bc: e59f0008 ldr r0, [pc, #8] ; 878042cc <rel_test+0x18> 9ff4b2c0: e5832000 str r2, [r3] 9ff4b2c4: ea00d64c b 87839bfc <printf> 9ff4b2c8: 8785dcf8 ; <UNDEFINED> instruction: 0x8785dcf8 9ff4b2cc: 87842aaf strhi r2, [r4, pc, lsr #21]- 這時(shí) Label中的值還是重定位之前的,必須要將8785dcf8換為重定位后的rel_a地址
- 重定位后的Label中的數(shù)據(jù) = 0x878042c8(老的Label) + 0x18747000(uboot整體的偏移量) = 0x9ffa4cf8;
- 讀取 rel_a 地址為 ,r3 = 0x9ff4b2b4 + 8 + 12 = 0x9ff4b2c8 ,即為L(zhǎng)abel地址,然后從Label中讀取到 0x9ffa4cf8,為rel_a重定義后真是地址
uboot 中使用 .rel.dyn 段具體實(shí)現(xiàn)位置無(wú)關(guān)碼的原理
完成這個(gè)功能在連接的時(shí)候需要加上”-pie”
.rel.dyn 段代碼段
8785dcec: 87800020 strhi r0, [r0, r0, lsr #32] 8785dcf0: 00000017 andeq r0, r0, r7, lsl r0 …… 8785e2fc: 878042c8 strhi r4, [r0, r8, asr #5] 8785e300: 00000017 andeq r0, r0, r7, lsl r0- .rel.dyn 段的格式,也就是兩個(gè) 4 字節(jié)數(shù)據(jù)為一組。
高 4 字節(jié)是 Label 地址標(biāo)識(shí) 0X17,低 4 字節(jié)就是 Label 的地址, - 第三行是878042c8,第四行是00000017 。說(shuō)明878042c8是一個(gè)Label。正是上述分析中 存放 rel_a 地址的Label
- 在重定位后,rel_a 真是地址 = Label內(nèi)數(shù)據(jù) + offset(0x18747000)
2.2.9 relocate_vectors函數(shù)
- 中斷向量表重定位后的地址,就是重定位后uboot的首地址
2.2.10. board_init_r 函數(shù)(板級(jí)初始化,進(jìn)入第二階段)
board_init_f 并沒(méi)有初始化所有的外設(shè),還需要做一些后續(xù)工作,這些后續(xù)工作就是由函數(shù) board_init_r 來(lái)完成的
uboot relocate后的板級(jí)初始化 ,最后執(zhí)行run_main_loop
第二階段
2.2.11 run_main_loop函數(shù)
- run_main_loop-> main_loop->
- -> bootdelay_process 獲取bootdelay的值,然后保存到stored_bootdelay
全局變量里面,獲取bootcmd環(huán)境變量值,并且將其
返回 - -> autoboot_command 參數(shù)是bootcmd的值。檢查倒計(jì)時(shí)內(nèi)有無(wú)打斷
- -> abortboot 參數(shù)為boot delay,此函數(shù)會(huì)處理倒計(jì)時(shí)
- -> abortboot_normal 參數(shù)為boot delay,此函數(shù)會(huì)處理倒計(jì)時(shí)
- ->run_command_list函數(shù),參數(shù)為s(bootcmd的命令)倒計(jì)時(shí)結(jié)束時(shí)hush shell沒(méi)輸入執(zhí)行,啟動(dòng)內(nèi)核
- -> abortboot 參數(shù)為boot delay,此函數(shù)會(huì)處理倒計(jì)時(shí)
- -> cli_loop 是uboot命令模式處理函數(shù)。
- -> parse_file_outer
- -> parse_stream_outer
- -> parse_stream 解析輸入的字符,得到命令
- -> run_list 運(yùn)行命令
- -> run_list_real
- -> run_pipe_real
- -> cmd_process 處理命令,也就是執(zhí)行命令
- -> run_pipe_real
- -> run_list_real
- -> parse_stream_outer
- -> parse_file_outer
- -> bootdelay_process 獲取bootdelay的值,然后保存到stored_bootdelay
進(jìn)入了main_loop()函數(shù)。
2.2.11 main_loop()函數(shù)
void main_loop(void) {const char *s;bootstage_mark_name(BOOTSTAGE_ID_MAIN_LOOP, "main_loop"); // 這里用于標(biāo)記uboot的進(jìn)度,對(duì)于tiny210來(lái)說(shuō)起始什么都沒(méi)做cli_init(); // cli的初始化,主要是hush模式下的初始化run_preboot_environment_command(); // preboot相關(guān)的東西,后續(xù)有用到再說(shuō)明s = bootdelay_process();if (cli_process_fdt(&s))cli_secure_boot_cmd(s);autoboot_command(s); // autoboot的東西,后續(xù)使用autoboot的時(shí)候再專門(mén)說(shuō)明cli_loop(); // 進(jìn)入cli的循環(huán)模式,也就是命令行模式panic("No CLI available"); }- 調(diào)用 bootstage_mark_name 函數(shù),打印出啟動(dòng)進(jìn)度。
- 調(diào)用 cli_init 函數(shù),跟命令初始化有關(guān),初始化 hush shell 相關(guān)的變量。( hush shell 為uboot倒計(jì)時(shí)結(jié)束的輸入shell)
- run_preboot_environment_command 函數(shù),獲取環(huán)境變量 perboot 的內(nèi)容,perboot
是一些預(yù)啟動(dòng)命令 - bootdelay_process 函數(shù),此函數(shù)會(huì)讀取環(huán)境變量 bootdelay (延遲時(shí)間)和 bootcmd(啟動(dòng)內(nèi)核命令) 的內(nèi)容,
然后將 bootdelay 的值賦值給全局變量 stored_bootdelay,返回值為環(huán)境變量 bootcmd 的值。 - autoboot_command(s)函數(shù),此函數(shù)就是檢查倒計(jì)時(shí)是否結(jié)束?倒計(jì)時(shí)結(jié)束之前有
沒(méi)有被打斷?輸入?yún)?shù)s為bootcmd的命令- 倒計(jì)時(shí)自然結(jié)束執(zhí)行函數(shù)run_command_list,此函數(shù)會(huì)執(zhí)行參數(shù) s 指定的一系列命令,也就是環(huán)境變量 bootcmd 的命令,bootcmd 里面保存著默認(rèn)的啟動(dòng)命令,因此 linux 內(nèi)核啟動(dòng)
- 倒計(jì)時(shí)結(jié)束之前按下了鍵盤(pán)上的按鍵,那么 run_command_list函數(shù)就不會(huì)執(zhí)行,執(zhí)行后面的cli_loop函數(shù)
- cli_loop函數(shù),是 uboot 的命令行處理函數(shù)
2.2.12 cli_loop函數(shù)
cli_loop 函數(shù)是 uboot 的命令行處理函數(shù),我們?cè)?uboot 中輸入各種命令,進(jìn)行各種操作就是有 cli_loop 來(lái)處理的
void cli_loop(void) {#ifdef CONFIG_SYS_HUSH_PARSERparse_file_outer();/* This point is never reached */for (;;);#elsecli_simple_loop(); //永遠(yuǎn)不會(huì)執(zhí)行#endif /*CONFIG_SYS_HUSH_PARSER*/ }- 調(diào)用 parse_file_outer函數(shù)
2.2.13 parse_file_outer函數(shù)
int parse_file_outer(void) {int rcode;struct in_str input;setup_file_in_str(&input);rcode = parse_stream_outer(&input, FLAG_PARSE_SEMICOLON);return rcode;}- 調(diào)用函數(shù) setup_file_in_str 初始化變量 input 的成員變量
- 調(diào)用函數(shù) parse_stream_outer,這個(gè)函數(shù)就是 hush shell 的命令解釋器,負(fù)責(zé)接收命令行輸入,然后解析并執(zhí)行相應(yīng)的命令
2.3.14 函數(shù) parse_stream_outer
static int parse_stream_outer(struct in_str *inp, int flag){struct p_context ctx;o_string temp=NULL_O_STRING;int rcode;int code = 1;do {......rcode = parse_stream(&temp, &ctx, inp,flag & FLAG_CONT_ON_NEWLINE ? -1 : '\n');......if (rcode != 1 && ctx.old_flag == 0) {......run_list(ctx.list_head);......} else {......}b_free(&temp);/* loop on syntax errors, return on EOF */} while (rcode != -1 && !(flag & FLAG_EXIT_FROM_LOOP) &&(inp->peek != static_peek || b_peek(inp)));return 0; }- do-while 循環(huán)就是處理輸入命令的
- 函數(shù) parse_stream 進(jìn)行命令解析
- 調(diào)用 run_list 函數(shù)來(lái)執(zhí)行解析出來(lái)的命令,run_list 調(diào)用 run_list_real 函數(shù),run_list_real 函數(shù)調(diào)用 run_pipe_real 函數(shù),run_pipe_real 函數(shù)調(diào)用 cmd_process 函數(shù)。最終通過(guò)函數(shù) cmd_process 來(lái)處理命令
2.3.15 補(bǔ)充:uboot的命令
uboot把所有命令的數(shù)據(jù)結(jié)構(gòu)都放在一個(gè)表格中,我們后續(xù)稱之為命令表。表中的每一項(xiàng)代表著一個(gè)命令,其項(xiàng)的類型是cmd_tbl_t。
struct cmd_tbl_s {char *name; /* Command Name */int maxargs; /* maximum number of arguments */int repeatable; /* autorepeat allowed? *//* Implementation function */int (*cmd)(struct cmd_tbl_s *, int, int, char * const []);char *usage; /* Usage message (short) */ #ifdef CONFIG_SYS_LONGHELPchar *help; /* Help message (long) */ #endif }; typedef struct cmd_tbl_s cmd_tbl_t;參數(shù)說(shuō)明如下:
name:定義一個(gè)命令的名字。 其實(shí)就是執(zhí)行的命令的字符串。這個(gè)要注意。
maxargs:這個(gè)命令支持的最大參數(shù)
repeatable:是否需要重復(fù)
cmd:命令處理函數(shù)的地址
usage:字符串,使用說(shuō)明
help:字符串,幫助
Uboot使用U_BOOT_CMD來(lái)定義一個(gè)命令。CONFIG_CMD_XXX來(lái)使能uboot中的某個(gè)命令。
//uboot命令定義代碼 #define U_BOOT_CMD(_name, _maxargs, _rep, _cmd, _usage, _help)U_BOOT_CMD最終是定義了一個(gè)cmd_tbl_t類型的結(jié)構(gòu)體變量,所有的命令最終都是存放在.u_boot_list段里面。cmd_tbl_t結(jié)構(gòu)體中的cmd成員變量就是具體的命令執(zhí)行函數(shù),命令執(zhí)行函數(shù)都是do_xxx。
// bootm就是我們的命令字符串 // 在tiny210.h中定義了最大參數(shù)數(shù)量是64 // #define CONFIG_SYS_MAXARGS 64 /* max number of command args */ // 1表示重復(fù)一次 // 對(duì)應(yīng)命令處理函數(shù)是do_bootm // usage字符串是"boot application image from memory" // help字符串是bootm_help_text定義的字符串。 U_BOOT_CMD(bootm, CONFIG_SYS_MAXARGS, 1, do_bootm,"boot application image from memory", bootm_help_text );//并且命令處理函數(shù)的格式如下: //當(dāng)命令處理函數(shù)執(zhí)行成功時(shí),需要返回0.返回非0值 int do_bootm(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])2.3.16 函數(shù) cmd_process
函數(shù) cmd_process 來(lái)處理命令并執(zhí)行
- cmd_process
- ->find_cmd 從.u_boot_list段里面查找命令,當(dāng)找到對(duì)應(yīng)的命令以后以返回值的
形式給出,為cmd_tbl_t類型 - ->cmd_call
->cmdtp->cmd 直接引用cmd成員變量,執(zhí)行do_xxx函數(shù)
- ->find_cmd 從.u_boot_list段里面查找命令,當(dāng)找到對(duì)應(yīng)的命令以后以返回值的
函數(shù) cmd_process代碼
enum command_ret_t cmd_process(int flag, int argc, char * const argv[],int *repeatable, ulong *ticks) {enum command_ret_t rc = CMD_RET_SUCCESS;cmd_tbl_t *cmdtp;/* Look up command in command table */cmdtp = find_cmd(argv[0]);// 第一個(gè)參數(shù)argv[0]表示命令,調(diào)用find_cmd獲取命令對(duì)應(yīng)的表項(xiàng)cmd_tbl_t。if (cmdtp == NULL) {printf("Unknown command '%s' - try 'help'\n", argv[0]);return 1;}/* found - check max args */if (argc > cmdtp->maxargs)rc = CMD_RET_USAGE;// 檢測(cè)參數(shù)是否正常/* If OK so far, then do the command */if (!rc) {if (ticks)*ticks = get_timer(0);rc = cmd_call(cmdtp, flag, argc, argv);// 調(diào)用cmd_call執(zhí)行命令表項(xiàng)中的命令,成功的話需要返回0值if (ticks)*ticks = get_timer(*ticks);// 判斷命令執(zhí)行的時(shí)間*repeatable &= cmdtp->repeatable;// 這個(gè)命令執(zhí)行的重復(fù)次數(shù)存放在repeatable中的}if (rc == CMD_RET_USAGE)rc = cmd_usage(cmdtp);// 命令格式有問(wèn)題,打印幫助信息return rc; }- find_cmd函數(shù), 從.u_boot_list段獲取命令對(duì)應(yīng)的命令表項(xiàng)cmd_tbl_t 。
- cmd_call函數(shù),執(zhí)行命令表項(xiàng)cmd_tbl_t 中的命令,執(zhí)行do_xxx函數(shù)
2.3.17 uboot啟動(dòng)linux內(nèi)核(bootz命令)
在run_main_loop函數(shù)倒計(jì)時(shí)結(jié)束時(shí)沒(méi)有輸入,uboot執(zhí)行bootcmd命令,啟動(dòng)內(nèi)核
-
bootcmd 保存著 uboot 默認(rèn)命令,這些命令一般都是用來(lái)啟動(dòng) Linux 內(nèi)核的,讀取Linux鏡像文件和設(shè)備樹(shù)文件(從MMC、SD卡、網(wǎng)絡(luò)讀取等)到DARM(DDR)中,然后啟動(dòng)內(nèi)核(bootz、bootm)。
-
bootm和bootz的不同地方
- bootm用于加載uImage和ramdisk
bootm ${kernel_load_address} ${ramdisk_load_address} ${devicetree_load_address} - bootz用于加載zImage和ext4文件系統(tǒng)
bootz ${kernel_load_address} ${ramdisk_load_address} ${devicetree_load_address}
- bootm用于加載uImage和ramdisk
-
bootz的使用(tftp從網(wǎng)絡(luò)讀取)
tftp 80800000 zImage tftp 83000000 imx6ull-14x14-emmc-7-1024x600-c.dtb bootz 80800000 - 83000000
bootz流程
2.3.18 do_bootz函數(shù)
do_bootz是bootz的執(zhí)行函數(shù)
do_bootz
- -> bootz_start
- -> do_bootm_states 階段為BOOTM_STATE_START
- -> bootm_start 對(duì)images全局變量清零
- -> images->ep = 0X80800000
- ->bootz_setup 判斷zImage是否正確
- -> bootm_find_images 查找鏡像文件
- -> boot_get_fdt 找到設(shè)備樹(shù),然后將設(shè)備樹(shù)起始地址和長(zhǎng)度,寫(xiě)入到images的ft_addr和ft_len成員變量中。
- -> bootm_disable_interrupts 關(guān)閉中斷相關(guān)
- -> images.os.os = IH_OS_LINUX; 表示要啟動(dòng)Linux系統(tǒng)
- -> do_bootm_states 狀態(tài)BOOTM_STATE_OS_PREP 、BOOTM_STATE_OS_FAKE_GO 、
BOOTM_STATE_OS_GO,- -> bootm_os_get_boot_func 查找Linux內(nèi)核啟動(dòng)函數(shù)。找到Linux內(nèi)核啟動(dòng)函數(shù)
do_bootm_linux,賦值給boot_fn。 - -> boot_fn(BOOTM_STATE_OS_PREP, argc, argv, images); 就是do_bootm_linux。
- -> boot_prep_linux 啟動(dòng)之前的一些工作,對(duì)于使用設(shè)備樹(shù)來(lái)說(shuō),他會(huì)將Bootargs傳遞給Linux內(nèi)核,通過(guò)設(shè)備樹(shù)完成。也就是向Linux內(nèi)核傳參。
- -> boot_jump_linux
- -> machid= gd->bd->bi_arch_number;
- -> kernel_entry = (void (*)(int, int, uint))images->ep; 0X80800000。
- -> announce_and_cleanup 輸出Starting kernel……
- -> kernel_entry(0, machid, r2); 啟動(dòng)Linux內(nèi)核。Uboot的最終使命,啟動(dòng)Linux內(nèi)核。
- -> bootm_os_get_boot_func 查找Linux內(nèi)核啟動(dòng)函數(shù)。找到Linux內(nèi)核啟動(dòng)函數(shù)
- -> do_bootm_states 階段為BOOTM_STATE_START
2.3.19 kernel_entry函數(shù)
kernel_entry函數(shù)是內(nèi)核的入口函數(shù)。內(nèi)核鏡像第一行代碼
kernel_entry(0, machid, r2);此函數(shù)有三個(gè)參數(shù):zero,arch,params,
- 第一個(gè)參數(shù) zero 同樣為 0;
- 第二個(gè)參數(shù)為機(jī)器 ID;
- 第三個(gè)參數(shù)啟動(dòng)參數(shù)標(biāo)記列表(ATAGS)或者設(shè)備樹(shù)(DTB)首地址,ATAGS 是傳統(tǒng)的方法,用于傳遞一些命令行信息啥的,如果使用設(shè)備樹(shù)的話就要傳遞設(shè)備樹(shù)(DTB)。
- 是匯編函數(shù),因?yàn)閮?nèi)核開(kāi)始是匯編代碼。
- images->ep 保存著 Linux內(nèi)核鏡像的起始地址,也就是kernel_entry的地址
kernel_entry函數(shù)的傳參
- 向匯編函數(shù)傳遞參數(shù)要使用 r0、r1 和 r2寄存器
- r0 = 0
- r1 = 機(jī)器類型ID(machid)
- Linux 內(nèi)核會(huì)在自己的機(jī)器 ID 列表里面查找是否存在與 uboot 傳遞進(jìn)來(lái)的 machid 匹配的項(xiàng)目,如果存在就說(shuō)明 Linux 內(nèi)核支持這個(gè)機(jī)器,那么 Linux 就會(huì)啟動(dòng)!
- 如果使用設(shè)備樹(shù)的話這個(gè) machid 就無(wú)效了,設(shè)備樹(shù)存有一個(gè)“兼容性”這個(gè)屬性,Linux 內(nèi)核會(huì)比較“兼容性”屬性的值(字符串)來(lái)查看是否支持這個(gè)機(jī)器。
- r2 =
- 如果使用設(shè)備樹(shù)的話,r2 應(yīng)該是設(shè)備樹(shù)的起始地址
- 如果不使用設(shè)備樹(shù)的話,r2 應(yīng)該是 uboot 傳遞給 Linux 的參數(shù)起始地址,也就是環(huán)境變量 bootargs 的值
如何從uboot跳轉(zhuǎn)到內(nèi)核
直接修改PC寄存器的值為L(zhǎng)inux內(nèi)核所在的地址,CPU從內(nèi)核所在的地址去取指令,從而執(zhí)行內(nèi)核代碼
為什么要傳參數(shù)給內(nèi)核
在此之前, uboot已經(jīng)完成了硬件的初始化,可以說(shuō)已經(jīng)噎適應(yīng)了“這塊開(kāi)發(fā)板。然而,內(nèi)核并不是對(duì)于所有的開(kāi)發(fā)板都能完美適配的(如果適配了,可想而知這個(gè)內(nèi)核有多龐大,又或者有新技術(shù)發(fā)明了,可以完美的適配各種開(kāi)發(fā)板),此時(shí),對(duì)于開(kāi)發(fā)板的環(huán)境一無(wú)所知。所以,要想啟動(dòng) Linux內(nèi)核, uboot必須要給內(nèi)核傳遞一些必要的信息來(lái)告訴內(nèi)核當(dāng)前所處的環(huán)境。
總結(jié)
以上是生活随笔為你收集整理的uboot启动流程详细分析(基于i.m6ull)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 面试相关东东
- 下一篇: ChatGPT实现代码生成