1、摘要
(1)、啟動4步驟
第一步:將內核搬移到DDR中
第二步:校驗內核格式、CRC等
第三步:准備傳參
第四步:跳轉執行內核
(2)、涉及到的主要函數是:do_bootm和do_bootm_linux
(3)、uboot能啟動的內核格式:zImage uImage fdt方式(設備樹)
(4)、跳轉與函數指針的方式運行內核
2、vmlinuz和zImage和uImage
(1)uboot經過編譯直接生成的elf格式的可執行程序是u-boot,這個程序類似於windows下的exe格式,在操作系統下是可以直接執行的。但是這種格式不能用來燒錄下載。我們用來燒錄下載的是u-boot.bin,這個東西是由u-boot使用arm-linux-objcopy工具進行加工(主要目的是去掉一些無用的)得到的。這個u-boot.bin就叫鏡像(image),鏡像就是用來燒錄到iNand中執行的。
(2)linux內核經過編譯后也會生成一個elf格式的可執行程序,叫vmlinux或vmlinuz,這個就是原始的未經任何處理加工的原版內核elf文件;嵌入式系統部署時燒錄的一般不是這個vmlinuz/vmlinux,而是要用objcopy工具去制作成燒錄鏡像格式(就是u-boot.bin這種,但是內核沒有.bin后綴),經過制作加工成燒錄鏡像的文件就叫Image(制作把78M大的精簡成了7.5M,因此這個制作燒錄鏡像主要目的就是縮減大小,節省磁盤)。
(3)原則上Image就可以直接被燒錄到Flash上進行啟動執行(類似於u-boot.bin),但是實際上並不是這么簡單。實際上linux的作者們覺得Image還是太大了所以對Image進行了壓縮,並且在image壓縮后的文件的前端附加了一部分解壓縮代碼。構成了一個壓縮格式的鏡像就叫zImage。(因為當年Image大小剛好比一張軟盤(軟盤有2種,1.2M的和1.44MB兩種)大,為了節省1張軟盤的錢於是乎設計了這種壓縮Image成zImage的技術)。
(4)uboot為了啟動linux內核,還發明了一種內核格式叫uImage。uImage是由zImage加工得到的,uboot中有一個工具,可以將zImage加工生成uImage。注意:uImage不關linux內核的事,linux內核只管生成zImage即可,然后uboot中的mkimage工具再去由zImage加工生成uImage來給uboot啟動。這個加工過程其實就是在zImage前面加上64字節的uImage的頭信息即可。
(4)原則上uboot啟動時應該給他uImage格式的內核鏡像,但是實際上uboot中也可以支持zImage,是否支持就看x210_sd.h中是否定義了LINUX_ZIMAGE_MAGIC這個宏。所以大家可以看出:有些uboot是支持zImage啟動的,有些則不支持。但是所有的uboot肯定都支持uImage啟動。
(5)如果直接在kernel底下去make uImage會提供mkimage command not found。解決方案是去uboot/tools下cp mkimage /usr/local/bin/,復制mkimage工具到系統目錄下。再去make uImage即可。
3、do_bootm函數
一般情況bootm命令對應do_bootm函數。如果在do前加下划線_do_xxx表示為內部調用函數
(1)命令名前加do_即可構成這個命令對應的函數,因此當我們bootm命令執行時,uboot實際執行的函數叫do_bootm函數,在cmd_bootm.c。
(2)do_bootm剛開始定義了一些變量,然后用宏來條件編譯執行了secureboot的一些代碼(主要進行簽名認證),先不管他;然后進行了一些一些細節部分操作,也不管他。然后到了CONFIG_ZIMAGE_BOOT,用這個宏來控制進行條件編譯一段代碼,這段代碼是用來支持zImage格式的內核啟動的。
4、zImage啟動細節
(1)do_bootm函數中一直到397行的after_header_check這個符號處,都是在進行鏡像的頭部信息校驗。校驗時就要根據不同種類的image類型進行不同的校驗。所以do_bootm函數的核心就是去分辨傳進來的image到底是什么類型,然后按照這種類型的頭信息格式去校驗。校驗通過則進入下一步准備啟動內核;如果校驗失敗則認為鏡像有問題,所以不能啟動。
LINUX_ZIMAGE_MAGIC
(1)這個是一個定義的魔數,這個數等於0x016f2818,表示這個鏡像是一個zImage。也就是說zImage格式的鏡像中在頭部的一個固定位置存放了這個數作為格式標記。如果我們拿到了一個image,去他的那個位置去取4字節判斷它是否等於LINUX_ZIMAGE_MAGIC,則可以知道這個鏡像是不是一個zImage。
(2)命令 bootm 0x30008000,所以do_boom的argc=2,argv[0]=bootm argv[1]=0x30008000。但是實際bootm命令還可以不帶參數執行。如果不帶參數直接bootm,則會從CFG_LOAD_ADDR地址去執行(定義在x210_sd.h中)。
(3)zImage頭部開始的第37-40字節處存放着zImage標志魔數,從這個位置取出然后對比LINUX_ZIMAGE_MAGIC。可以用二進制閱讀軟件來打開zImage查看,就可以證明。很多軟件都可以打開二進制文件,如winhex、UltraEditor。
image_header_t
(1)這個數據結構是我們uboot啟動內核使用的一個標准啟動數據結構,zImage頭信息也是一個image_header_t,但是在實際啟動之前需要進行一些改造。hdr->ih_os = IH_OS_LINUX;
hdr->ih_ep = ntohl(addr);這兩句就是在進行改造。
(2)images全局變量是do_bootm函數中使用,用來完成啟動過程的。zImage的校驗過程其實就是先確認是不是zImage,確認后再修改zImage的頭信息到合適,修改后用頭信息去初始化images這個全局變量,然后就完成了校驗。
5、uImage啟動
(1)LEGACY(遺留的),在do_bootm函數中,這種方式指的就是uImage的方式。
(2)uImage方式是uboot本身發明的支持linux啟動的鏡像格式,但是后來這種方式被一種新的方式替代,這個新的方式就是設備樹方式(在do_bootm方式中叫FIT)
(3)uImage的啟動校驗主要在boot_get_kernel函數中,主要任務就是校驗uImage的頭信息,並且得到真正的kernel的起始位置去啟動。
總結1:uboot本身設計時只支持uImage啟動,原來uboot的代碼也是這樣寫的。后來有了fdt方式之后,就把uImage方式命令為LEGACY方式,fdt方式命令為FIT方式,於是乎多了寫#if #endif添加的代碼。后來移植的人又為了省事添加了zImage啟動的方式,又為了省事把zImage啟動方式直接寫在了uImage和fdt啟動方式之前,於是乎又有了一對#if #endif。於是乎整天的代碼看起來很惡心。
總結2:第二階段校驗頭信息結束,下面進入第三階段,第三階段主要任務是啟動linux內核,調用do_bootm_linux函數來完成。
6、啟動
(1)ep就是entrypoint的縮寫,就是程序入口。一個鏡像文件的起始執行部分不是在鏡像的開頭(鏡像開頭有n個字節的頭信息),真正的鏡像文件執行時第一句代碼在鏡像的中部某個字節處,相當於頭是有一定的偏移量的。這個偏移量記錄在頭信息中。
(2)一般執行一個鏡像都是:第一步先讀取頭信息,然后在頭信息的特定地址找MAGIC_NUM,由此來確定鏡像種類;第二步對鏡像進行校驗;第三步再次讀取頭信息,由特定地址知道這個鏡像的各種信息(鏡像長度、鏡像種類、入口地址);第四步就去entrypoint處開始執行鏡像。
(3)theKernel = (void (*)(int, int, uint))ep;將ep賦值給theKernel,則這個函數指向就指向了內存中加載的OS鏡像的真正入口地址(就是操作系統的第一句執行的代碼)。
7、傳參准備
(1)uboot在啟動內核時,機器碼要傳給內核。uboot傳給內核的機器碼是怎么確定的?第一順序備選是環境變量machid,第二順序備選是gd->bd->bi_arch_num(x210_sd.h中硬編碼配置的)
(2)從110行到144行就是uboot在給linux內核准備傳遞的參數處理。(uboot/lib_arm/bootm.c)
(2)Starting kernel ... 這個是uboot中最后一句打印出來的東西。這句如果能出現,說明uboot整個是成功的,也成功的加載了內核鏡像,也校驗通過了,也找到入口地址了,也試圖去執行了。如果這句后串口就沒輸出了,說明內核並沒有被成功執行。原因一般是:傳參(80%)、內核在DDR中的加載地址·······
7、傳參詳解
(1)、tag方式傳參
(1)struct tag,tag是一個數據結構,在uboot和linux kernel中都有定義tag數據機構,而且定義是一樣的。
(2)tag_header和tag_xxx。tag_header中有這個tag的size和類型編碼,kernel拿到一個tag后先分析tag_header得到tag的類型和大小,然后將tag中剩余部分當作一個tag_xxx來處理。
(3)tag_start與tag_end。kernel接收到的傳參是若干個tag構成的,這些tag由tag_start起始,到tag_end結束。
(4)tag傳參的方式是由linux kernel發明的,kernel定義了這種向我傳參的方式,uboot只是實現了這種傳參方式從而可以支持給kernel傳參。
(2)、x210_sd.h中配置傳參宏
(1)CONFIG_SETUP_MEMORY_TAGS,tag_mem,傳參內容是內存配置信息。
(2)CONFIG_CMDLINE_TAG,tag_cmdline,傳參內容是啟動命令行參數,也就是uboot環境變量的bootargs.
(3)CONFIG_INITRD_TAG
(4)CONFIG_MTDPARTITION,傳參內容是iNand/SD卡的分區表。
(5)起始tag是ATAG_CORE、結束tag是ATAG_NONE,其他的ATAG_XXX都是有效信息tag。
思考:內核如何拿到這些tag?
uboot最終是調用theKernel函數來執行linux內核的,uboot調用這個函數(其實就是linux內核)時傳遞了3個參數。這3個參數就是uboot直接傳遞給linux內核的3個參數,通過寄存器來實現傳參的。(第1個參數就放在r0中,第二個參數放在r1中,第3個參數放在r2中)第1個參數固定為0,第2個參數是機器碼,第3個參數傳遞的就是大片傳參tag的首地址。
(3)、移植時注意事項
(1)uboot移植時一般只需要配置相應的宏即可
(2)kernel啟動不成功,注意傳參是否成功。傳參不成功首先看uboot中bootargs設置是否正確,其次看uboot是否開啟了相應宏以支持傳參。