uboot學習之五-----uboot如何啓動Linux內核

uboot和內核究竟是什麼?
uboot實質就是一個複雜的裸機程序;uboot能夠被配置也能夠作移植;

操做系統內核自己就是一個裸機程序,和咱們學的uboot和其餘裸機程序沒有本質的區別;區別就是咱們操做系統運行起來後能夠分爲應用層和內核層,分層後,兩層的權限不一樣,內存訪問和設備操做的管理上更加精細(內核能夠隨便方位各類硬件,而應用程序只能被限制的訪問硬件和內存地址)

直觀來看:uboot的鏡像是u-boot.bin,Linux系統的鏡像是zImage,這兩個東西其實都是兩個裸機程序鏡像。從系統啓動的角度來說的。內核和uboot就是裸機程序;

部署在SD卡中特定分區內
(1)一個完整的軟件+硬件的嵌入式系統,靜止時(未上電)bootloader、kernel、rootfs等必須的軟件都以鏡像的形式存儲在啓動介質中(x210中是inand/SD卡);運行時都是在DDR內存中運行的,與存儲介質無關。上面兩個狀態都是穩定狀態的,第三個狀態是動態過程,即從靜止態到運行態的過程,也就是啓動過程。也就是這整個過程。
(2)動態啓動過程就是從SD卡逐步搬移到DDR內存,而且運行啓動代碼進行相關硬件初始化和軟件架構的創建,最終達到運行時穩定狀態。
(3)靜止時u-boot.bin zImage rootfs都在SD卡中,他們不可能隨意存在SD卡的任意位置,所以咱們須要對SD卡進行一個分區,而後將各類鏡像各自存在各自的分區中,這樣在啓動過程當中,uboot、內核等就知道到哪裏去找誰。(uboot和內核的分區表必須一致,同時和SD卡的實際使用的分區要一致)

運行時必須先加載到DDR中連接地址處
uboot在第一階段中進行重定位時將第二階段(整個uboot鏡像)加載到DDR中的0xc3e00000地址處,這個地址就是uboot的鏈接地址。
(2)內核也有相似的要求,uboot啓動內核時將內核從SD卡讀取到DDR中(其實就是重定位過程),不能隨意放,必須放在內核的連接地址處,不然啓動不起來,譬如咱們用的內核鏈接地址是0x300080000

內核啓動須要必要的啓動參數
(1)內核是不能開機自動徹底從零開始啓動的,內核啓動須要別人幫忙。uboot要幫助內核實現重定位(從SD卡到DDR )uboot還要給內核提供啓動參數。

啓動內核第一步:加載內核到DDR中
uboot要啓動內核,分爲2個步驟:第一步是將內核鏡像從啓動介質中加載到DDR中,第二步是去DDR中啓動內核鏡像。(內核代碼根本就沒考慮重定位,由於內核知道有bootloader幫忙把本身加載到DDR中連接地址處,內核就直接從連接地址處運行的)

靜態內核鏡像在哪裏?
(1)SD卡、inand、nand、norflash等:raw區
常規啓動時各類鏡像都在SD卡中,所以uboot只須要從啓動介質SD卡的內核分區去讀取內核鏡像到DDR的連接地址處,讀取要使用uboot命令來讀取(例如inand版本是movi命令,x210的nand版本就是nand命令)
(2)這種啓動方式來加載ddr,使用命令:movi read kernel 30008000. 其中kernel指的是uboot中的kernel分區(就是uboot規定SD卡中的一個區域範圍,這個區域範圍被設計來存放kernel鏡像,就是所謂的kernel分區,有時候也叫原始分區,操做系統啓動後能夠用文件系統來管理分區。)
(3)tftp、nfs等網絡下載方式從遠端服務器獲取鏡像
uboot還支持遠程啓動,也就是內核鏡像不燒錄到開發板的SD卡中,而是放在主機的服務器中,而後須要啓動時uboot經過網絡從服務器中下載鏡像到開發板的DDR中。

分析總結:最終結果是內核鏡像到DDR中的特定地址便可。以上兩種方式各有優劣,產品在出廠時會設置從SD卡啓動,客戶不會還有去搭建tftp服務器才能使用,tftp下載遠程啓動,適合開發。


