日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

uboot启动流程详细分析(基于i.m6ull)

發布時間:2024/1/8 编程问答 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 uboot启动流程详细分析(基于i.m6ull) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

uboot介紹

uboot就是一段引導程序在加載系統內核之前,完成硬件初始化,內存映射,為后續內核的引導提供一個良好的環境。uboot是bootloader的一種,全稱為universal boot loader。

一、uboot的makefile

1.1 makefile整體解析過程

為了生成u-boot.bin這個文件,首先要生成構成u-boot.bin的各個庫文件、目標文件。為了各個庫文件、目標文件就必須進入各個子目錄執行其中的Makefile。由此,確定了整個編譯的命令和順序。

1.2 makefile整體編譯過程

  • 首先,根據各個庫文件、目標文件出現的先后順序,依次進入各個子目錄編譯從而生成這些目標
  • 然后,回到頂層目錄,繼續執行頂層Makefile的總目標,最后生成u-boot.bin。

uboot的編譯分為兩步:配置、編譯。

(1)第一步:配置,執行make pangu_basic_defconfig進行配置,生成.config文件

(2)第二步:編譯,執行make進行編譯,生成u-boot.bin

二、uboot啟動流程

  • uboot分為 uboot-spl 和 uboot 兩個組成部分。

uboot啟動分三個階段

  • BL0
    ROM上的固化程序(Boot Rom)

  • BL1(u-boot-spl)

    • 初始化部分時鐘(和SDRAM相關)
    • 初始化DDR(外部SDRAM)
    • 從存儲介質上(比如SD\eMMC\nand flash)將BL2鏡像加載到SDRAM上
    • 驗證BL2鏡像的合法性
    • 跳轉到BL2鏡像所在的地址上
  • BL2 (uboot)
    • 初始化部分硬件,包括時鐘、內存等等
    • 加載內核到內存上
    • 加載文件系統、atags或者dtb到內存上
    • 根據操作系統啟動要求正確配置好一些硬件

    啟動操作系統

    2.1 uboot的鏈接文件(u-boot.lds)

    鏈接文件的作用

  • 指定代碼段和數據段、只讀數據段在內存中的存放地址;(地址具體為i.m6ull , 其他芯片可能不是 0X87800000)

    • u-boot.map 是 uboot 的映射文件,看到某個文件或者函數鏈接到了哪個地址,

    • __image_copy_start 為 0X87800000,而.text 的起始地址也是0X87800000。

    • vectors 段保存中斷向量表,vectors 段的起始地址也是 0X87800000,說明整個 uboot 的起始地址就是 0X87800000,

    • 這也是為什么我們裸機例程的鏈接起始地址選擇 0X87800000 了,目的就是為了和 uboot 一致。

  • 指定代碼的入口地址;

    • 連接文件中找到程序的入口點:_start, 其中_start 在文件 arch/arm/lib/vectors.S 。
  • 2.2 uboot啟動流程

    第一階段(uboot-spl , 入口是上述lds文件中分析的_start)

    • SPL是Secondary Program Loader的簡稱,第二階段程序加載器,這里所謂的第二階段是相對于SOC中的Boot ROM來說的

    Boot ROM會通過檢測啟動方式來加載第二階段bootloader。uboot已經是一個bootloader了,那么為什么還多一個uboot spl呢?

    • 這個主要原因是對于一些SOC來說,它的內部SRAM可能會比較小,小到無法裝載下一個完整的uboot鏡像,那么就需要spl,它主要負責初始化外部RAM和環境,并加載真正的uboot鏡像到外部RAM(DDR)中來執行。

    • 所以由此來看,SPL應該是一個非常小的loader程序,可以運行于SOC的內部SRAM中,它的主要功能就是加載真正的uboot并運行之。

    2.2.1. 進入_start函數:

    _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

    有一條跳轉指令b reset跳轉到reset函數處去執行
    注意,spl的流程在reset中就應該被結束,也就是說在reset中,就應該轉到到BL2,也就是uboot中了。

    2.2.2. 進入reset函數

    reset:/* Allow the board to save important registers */b save_boot_params @進入reset第一步跳轉到save_boot_params save_boot_params_ret: @ save_boot_params 內部通過cpsr 設置cpu為SVC模式,關閉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 @ 跳轉到cpu_init_cp15 ,初始化協處理器CP15,從而禁用MMU和TLB。bl cpu_init_crit @ 跳轉到cpu_init_crit ,進行一些關鍵的初始化動作,也就是平臺級和板級的初始化 #endifbl _main @ 跳轉到_main ,加載BL2以及跳轉到BL2的主體部分

    進入reset函數,首先設置cpu為SVC模式關閉中斷。然后跳轉到cpu_init_cp15初始化協處理器CP15,從而禁用MMU和TLB。跳轉到cpu_init_crit ,進行一些關鍵的初始化動作,也就是平臺級和板級的初始化。最后跳轉到**_main**,加載BL2以及跳轉到BL2的主體部分

    • 關閉中斷。
      uboot引導linux起到的過程中本身就是一個完成的過程,不需要中斷機制。

    2.2.3 cpu_init_cp15函數

    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 @@ 這里只需要知道是對CP15處理器的部分寄存器清零即可。 @@ 將協處理器的c7\c8清零等等,各個寄存器的含義請參考《ARM的CP15協處理器的寄存器》/** 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 @@ 通過上述的文章的介紹,我們可以知道cp15的c1寄存器就是MMU控制器 @@ 上述對MMU的一些位進行清零和置位,達到關閉MMU和cache的目的,具體的話去看一下上述文章吧。ENDPROC(cpu_init_cp15)

    cpu_init_cp15 用來設置 CP15 相關的內容,完成啟動ICACHE,關閉DCACHE,關閉MMU和TLB

    • 關閉MMU
      ,MMU是用于虛擬地址向物理地址進行映射的一個結構。在 uboot階段操作的就直接是 物理地址,所以不需要轉換。
    • 啟動ICACHE(指令),關閉DCACHE(數據)
      啟動指令CACHE課可以加快指令讀取的速度,但是數據CACHE 必須 要關閉,因為它本身是一個CPU的二級緩存,在運行程序的時候可能會往里面去取數據,但是此時ram里面的數據可能并沒有存入到里面,這就可能導致讀取到錯誤的數據。

    2.2.4 cpu_init_crit函數(包含lowlevel_init函數)

    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函數的內容是跳轉到lowlevel_init函數。

    2.2.5. lowlevel_init 函數(平臺級和板級的初始化)

    lowlevel_init主要完成平臺級和板級的初始化
    在lowlevel_init中,我們要實現如下:

    • 檢查一些復位狀態
    • 關閉看門狗
    • 系統時鐘的初始化
    • 內存、DDR的初始化
    • 串口初始化(可選)
    • Nand flash的初始化

    在im6ull中

    此時初始化SP指向 內存空間為IRAM(內部ram ,OCRAM 128K ,0x00900000),(初始化內存空間,為第二階段準備ram)

    2.2.6. _main函數

    ** _main函數的主要工作是 **

    • 設置c語言的運行環境
    • 設置sp和gd的中間環境
    • 重定義代碼
    • 設置最終的環境
    ENTRY(_main)/** Set up initial C runtime environment and call board_init_f(0). */ @ 為c語言環境準備 @ uboot-spl和uboot代碼共用,CONFIG_SPL_BUILD來區分是誰調用 #if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)ldr sp, =(CONFIG_SPL_STACK) @ 在spl中為 c語言環境設置棧 #elseldr sp, =(CONFIG_SYS_INIT_SP_ADDR) #endif #if defined(CONFIG_CPU_V7M) /* v7M forbids using SP as BIC destination */mov r3, spbic r3, r3, #7mov sp, r3 #elsebic sp, sp, #7 /* 8-byte alignment for ABI compliance */ #endifmov r0, spbl board_init_f_alloc_reserve @ 把棧前面的空間分配給GDmov sp, r0 @重新設置指針SP/* set up gd here, outside any C code */mov r9, r0 @ 保存GD地址到r9寄存器bl board_init_f_init_reserve @ 初始化GD空間mov r0, #0bl board_init_f @ 跳轉到板級前期初始化函數,#if ! defined(CONFIG_SPL_BUILD)/** Set up intermediate environment (new sp and gd) and call* relocate_code(addr_moni). Trick here is that we'll return* 'here' but relocated.*/@ 設置sp和gd的中間環境ldr sp, [r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp */ #if defined(CONFIG_CPU_V7M) /* v7M forbids using SP as BIC destination */mov r3, spbic r3, r3, #7mov sp, r3 #elsebic sp, sp, #7 /* 8-byte alignment for ABI compliance */ #endifldr r9, [r9, #GD_BD] /* r9 = gd->bd */sub r9, r9, #GD_SIZE /* new GD is below bd */adr lr, hereldr r0, [r9, #GD_RELOC_OFF] /* r0 = gd->reloc_off */add lr, lr, r0 #if defined(CONFIG_CPU_V7M)orr lr, #1 /* As required by Thumb-only */ #endifldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */b relocate_code @ 重定位代碼 here: /** now relocate vectors*/bl relocate_vectors @重定位向量表/* Set up final (full) environment */@設置最終的環境bl c_runtime_cpu_setup /* we still call old routine here */ #endif #if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_FRAMEWORK) # ifdef CONFIG_SPL_BUILD/* Use a DRAM stack for the rest of SPL, if requested */bl spl_relocate_stack_gdcmp r0, #0movne sp, r0movne r9, r0 # endifldr r0, =__bss_start /* this is auto-relocated! */#ifdef CONFIG_USE_ARCH_MEMSETldr r3, =__bss_end /* this is auto-relocated! */mov r1, #0x00000000 /* prepare zero to clear BSS */subs r2, r3, r0 /* r2 = memset len */bl memset #elseldr r1, =__bss_end /* this is auto-relocated! */mov r2, #0x00000000 /* prepare zero to clear BSS */clbss_l:cmp r0, r1 /* while not at end of BSS */ #if defined(CONFIG_CPU_V7M)itt lo #endifstrlo r2, [r0] /* clear 32-bit BSS word */addlo r0, r0, #4 /* move to next */blo clbss_l #endif#if ! defined(CONFIG_SPL_BUILD)bl coloured_LED_initbl red_led_on #endif/* call board_init_r(gd_t *id, ulong dest_addr) */mov r0, r9 /* gd_t */ldr r1, [r9, #GD_RELOCADDR] /* dest_addr *//* call board_init_r */@ 跳轉board_init_r 設置最終環境 #if defined(CONFIG_SYS_THUMB_BUILD) ldr lr, =board_init_r /* this is auto-relocated! */bx lr #elseldr pc, =board_init_r /* this is auto-relocated! */ #endif/* we should not return here. */ #endifENDPROC(_main)
    • 因為后面是C語言環境,首先是設置堆棧

    • 初始化gd(下圖中global date,內部ram) , 進行清零(同上內部ram)

    • 調用 board_init_f 函數(將SP指針從內部IRAM,轉移到外部DDR),主要用來初始化 DDR,定時器,完成代碼拷貝等等

    • 調用函數 relocate_code,也就是代碼重定位函數,此函數負責將 uboot 拷貝到新的地方去

    • 調用函數 relocate_vectors,對中斷向量表做重定位

    • 清除 BSS 段 , 。
      bss段不占用空間,都是未初始化的全局變量或者已經初始化為零的變量,本來就是零,直接清零就好。不清零的話未初始化的變量可能會存在未知的數值。

    • 設置函數 board_init_r 的兩個參數調用 board_init_r 函數

    • board_init_r 函數打印一些列的信息到串口,然后會進入main_loop() 。main_loop會進行倒計時,如果此時按下回車就會進入uboot的shell交互界面,否則就會自動引導啟動OS系統。

    2.2.7. board_init_f 函數

  • 初始化一系列外設,比如串口、定時器,或者打印一些消息等。
  • 重新設置環境(sp 和 gd) , 新的 sp 和 gd 將會存放到 DDR 中(外部),而不是內部的 RAM 了
    • uboot 會將自己重定位到 DRAM(DDR) 最后面的地址區域,也就是將自己拷貝到 DRAM 最后面的內存區域中。這么做的目的是給 Linux 騰出空間,防止 Linuxkernel 覆蓋掉 uboot,將 DRAM 前面的區域完整的空出來。
    • 在拷貝之前肯定要給 uboot 各部分分配好內存位置和大小,比這些信息都保存在 gd 的成員變量中(從板子配置文件里讀取),因此要對 gd 的這些成員變量做初始化。最終形成一個完整的內存“分配圖”,在后面重定位 uboot 的時候就會用到這個內存“分配圖”(外部)。
    • 注:上電后芯片內部Boot ROM把uboot搬移到DRAM頭部(0x87800000),重定位則再搬移到DDR后部
  • 2.2.8. relocate_code 函數

    • relocate_code 函數是用于代碼拷貝

    重定位就是 uboot 將自身拷貝到 DRAM 的另一個地放去繼續運行(DRAM 的高地址處)。我們知道,一個可執行的 bin 文件,其鏈接地址和運行地址要相等,也就是鏈接到哪個地址,在運行之前就要拷貝到哪個地址去。現在我們重定位以后,運行地址就和鏈接地址不同了,這樣尋址的時候不會出問題嗎?

    代碼動態重定位原理

  • 分析問題產生原因

    r0 = gd->relocaddr = 0x9ff47000 , uboot重定位后的首地址
    r1 = 0x87800000 源地址的首地址
    r2 = 0x8785dc6c 源地址的結束地址
    r4 = 0x9ff46000 - 0x87800000 = 0x18747000 偏移量
    拷貝是從r1復制往r0粘貼 , 一次兩個32位
    當r1等于r2,拷貝完成

    當簡單粗暴的將uboot從0x87800000拷貝到0x9ff47000 ,程序運行時地址和連接地址不同,發生錯誤。uboot解決方法是使用位置無關碼,借用 .rel.dyn 段

  • 使用位置無關碼解重定位后和連接地址不同問題的原理

    舉例: board_init 函數會調用 rel_test,rel_test 會調用全局變量 rel_a
    源代碼

    static int rel_a = 0;void rel_test(void){rel_a = 100;printf("rel_test\r\n");} int board_init(void) {...rel_test();... }

    反匯編代碼

    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 = 當前地址 + 8)
    • 之后 r3 在0x878042c8 中存儲數據為0x8785dcf8,即為rel_a地址
    • 這里沒直接讀取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]
    • 這時 Label中的值還是重定位之前的,必須要將8785dcf8換為重定位后的rel_a地址
    • 重定位后的Label中的數據 = 0x878042c8(老的Label) + 0x18747000(uboot整體的偏移量) = 0x9ffa4cf8
    • 讀取 rel_a 地址為 ,r3 = 0x9ff4b2b4 + 8 + 12 = 0x9ff4b2c8 ,即為Label地址,然后從Label中讀取到 0x9ffa4cf8,為rel_a重定義后真是地址
  • uboot 中使用 .rel.dyn 段具體實現位置無關碼的原理

    完成這個功能在連接的時候需要加上”-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 段的格式,也就是兩個 4 字節數據為一組。
      高 4 字節是 Label 地址標識 0X17,低 4 字節就是 Label 的地址,
    • 第三行是878042c8,第四行是00000017 。說明878042c8是一個Label。正是上述分析中 存放 rel_a 地址的Label
    • 在重定位后,rel_a 真是地址 = Label內數據 + offset(0x18747000)
  • 2.2.9 relocate_vectors函數

    • 中斷向量表重定位后的地址,就是重定位后uboot的首地址

    2.2.10. board_init_r 函數(板級初始化,進入第二階段)

    board_init_f 并沒有初始化所有的外設,還需要做一些后續工作,這些后續工作就是由函數 board_init_r 來完成的
    uboot relocate后的板級初始化 ,最后執行run_main_loop

    第二階段

    2.2.11 run_main_loop函數

    • run_main_loop-> main_loop->
      • -> bootdelay_process 獲取bootdelay的值,然后保存到stored_bootdelay
        全局變量里面,獲取bootcmd環境變量值,并且將其
        返回
      • -> autoboot_command 參數是bootcmd的值。檢查倒計時內有無打斷
        • -> abortboot 參數為boot delay,此函數會處理倒計時
          • -> abortboot_normal 參數為boot delay,此函數會處理倒計時
        • ->run_command_list函數,參數為s(bootcmd的命令)倒計時結束時hush shell沒輸入執行,啟動內核
      • -> cli_loop 是uboot命令模式處理函數。
        • -> parse_file_outer
          • -> parse_stream_outer
            • -> parse_stream 解析輸入的字符,得到命令
            • -> run_list 運行命令
              • -> run_list_real
                • -> run_pipe_real
                  • -> cmd_process 處理命令,也就是執行命令
    static int run_main_loop(void) {/* main_loop() can return to retry autoboot, if so just run it again */for (;;)main_loop(); // 這里進入了主循環,而autoboot也是在主循環里面實現return 0; }

    進入了main_loop()函數。

    2.2.11 main_loop()函數

    void main_loop(void) {const char *s;bootstage_mark_name(BOOTSTAGE_ID_MAIN_LOOP, "main_loop"); // 這里用于標記uboot的進度,對于tiny210來說起始什么都沒做cli_init(); // cli的初始化,主要是hush模式下的初始化run_preboot_environment_command(); // preboot相關的東西,后續有用到再說明s = bootdelay_process();if (cli_process_fdt(&s))cli_secure_boot_cmd(s);autoboot_command(s); // autoboot的東西,后續使用autoboot的時候再專門說明cli_loop(); // 進入cli的循環模式,也就是命令行模式panic("No CLI available"); }
    • 調用 bootstage_mark_name 函數,打印出啟動進度。
    • 調用 cli_init 函數,跟命令初始化有關,初始化 hush shell 相關的變量。( hush shell 為uboot倒計時結束的輸入shell)
    • run_preboot_environment_command 函數,獲取環境變量 perboot 的內容,perboot
      是一些預啟動命令
    • bootdelay_process 函數,此函數會讀取環境變量 bootdelay (延遲時間)和 bootcmd(啟動內核命令) 的內容,
      然后將 bootdelay 的值賦值給全局變量 stored_bootdelay,返回值為環境變量 bootcmd 的值。
    • autoboot_command(s)函數,此函數就是檢查倒計時是否結束?倒計時結束之前有
      沒有被打斷?輸入參數s為bootcmd的命令
      • 倒計時自然結束執行函數run_command_list,此函數會執行參數 s 指定的一系列命令,也就是環境變量 bootcmd 的命令,bootcmd 里面保存著默認的啟動命令,因此 linux 內核啟動
      • 倒計時結束之前按下了鍵盤上的按鍵,那么 run_command_list函數就不會執行,執行后面的cli_loop函數
    • cli_loop函數,是 uboot 的命令行處理函數

    2.2.12 cli_loop函數

    cli_loop 函數是 uboot 的命令行處理函數,我們在 uboot 中輸入各種命令,進行各種操作就是有 cli_loop 來處理的

    void cli_loop(void) {#ifdef CONFIG_SYS_HUSH_PARSERparse_file_outer();/* This point is never reached */for (;;);#elsecli_simple_loop(); //永遠不會執行#endif /*CONFIG_SYS_HUSH_PARSER*/ }
    • 調用 parse_file_outer函數

    2.2.13 parse_file_outer函數

    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;}
    • 調用函數 setup_file_in_str 初始化變量 input 的成員變量
    • 調用函數 parse_stream_outer,這個函數就是 hush shell 的命令解釋器,負責接收命令行輸入,然后解析并執行相應的命令

    2.3.14 函數 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 循環就是處理輸入命令的
    • 函數 parse_stream 進行命令解析
    • 調用 run_list 函數來執行解析出來的命令,run_list 調用 run_list_real 函數,run_list_real 函數調用 run_pipe_real 函數,run_pipe_real 函數調用 cmd_process 函數。最終通過函數 cmd_process 來處理命令

    2.3.15 補充:uboot的命令

    uboot把所有命令的數據結構都放在一個表格中,我們后續稱之為命令表。表中的每一項代表著一個命令,其項的類型是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;

    參數說明如下:

    name:定義一個命令的名字。 其實就是執行的命令的字符串。這個要注意。
    maxargs:這個命令支持的最大參數
    repeatable:是否需要重復
    cmd:命令處理函數的地址
    usage:字符串,使用說明
    help:字符串,幫助

    Uboot使用U_BOOT_CMD來定義一個命令。CONFIG_CMD_XXX來使能uboot中的某個命令。

    //uboot命令定義代碼 #define U_BOOT_CMD(_name, _maxargs, _rep, _cmd, _usage, _help)

    U_BOOT_CMD最終是定義了一個cmd_tbl_t類型的結構體變量,所有的命令最終都是存放在.u_boot_list段里面。cmd_tbl_t結構體中的cmd成員變量就是具體的命令執行函數,命令執行函數都是do_xxx。

    // bootm就是我們的命令字符串 // 在tiny210.h中定義了最大參數數量是64 // #define CONFIG_SYS_MAXARGS 64 /* max number of command args */ // 1表示重復一次 // 對應命令處理函數是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 );//并且命令處理函數的格式如下: //當命令處理函數執行成功時,需要返回0.返回非0值 int do_bootm(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])

    2.3.16 函數 cmd_process

    函數 cmd_process 來處理命令并執行

    • cmd_process
      • ->find_cmd 從.u_boot_list段里面查找命令,當找到對應的命令以后以返回值的
        形式給出,為cmd_tbl_t類型
      • ->cmd_call
        ->cmdtp->cmd 直接引用cmd成員變量,執行do_xxx函數

    函數 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]);// 第一個參數argv[0]表示命令,調用find_cmd獲取命令對應的表項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;// 檢測參數是否正常/* If OK so far, then do the command */if (!rc) {if (ticks)*ticks = get_timer(0);rc = cmd_call(cmdtp, flag, argc, argv);// 調用cmd_call執行命令表項中的命令,成功的話需要返回0值if (ticks)*ticks = get_timer(*ticks);// 判斷命令執行的時間*repeatable &= cmdtp->repeatable;// 這個命令執行的重復次數存放在repeatable中的}if (rc == CMD_RET_USAGE)rc = cmd_usage(cmdtp);// 命令格式有問題,打印幫助信息return rc; }
    • find_cmd函數, 從.u_boot_list段獲取命令對應的命令表項cmd_tbl_t 。
    • cmd_call函數,執行命令表項cmd_tbl_t 中的命令,執行do_xxx函數

    2.3.17 uboot啟動linux內核(bootz命令)

    在run_main_loop函數倒計時結束時沒有輸入,uboot執行bootcmd命令,啟動內核

    • bootcmd 保存著 uboot 默認命令,這些命令一般都是用來啟動 Linux 內核的,讀取Linux鏡像文件和設備樹文件(從MMC、SD卡、網絡讀取等)到DARM(DDR)中,然后啟動內核(bootz、bootm)。

    • bootm和bootz的不同地方

      • bootm用于加載uImage和ramdisk
        bootm ${kernel_load_address} ${ramdisk_load_address} ${devicetree_load_address}
      • bootz用于加載zImage和ext4文件系統
        bootz ${kernel_load_address} ${ramdisk_load_address} ${devicetree_load_address}
    • bootz的使用(tftp從網絡讀取)

      tftp 80800000 zImage tftp 83000000 imx6ull-14x14-emmc-7-1024x600-c.dtb bootz 80800000 - 83000000

    bootz流程

    2.3.18 do_bootz函數

    do_bootz是bootz的執行函數
    do_bootz

    • -> bootz_start
      • -> do_bootm_states 階段為BOOTM_STATE_START
        • -> bootm_start 對images全局變量清零
      • -> images->ep = 0X80800000
      • ->bootz_setup 判斷zImage是否正確
      • -> bootm_find_images 查找鏡像文件
        • -> boot_get_fdt 找到設備樹,然后將設備樹起始地址和長度,寫入到images的ft_addr和ft_len成員變量中。
      • -> bootm_disable_interrupts 關閉中斷相關
      • -> images.os.os = IH_OS_LINUX; 表示要啟動Linux系統
      • -> do_bootm_states 狀態BOOTM_STATE_OS_PREP 、BOOTM_STATE_OS_FAKE_GO 、
        BOOTM_STATE_OS_GO,
        • -> bootm_os_get_boot_func 查找Linux內核啟動函數。找到Linux內核啟動函數
          do_bootm_linux,賦值給boot_fn。
        • -> boot_fn(BOOTM_STATE_OS_PREP, argc, argv, images); 就是do_bootm_linux。
          • -> boot_prep_linux 啟動之前的一些工作,對于使用設備樹來說,他會將Bootargs傳遞給Linux內核,通過設備樹完成。也就是向Linux內核傳參。
          • -> 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); 啟動Linux內核。Uboot的最終使命,啟動Linux內核。

    2.3.19 kernel_entry函數

    kernel_entry函數是內核的入口函數。內核鏡像第一行代碼

    kernel_entry(0, machid, r2);

    此函數有三個參數:zero,arch,params,

    • 第一個參數 zero 同樣為 0;
    • 第二個參數為機器 ID;
    • 第三個參數啟動參數標記列表(ATAGS)或者設備樹(DTB)首地址,ATAGS 是傳統的方法,用于傳遞一些命令行信息啥的,如果使用設備樹的話就要傳遞設備樹(DTB)。
    • 是匯編函數,因為內核開始是匯編代碼。
    • images->ep 保存著 Linux內核鏡像的起始地址,也就是kernel_entry的地址

    kernel_entry函數的傳參

    • 向匯編函數傳遞參數要使用 r0、r1 和 r2寄存器
      • r0 = 0
      • r1 = 機器類型ID(machid)
        • Linux 內核會在自己的機器 ID 列表里面查找是否存在與 uboot 傳遞進來的 machid 匹配的項目,如果存在就說明 Linux 內核支持這個機器,那么 Linux 就會啟動!
        • 如果使用設備樹的話這個 machid 就無效了,設備樹存有一個“兼容性”這個屬性,Linux 內核會比較“兼容性”屬性的值(字符串)來查看是否支持這個機器。
      • r2 =
        • 如果使用設備樹的話,r2 應該是設備樹的起始地址
        • 如果不使用設備樹的話,r2 應該是 uboot 傳遞給 Linux 的參數起始地址,也就是環境變量 bootargs 的值

    如何從uboot跳轉到內核
    直接修改PC寄存器的值為Linux內核所在的地址,CPU從內核所在的地址去取指令,從而執行內核代碼
    為什么要傳參數給內核
    在此之前, uboot已經完成了硬件的初始化,可以說已經噎適應了“這塊開發板。然而,內核并不是對于所有的開發板都能完美適配的(如果適配了,可想而知這個內核有多龐大,又或者有新技術發明了,可以完美的適配各種開發板),此時,對于開發板的環境一無所知。所以,要想啟動 Linux內核, uboot必須要給內核傳遞一些必要的信息來告訴內核當前所處的環境。

    總結

    以上是生活随笔為你收集整理的uboot启动流程详细分析(基于i.m6ull)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。