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