鏡像要放在DDR的什麼地址?
內核必定要放在連接地址處,連接地址去內核源代碼的鏈接腳本或者makefile中去查找。x210中是0x300080000。

zImage和uImage的區別聯繫
bootm命令對應do_boot函數
(1)命令名前加do_,do_bootm在cmd_bootm.c中
(2)do_bootm剛開始定義了一個變量,而後用宏來條件編譯執行了secureboot的一些代碼主要是安全認證。而後到了CONFIG_ZIMAGE_BOOT,用這個宏來控制進行條件編譯的一段代碼,這段代碼是用來支持zImage格式的內核啓動的。

vmlinuz和zImage和uImage
(1)uboot編譯後生成了一個ELF格式的可執行程序u-boot,這個相似於windows下的EXE格式,在操做系統下能夠執行,可是不能用來燒錄下載,咱們用來燒錄下載的是u-boot.bin,它是由u-boot使用arm-linux-objcopy(主要目的是去掉一些無用的)獲得。u-boot.bin就叫鏡像,鏡像就是用來燒錄的,燒錄到inand中執行,或者是放在SD卡中執行,或者dnw下載執行,
(2)Linux kernel通過編譯後也會生成一個ELF格式的可執行程序,叫vmlinuz或者vmlinux,這個就是原始的未經任何處理加工的原版內核ELF文件,嵌入式部署燒錄的通常不是這個vmlinuz,而是使用objcpy工具去製做成燒錄鏡像格式(就是u-boot.bin這種,可是內核沒有.bin後綴)製做的這個燒錄鏡像是Image,製做鏡像主要目的是縮減大小,節儉磁盤。
(3)原則上Image就能夠直接被燒錄到Flash,但實際上並非這麼簡單,實際上Linux的做者們以爲Image太大了,對其進行了壓縮,而且在壓縮後的前一段部分,附加了一部分解壓縮代碼,構成了一個壓縮但是的鏡像就是zImage(由於當年Image大小剛剛比一張軟盤大,(軟盤有2中,1.2M和1.4M,Image比1.4M大一點),爲了節省一張軟盤的錢,因而乎,設計了這種壓縮Image成zImage的技術)
(4)uboot爲了啓動Linux內核,還發明瞭一種內核格式叫uImage。uImage是由zImage加工獲得的,uboot中有一個工具,能夠將zImage加工生產uImage。注意:uImage無論Linux內核的事,Linux內核只管生成zImage便可,而後uboot中的mkimage工具再去有zImage加工生成uImage來給uboot啓動。這個加工的過程是在zImage前面加上64字節的uImage的頭信息便可。
(5)原則上uboot啓動時應該給他uImage格式的內核鏡像,可是實際上uboot也能夠支持zImage,是否支持就看x210_sd.h中是否認義了LINUX_ZIMAG_MAGIC這個宏。因此有些uboot支持zImage啓動,有些則不支持。可是全部的uboot確定都支持uImage啓動。

編譯內核獲得uImage去啓動
(1)第一步:若是直接在kernel底下去make uImage會提示mkimage沒找到,解決方案是在 /uboot/tool下去複製到 /usr/local/bin.下面去,複製它到系統目錄下,再去執行make uImage就能夠了

