uboot启动内核
1、操作系統運行起來后分為兩內核層和應用層。兩層的權限不同,內核可以直接訪問硬件,而應用層對硬件的訪問遭到了限制。
(uboot的鏡像是u-boot.bin,Linux的鏡像是zImage)
2、SD卡中的特定分區
在沒有上電時,bootloader(引導區)、kernel(內核)、rootfs(文件系統)等必要的軟件都以鏡像的形式存儲在SD中(存儲介質),運行時都是在DDR中,與其他外存介質無關。以上兩種狀態可以認為穩定狀態。而從靜止狀態到運行狀態的過程(啟動過程),可認為是動態過程。啟動過程就是從SD卡中把代碼搬移到DDR內存中運行,這些代碼就是進行相關硬件的初始化和軟件架構的建立,最終達到運行時的穩定狀態。未上電時,uboot.bin、ZImage、rootfs必須在SD中的特定位置存放,也就是說需要對SD卡進行分區,然后將各種鏡像存放在各自的分區中。uboot在啟動內核過程中傳參給內核時,這些分區參數不能傳錯。
3、運行時必須先加載到DDR的連接地址處
uboot在第一階段中進行重定位時將第二階段(整個uboot的鏡像)加載到DDR的0xC3E00000地址處,這個地址就是uboot的連接地址。一樣的,uboot在啟動內核時將內核鏡像從SD卡讀取存到DDR中,此時也必須存放到內核的鏈接地址處,否則啟動不起來。(內核鏈接地址0x30008000)
?
4、內核啟動需要必要的參數
uboot是無條件啟動的,而內核不能,內核需要uboot來幫助啟動。uboot要幫助內核實現重定位、給內核提供啟動參數。
5、啟動內核的第一步:加載內核到DDR中
Uboot如何啟動內核? 1)先將內核鏡像從啟動介質(SD卡)加載到DDR中。 2)到DDR中啟動內核鏡像(uboot的第二階段就是在DDR中運行的)。 內核的代碼不需要考慮重定位,因為這些uboot已經幫做好了,內核只需要從鏈接地址處開始運行就可以了。
6、如何獲取內核鏡像?
1)在SD卡/iNand/Nand/NorFlash等存儲介質。
如SD卡:uboot只需到SD卡kernel分區中讀取內核鏡像到DDR即可。讀取需要用uboot的命令來讀取。(x210的iNand版本是movi命令,Nand版本就是Nand命令)
movi read kernel 30008000
kernel 是SD卡的內核分區,30008000 是內核的鏈接地址。
2)tftp、nfs等網絡下載方式從服務器遠端獲取鏡像。
uboot還支持遠程啟動,也就是內核鏡像不燒錄到開發板的SD卡中等其他介質,而是放在主機的服務器中,然后啟動時uboot通過網絡從服務器中下載鏡像到開發板的DDR中。
tftp 30008000 zImage
7、如何知道內核的鏈接地址?
鏈接地址在內核源代碼的連接腳本或者Makefile中查找。
?
8、zImage和uImage的區別
bootm命令對應do_bootm函數,命令前加do_即可構成這個命令對應的函數,因此執行boot命令時,uboot實際執行的函數叫做do_bootm函數,在cmd_bootm.c文件內。
#if defined(CONFIG_SECURE_BOOT) 這個宏主要用來編譯一些簽名認證的代碼。
#ifdef CONFIG_ZIMAGE_BOOT ?這個條件用來支持zImage格式內核啟動的。
?
9、vmlinuz、zImage和uImage
1)uboot經過編譯生成的elf格式可執行文件為u-boot,類似于windows的exe格式,在操作系統下是可以直接運行的。但不能用來燒錄下載,燒錄時用u-boot.bin(也叫鏡像文件,用來燒錄到iNand中執行的)。U-boot.bin的來源:是由u-boot使用arm-linux-objcopy進行加工得到。
2)linux內核經過編譯后也會生成elf格式的可執行程序,叫vmlinux或vmlinuz。這個是原始未加工的原版內核elf格式文件。vmlinuz和zImage所在的目錄: ?~/x210v3_bsp/kernel/arch/arm/boot ?
在實際燒錄時不是vmlinuz/vmlinux,而是要用objcopy工具去制作成燒錄鏡像格式,Image格式(內核沒有.bin后綴)。Vmlinuz大概有78M而Image只有7.5M。但還是覺得Image太大了,所以實際使用時要對其壓縮,所以就有了zImage壓縮鏡像文件。
uImage和zImage是什么關系?uImage是由uboot對zImage加工得到的,加工的過程就是在zImage前面加上64k字節的頭信息。
uImage不關linux內核的事,linux內核只管生成zImage即可,然后uboot中的mkimage工具再去由zImage加工生成uImage來給uboot啟動。uboot中也可以支持zImage,是否支持就看x210_sd.h中是否定義了LINUX_ZIMAGE_MAGIC這個宏。
?
10、編譯內核得到uImage然后啟動
如果直接在kernel底下去make uImage會出現mkimage command not found。解決方案:去uboot/tools下cp mkimage /usr/local/bin/,復制mkimage工具到系統目錄下。再去make uImage即可。
#if defined(CONFIG_ZIMAGE_BOOT)
after_header_check:
396-397行后,才是真正的zImage啟動,之前都是進行鏡像的頭部信息校驗(判斷是哪種格式等)。
?
11、LINUX_ZIMAGE_MAGIC ?宏定義
#ifdef CONFIG_ZIMAGE_BOOT
#define LINUX_ZIMAGE_MAGIC ?0x016f2818
LINUX_ZIMAGE_MAGIC在這里稱作一個魔數,這個數等于0x016f2818表示這個鏡像是一個zImage。也就是說zImage格式的鏡像中在頭部的一個固定位置存放了這個數作為格式標記。
do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])函數。當執行bootm 0x30008000時,就是執行zImage啟動內核,此時do_bootm的argc=2,argv[0]=bootm ?argv[1]=0x30008000。也可以不帶參進行默認執行,默認執行的連接地址為CFG_LOAD_ADDR,此宏定義在x210_sd.h。如何判斷是zImage文件?zImage頭部開始的第37-40字節處存放著zImage標志魔數,從這個位置取出然后對比LINUX_ZIMAGE_MAGIC。
?
12、image_header_t 結構體
image_header_t *hdr;
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;
這個數據結構是uboot啟動內核時使用的一個標準啟動數據結構。zImage頭信息也是一個image_header_t,但是在實際啟動之前需要進行一些改造。hdr->ih_os = IH_OS_LINUX;
hdr->ih_ep = ntohl(addr);這兩句就是在進行改造。
static bootm_headers_t images; /*pointers to os/initrd/fdt images */
images全局變量是do_bootm函數中使用,用來完成啟動過程的。zImage的校驗過程其實就是先確認是不是zImage,確認后再修改zImage的頭信息到合適,修改后用頭信息去初始化images這個全局變量,然后就完成了校驗。
?
13、uImage啟動
case IMAGE_FORMAT_LEGACY:
type = image_get_type (os_hdr);
comp = image_get_comp (os_hdr);
os = image_get_os (os_hdr);
image_end = image_get_image_end (os_hdr);
load_start = image_get_load (os_hdr);
break;
以上代碼就是用來啟動uImage格式的。uImage方式是uboot本身發明的支持linux啟動的鏡像格式,但是后來這種方式被設備樹方式所取代了(在do_bootm方式中叫FIT)。uImage的啟動校驗主要在boot_get_kernel函數中,主要任務就是校驗uImage的頭信息,并且得到真正的kernel的起始位置去啟動。
uboot設計時只支持uImage啟動,后來有了fdt方式之后,就把uImage方式命令為LEGACY方式,fdt方式命令為FIT方式。第二階段校驗頭信息結束,然后進入第三階段,第三階段主要任務是調用do_bootm_linux函數啟動linux內核。
?
14、do_bootm_linux函數 和 鏡像的入口處entrypoint
此函數在uboot/lib_arm/bootm.c目錄下。ep就是entrypoint的縮寫,就是程序入口。一個鏡像文件的起始執行部分不是在鏡像的開頭(鏡像開頭有n個字節的頭信息),真正的鏡像文件執行時第一句代碼在鏡像的中部某個字節處,相當于頭是有一定的偏移量。一般執行一個鏡像都是:第一步先讀取頭信息,然后在頭信息的特定地址找MAGIC_NUM,由此來確定鏡像種類;第二步對鏡像進行校驗;第三步再次讀取頭信息,由特定地址知道這個鏡像的各種信息(鏡像長度、鏡像種類、入口地址);第四步就去entrypoint處開始執行鏡像。
theKernel = (void (*)(int, int, uint))ep;將ep賦值給theKernel,則這個函數指向就指向了內存中加載的OS鏡像的真正入口地址(就是操作系統的第一句執行的代碼)。
?
#if defined (CONFIG_SETUP_MEMORY_TAGS) || \
????defined (CONFIG_CMDLINE_TAG) || \
????defined (CONFIG_INITRD_TAG) || \
????defined (CONFIG_SERIAL_TAG) || \
????defined (CONFIG_REVISION_TAG) || \
????defined (CONFIG_LCD) || \
????defined (CONFIG_VFD) || \
????defined (CONFIG_MTDPARTITION)
setup_start_tag (bd);
給linux內核準備傳遞的參數處理。
?
?
15、傳參詳解
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傳參。
16、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.3、移植時注意事項
(1)uboot移植時一般只需要配置相應的宏即可
(2)kernel啟動不成功,注意傳參是否成功。傳參不成功首先看uboot中bootargs設置是否正確,其次看uboot是否開啟了相應宏以支持傳參。
總結
- 上一篇: LwIP移植到FreeRTOS(STM3
- 下一篇: Modbus以太网传输方式