uboot啟動核心過程
我們都知道u-boot被締造出來的使命是 啟動核心。
那麼,他是如何完成他的使命的呢!
(1)我們先來分析下Linux核心映象這個概念吧。
我們編譯核心完(編譯成功)會生成vmlinux,Image,zImage,再通過
uboot提供的工具mkimage,執行make uImage會生成uImage,那麼他
們誰是核心映象。如下圖為這3個東東:
vmlinux在kernel根目錄下:
Image和zImage和uImage在根目錄下的arch/arm/boot目錄下:
哪個是映象檔案?三個都是。
vmlinux:這個就是原始的未經任何處理加工的原版核心elf檔案,他一般載入在PC機這種的硬碟中,因為未加工(比較大),所以PC機有大硬碟不怕放不下,所以PC機的上電後直接載入vmlinux核心映象就可以運行了。比如(Ubuntu)。
Image:vmlinux經過objcopy去掉一些不需要的東西之後得到的。
zImage:Image經過壓縮得到的。原則上,Image就可以直接運行了,但是,嵌入式系統要求精簡所以把Image壓縮在加上壓縮演算法放到真正的壓縮映象前面就得到了zImage。如圖可知zImage變小了。
uImage:uImage是經過加64位元組的頭部資訊放到zImage的前面合成得到的。uImage是用於uboot引導啟動時提供的核心映象,所以uboot在編譯成功後,在根目錄下tools/下回提供一個mkimage的工具,將該工具拷貝到系統目錄下,在核心根目錄執行make uImage就可以提供zImage生成uImage。
注:uboot本來只支援uImage的啟動,而編譯核心只負責生成zImage,所以uboot
自己提供了mkimage工具。但是後來uboot也可以支援直接啟動zImage,可通過
uboot的配置檔案中設定對應的巨集開關(CONFIG_ZIMAGE_BOOT)實現。
(2)uboot要啟動核心,說明核心要執行,肯定要進DDR。
那麼uboot提供了那些方法來載入核心映象到DDR?uboot提供了大概2種方法:
【1】通過uboot提供的讀取FLASH的命令從存取核心的介質載入。比如,SD卡,nandflash,iNand等等。
【2】通過網路從伺服器下載核心映象到DDR。比如uboot提供的nfs,tftp伺服器。
(3)校驗核心格式、CRC等
在uboot根目錄下的common/cmd_bootm.c檔案中的函式
int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
該函式就是實現uboot的命令bootm的。
我們在uboot命令列載入核心就是執行:bootm {DDR address}。
do_bootm函式剛開始定義了一些變數,然後用巨集來條件編譯執行了secureboot的一些程式碼(主要進行簽名認證),然後進行了一些一些細節部分操作。我們上面說過,我們可以通過配置巨集來命令uboot載入哪種核心映象。所以uboot是怎麼判斷的呢!
先來zImage啟動:
uboot回去讀取zImage的剛開始部分的資訊和zImage的模數對比是否一致。
- #ifdef CONFIG_ZIMAGE_BOOT
- <span style="color:#000099;">#define LINUX_ZIMAGE_MAGIC 0x016f2818 <span style="background-color: rgb(255, 255, 255);"> //魔數</span></span>
- /* find out kernel image address */
- if (argc < 2) {
- addr = load_addr;
- debug ("* kernel: default image load address = 0x%08lx\n",
- load_addr);
- } else {
- addr = simple_strtoul(argv[1], NULL, 16);
- debug ("* kernel: cmdline image address = 0x%08lx\n", img_addr);
- }
- <span style="color:#000066;">if (*(ulong *)(addr + 9*4) == LINUX_ZIMAGE_MAGIC) { //獲取映象中的魔數 和上面魔數對比</span>
- printf("Boot with zImage\n");
- addr = virt_to_phys(addr);<span style="white-space:pre"> </span>//將DDR地址轉換為實體地址 (使用了MMU)
- hdr = (image_header_t *)addr;
- hdr->ih_os = IH_OS_LINUX;
- hdr->ih_ep = ntohl(addr);<span style="white-space:pre"> </span>//修改填充zImage的頭部資訊結構體
- memmove (&images.legacy_hdr_os_copy, hdr, sizeof(image_header_t));
- /* save pointer to image header */
- images.legacy_hdr_os = hdr;
- images.legacy_hdr_valid = 1;
- goto after_header_check;<span style="white-space:pre"> </span>//跳到該標號處
- }
- #endif
如果那個zImage的巨集沒有定義將會執行uImage或者裝置樹的方式。
取決於genimg_get_format (os_hdr)的結果:
- switch (genimg_get_format (os_hdr)) {
- case IMAGE_FORMAT_LEGACY: <span style="white-space:pre"> </span>···
- case IMAGE_FORMAT_FIT:<span style="white-space:pre"> </span>···}
#define IMAGE_FORMAT_LEGACY 0x01/* legacy image_header based format */ #define IMAGE_FORMAT_FIT 0x02/* new, libfdt based format */
前者是uImage,後者是裝置樹。
(4)選擇對應的OS,設定引數(machid,tag)。
- switch (os) {
- default: /* handled by (original) Linux case */
- case IH_OS_LINUX: //我們是Linux
- do_bootm_linux (cmdtp, flag, argc, argv, &images);
- break;
- case IH_OS_NETBSD:
- do_bootm_netbsd (cmdtp, flag, argc, argv, &images);
- break;
- case IH_OS_RTEMS:
- ···
因為我們是Linux,所以接下里會執行函式:
do_bootm_linux (cmdtp, flag, argc, argv, &images);
裡面主要是設定給核心的傳參。主要是machid和tag。
int machid = bd->bi_arch_number;
或者
s = getenv ("machid");
machid = simple_strtoul (s, NULL, 16);
就是說如果環境變數裡面有則採用環境變數的,否則是配置裡的。
uboot最終是呼叫theKernel函式來執行linux核心的,uboot呼叫這個函式
(其實就是linux核心)時傳遞了3個引數。這3個引數就是uboot直接傳遞
給linux核心的3個引數,通過暫存器來實現傳參的。(第1個引數就放在r0
中,第二個引數放在r1中,第3個引數放在r2中)第1個引數固定為0,第2個
引數是機器碼,第3個引數傳遞的就是大片傳參tag的首地址。
(4)函式指標並傳參(跳轉到執行核心)
theKernel (0, machid, bd->bi_boot_params); /* does not return */
到此,uboot的使命就算完成了,不過核心是否啟動成功,此次啟動過程後
再也沒有uboot的身影了。