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.3、移植時注意事項
(1)uboot移植時一般只需要配置相應的宏即可
(2)kernel啟動不成功,注意傳參是否成功。傳參不成功首先看uboot中bootargs設置是否正確,其次看uboot是否開啟了相應宏以支持傳參。
https://www.cnblogs.com/yr-linux/p/5495734.html