zImage啓動細節
#ifdef CONFIG_ZIMAGE_BOOT
#define LINUX_ZIMAGE_MAGIC    0x016f2818
    /* 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);
    }


    if (*(ulong *)(addr + 9*4) == LINUX_ZIMAGE_MAGIC) {
        printf("Boot with zImage\n");
        addr = virt_to_phys(addr);
        hdr = (image_header_t *)addr;
        hdr->ih_os = IH_OS_LINUX;
        hdr->ih_ep = ntohl(addr);

        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;
    }
#endif
do_bootm函數一直到397行以前都是進行zImage鏡像的頭部信息校驗。校驗時就要根據不一樣種類的image類型進行不一樣的校驗。因此do_bootm函數的核心就是去分辨傳進來的image究竟是什麼類型,而後按照這種類型的頭信息格式去校驗。校驗經過則進入下一步準備啓動內核;若果校驗失敗則認爲鏡像有問題,因此不能啓動。

#define LINUX_ZIMAGE_MAGIC    0x016f2818
(1)這是一個魔數,0x016f2818表示這個鏡像是zImage,也就是說zImage格式的鏡像中,在頭部的一個固定位置存放了這個數,做爲格式標記,若是咱們拿到了一個image,去他的那個位置去取4個字節,判斷它是否等於這個魔數LINUX_ZIMAGE_MAGIC。則能夠知道這個鏡像是否是zImage
(2)命令bootm 0x30008000,因此do_bootm的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的鏡像,發現真的是存放的這個魔數。

image_header_t
(1)這個數據結構是咱們uboot啓動內核使用的一個標準啓動數據結構,zImage頭信息也是一個image_header_t,可是在實際啓動以前須要進行一些改造,
if (*(ulong *)(addr + 9*4) == LINUX_ZIMAGE_MAGIC) {
        printf("Boot with zImage\n");
        addr = virt_to_phys(addr);
        hdr = (image_header_t *)addr;
        hdr->ih_os = IH_OS_LINUX;
        hdr->ih_ep = ntohl(addr);

        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;

這裏就是在進行改造。
(2)image全局變量是在bootm函數中使用,目的是用來指向 os/initrd/fdt images,也就是用來完成啓動的,zImage校驗過程先肯定是否是zImage,而後再修改zImage頭信息,到合適,也就是上面的改造,最後再用這個頭信息去初始化image,而後完成了校驗。
zImage啓動方式是後來添加的,並且用了goto的方式跳轉了一部分代碼,自己對uboot的結構上添加的。

uImage啓動
(1)在uboot啓動的do_boot中有一個legacy的方法,指的就是uImage這樣的方式,爲何是legacy(遺留的),是由於uImage自己是uboot發明的一種啓動的方式,後來這種方式是很差的,被廢棄,因而被一種新的方式給替代了,新的方式就是設備樹的方式,在這裏被叫作fit,這個就是設備樹的方式。
(2)uImage啓動校驗函數是在boot_get_kernel這個函數裏,主要任務就是校驗咱們的uImage的頭信息,而且獲得真正的kernel的起始位置去啓動。

總結:uboot自己也只支持uImage方式啓動的,後來有了設備樹以後,就把uImage方式命名爲legacy方式,fdt方式就命名爲fit方式,因而乎多了#if  #endif添加代碼。後來移植的人又爲了省事添加了zImage的方式,又爲了省事,添加了#if  #endif .

第二階段校驗頭信息結束,進行第三階段,啓動Linux內核,調用do_bootm_linux這個函數


do_bootm_linux
(1)找到do_bootm_linux,這個函數在lib_arm/bootm.c中,sourceinsight找不到,要搜索查找,

(2)鏡像的entrypoint
ep就是程序入口,一個鏡像的起始部分不是在鏡像的開頭(鏡像的開頭有不少字節的頭信息),真正開始執行的代碼在中間的某個部分,相對於頭有必定偏移量的,這個偏移量放在頭信息中的。
 通常執行一個鏡像都是:第一步先讀取頭信息,而後在頭信息的特定地址找MAGIC_NUM,由此來肯定種類,第二步對鏡像進行校驗,第三步再次讀取頭信息,由頭信息的特定地址知道這個鏡像的各類信息,包括長度,種類,入口地址等等,第四步就是去entrypoint處開始執行鏡像,

theKernel = (void (*)(int, int, uint))ep;將真正的入口地址賦值給thekernel,就是操做系統的第一句代碼


機器碼的再次肯定
uboot在啓動內核時,機器碼要傳給內核。uboot傳給內核的機器碼是怎麼肯定的?第一順序備選的是環境變量machid,第二順序備選是gd->bd->bi_arch_number(x210_sd.h配置的)


傳參並啓動
給內核傳參(do_bootm_linux這裏的110-144行)

Starting kernel ...這裏打印是uboot的最後打印信息,若是這句後,串口沒輸出信息了,說明內核沒有成功被執行,緣由是傳參錯誤(80%),內核在DDR中的加載地址。。。

一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傳參。

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的首地址。

2.7.7.三、移植時注意事項
(1)uboot移植時通常只須要配置相應的宏便可
(2)kernel啓動不成功,注意傳參是否成功。傳參不成功首先看uboot中bootargs設置是否正確,其次看uboot是否開啓了相應宏以支持傳參。html

 

https://www.cnblogs.com/yr-linux/p/5495734.htmllinux

相關文章
相關標籤/搜索