uboot分析:uboot啟動內核


(注:本文參考資料:朱有鵬嵌入式課程。本文為個人學習記錄,如有錯誤,歡迎指正。)

1. U-Boot啟動內核概述

  • U-Boot啟動完成后,最終進入到main_loop()循環中。若在bootdelay倒計時為0之前,U-Boot控制台有輸入,則進入命令解析-執行的循環;若控制台無輸入,U-Boot將啟動內核。
  • U-Boot啟動內核可歸結為以下四個步驟:  

                           1)將內核搬移至DDR中;

                           2)校驗內核格式、CRC;

                           3)准備傳參;

                           4)跳轉執行內核。

2. U-Boot啟動內核過程分析

2.1 將內核搬移至DDR中

  • 在/uboot/lib_arm/board.c->start_armboot()函數調用/uboot/common/main.c->main_loop()函數,main_loop()函數中包含了內核的啟動代碼。
s = getenv ("bootcmd");        //獲取bootcmd環境變量的值
debug ("### main_loop: bootcmd=\"%s\"\n", s ? s : "<UNDEFINED>"); if (bootdelay >= 0 && s && !abortboot (bootdelay)) { ................................ #ifndef CFG_HUSH_PARSER        run_command (s, 0);   //執行bootcmd環境變量中的命令
 ................................ }
  • 環境變量bootcmd的值在開發板配置文件(/uboot/include/configs/x210_sd.h)中定義。
#if defined(CFG_FASTBOOT_NANDBSP)
#define CONFIG_BOOTCOMMAND    "nand read C0008000 600000 400000; nand read 30A00000 B00000 180000;  \ bootm C0008000 30A00000" #elif defined(CFG_FASTBOOT_SDMMCBSP)
#define CONFIG_BOOTCOMMAND    "movi read kernel C0008000; movi read rootfs 30A00000 180000; \ bootm C0008000 30A00000" #endif
  • 環境變量bootcmd包含如下命令:
bootcmd=nand read C0008000 600000 400000;  /*將kernel(大小0x00400000字節)從nand中的0x00600000地址處拷貝到DDR中的 0xc0008000地址處*/ nand read 30A00000 B00000 180000; /*將rootfs(大小0x00180000字節)從nand中的0x00B00000地址處拷貝到DDR中的 0x30A08000地址處*/
bootm C0008000 30A00000 //啟動kernel、rootfs
  • U-Boot能准確識別kernel、rootfs在NandFlash中的位置,是因為U-Boot中已對NandFlash進行分區。燒錄時,嚴格按照分區要求將uboot、kernel、rootfs燒錄進NandFlash中即可。
  • 在U-Boot下執行mtd命令,即可查看各個分區的情況。

2.2 校驗內核格式、CRC

  • 內核格式有兩類:zImage和uImage。
  • 並不是所有U-Boot都支持zImage,是否支持就看其配置文件(x210_sd.h)中是否定義CONFIG_ZIMAGE_BOOT這個宏。所以有些uboot是支持zImage啟動的,有些則不支持。但是所有的uboot肯定都支持uImage啟動。
zImage         

Linux內核經過編譯后生成一個ELF格式的可執行文件,vmlinux。再通過arm-linux-objcopy工具進行加工,最后進行壓縮,得到zImage格式的內核鏡像,可以燒錄進啟動介質中。

uImage

uImage是由zImage加工得到的。uboot中的mkimage工具將zImage加工生成uImage來給uboot啟動。這個加工過程其實就是在zImage前面加上64字節的uImage的頭信息即可。

  • ulmage格式的內核頭部信息在/uboot/include/Image.h中定義。
  • ih_load是加載地址,即內核在DDR中的地址(運行地址);ih_ep是內核入口地址。
typedef struct image_header { uint32_t ih_magic; /* Image Header Magic Number */ uint32_t ih_hcrc; /* Image Header CRC Checksum */ uint32_t ih_time; /* Image Creation Timestamp */ uint32_t ih_size; /* Image Data Size */ uint32_t ih_load; /* Data Load Address */ uint32_t ih_ep; /* Entry Point Address */ uint32_t ih_dcrc; /* Image Data CRC Checksum */ uint8_t ih_os; /* Operating System */ uint8_t ih_arch; /* CPU architecture */ uint8_t ih_type; /* Image Type */ uint8_t ih_comp; /* Compression Type */ uint8_t ih_name[IH_NMLEN]; /* Image Name */ } image_header_t;
  • 執行環境變量bootcmd中的命令"bootm  C0008000  30A00000",實質是執行do_bootm()函數(/uboot/common/Cmd_bootm.c->do_bootm())。
  • do_bootm()函數在標號after_header_check之前,都是在校驗內核的頭部信息。根據頭部信息,判斷內核格式和進行CRC校驗。
  • 實際上,從NandFlash中讀取的uImage可以放置在DDR中的任意位置,只要不破壞U-Boot占有的內存空間即可。因為do_bootm()函數內部從頭部獲取內核的加載地址,當發現該uImage當前所處的地址與加載地址不同時,會將內核搬移至加載地址中。一般情況下,都會將內核搬移至加載地址中,便不用使用do_bootm()函數來搬移內核,提高效率。
  • do_bootm()函數將根據系統類型,啟動內核。此處將調用/uboot/lib_arm/Bootm.c/->do_bootm_linux ()函數啟動內核。
after_header_check: os = hdr->ih_os; #endif

switch (os) { default:    /* handled by (original) Linux case */

        case IH_OS_LINUX: #ifdef CONFIG_SILENT_CONSOLE fixup_silent_linux(); #endif do_bootm_linux (cmdtp, flag, argc, argv, &images); break; case IH_OS_NETBSD: do_bootm_netbsd (cmdtp, flag, argc, argv, &images); break; .........................................
 

2.3 准備傳參

  • U-Boot中以標記鏈表(tagged list)形式來傳遞啟動參數。
  • 標記是一種數據結構,標記鏈表就是挨着存放的多個標記。
  • 標記有多種類型,在/uboot/include/asm-arm/Setup.h中定義。標記鏈表以ATAG_CORE開始,以標記ATAG_NONE結束,中間包含其他標記。
#define ATAG_CORE    0x54410001                    //起始標記
#define ATAG_NONE    0x00000000                    //結束標記
#define ATAG_MEM    0x54410002
#define ATAG_VIDEOTEXT    0x54410003
#define ATAG_RAMDISK    0x54410004
#define ATAG_INITRD    0x54410005
#define ATAG_INITRD2    0x54420005
#define ATAG_SERIAL    0x54410006
#define ATAG_REVISION    0x54410007
#define ATAG_VIDEOLFB    0x54410008
#define ATAG_CMDLINE    0x54410009
#define ATAG_ACORN    0x41000101
#define ATAG_MEMCLK    0x41000402
#define ATAG_MTDPART    0x41001099
  • 標記的數據結構定義在/uboot/include/asm-arm/Setup.h中
struct tag_header {   u32 size; //表示標記的類型
  u32 tag;      //表示標記的結構
}; struct tag { struct tag_header hdr; //不同的標記類型使用不同的聯合,每一個標記對應一個數據結構體 union { struct tag_core core; struct tag_mem32 mem; struct tag_videotext videotext; struct tag_ramdisk ramdisk; struct tag_initrd initrd; struct tag_serialnr serialnr; struct tag_revision revision; struct tag_videolfb videolfb; struct tag_cmdline cmdline; /* * Acorn specific */
                struct tag_acorn acorn; /* * DC21285 specific */
                struct tag_memclk memclk; struct tag_mtdpart mtdpart_info; } u; };

標記列表舉例:

static void setup_start_tag (bd_t *bd) { params = (struct tag *) bd->bi_boot_params; params->hdr.tag = ATAG_CORE;          //起始標記
params->hdr.size = tag_size (tag_core); params->u.core.flags = 0; params->u.core.pagesize = 0; params->u.core.rootdev = 0; params = tag_next (params); } #ifdef CONFIG_SETUP_MEMORY_TAGS static void setup_memory_tags (bd_t *bd) { int i; for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) { params->hdr.tag = ATAG_MEM; params->hdr.size = tag_size (tag_mem32); params->u.mem.start = bd->bi_dram[i].start; params->u.mem.size = bd->bi_dram[i].size; params = tag_next (params); } } #endif ..........................................................//中間還有多個標記

static void setup_end_tag (bd_t *bd) { params->hdr.tag = ATAG_NONE;   //結束標記
params->hdr.size = 0; }
  • do_bootm_linux ()函數啟動內核前,先將U-Boot中的啟動參數傳給內核。
  • 參數分析:

1)0 : 相當於mov r0  #0。

2)machid : U-Boot中的機器碼,從全局變量bd中獲取。內核機器碼和U-Boot機器碼必須一致才能啟動內核。

3)bd->bi_boot_params : 啟動參數地址。

void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[], bootm_headers_t *images) { ..................................... //標記鏈表初始化
 setup_start_tag (bd); setup_serial_tag (&params); setup_revision_tag (&params); setup_memory_tags (bd); setup_commandline_tag (bd, commandline); setup_initrd_tag (bd, initrd_start, initrd_end); setup_videolfb_tag ((gd_t *) gd); setup_mtdpartition_tag(); setup_end_tag (bd); ....................................... void    (*theKernel)(int zero, int arch, uint params);       //定義函數指針theKernel
 ...................................... theKernel = (void (*)(int, int, uint))ep;                             //將入口地址賦值給theKernel
 ...................................... theKernel (0, machid, bd->bi_boot_params);                //傳參,調用theKernel
 ...................................... }

2.4 跳轉執行內核

  • theKernel()函數執行成功后,內核將讀取啟動參數,並開始啟動。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM