U-BOOT全線移植分析系列之四
――U-boot如何引導(dǎo)Linux內(nèi)核啟動?
Sailor_forever sailing_9806@163.com 轉(zhuǎn)載請注明
【摘要】本節(jié)介紹了U-boot使用go或bootm啟動linux內(nèi)核的方法。首先介紹了mkimage的參數(shù)意義和bootm的詳細執(zhí)行流程。然后分析了如何利用mkimage生成內(nèi)核映象的方法。對于bootm方式的內(nèi)核是否壓縮、-a、-e、運行地址等16種組合情況,給出了詳細的測試過程,提出了6種可用方法種的三種最優(yōu)解。
【關(guān)鍵字】:U-boot;AT91RM9200;bootm;mkimage;-a;-e;-c
1) 運行地址?。?/font>鏈接地址0x20008000,不能啟動
Uboot> tftp 21000000 Image;tftp 21100000 ramdisk;go 21000000
。。。。
done
Bytes transferred = 6993691 (6ab71b hex)
## Starting application at 0x21000000 ...
Error: a 在哪提示的?
2) 運行地址=鏈接地址0x20008000,不能啟動,難道是ramdisk的問題
Uboot> tftp 20008000 Image;tftp 21100000 ramdisk;go 20008000
。。。。
done
Bytes transferred = 6993691 (6ab71b hex)
## Starting application at 0x21000000 ...
Error: a
1) 運行地址!=鏈接地址0x20008000,能啟動,內(nèi)核自解壓成功,但是解壓后的內(nèi)核運行錯誤
Uboot> tftp 21000000 zImage;tftp 21100000 ramdisk;go 21000000
。。。。。。。。。。。
done
Bytes transferred = 6993691 (6ab71b hex)
## Starting application at 0x21000000 ...
Uncompressing Linux............................................................. done, booting the kernel.
€?~??鄜屈
2) 運行地址==鏈接地址0x20008000,能啟動,內(nèi)核自解壓成功,但是解壓后的內(nèi)核運行錯誤
Uboot> tftp 20008000 zImage;tftp 21100000 ramdisk; go 20008000
## Starting application at 0x20008000 ...
Uncompressing Linux............................................................. done, booting the kernel.
€?~??鄜屈
上面的ramdisk都是添加了uboot的頭的,去掉頭部再試試。去掉了還是不行,go方法的ramdisk的地址是怎么設(shè)置的??要詳細看下uboot在ramdisk這塊是如何跟內(nèi)核交互的?
通過mkimage這個tool可以給zImage添加一個header:
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;
此header是如何生成的?利用u-boot里面的mkimage工具來生成uImage (u-boot源碼包/tools/mkimage.c )
這里解釋一下參數(shù)的意義:
-A ==> set architecture to 'arch'
-O ==> set operating system to 'os'
-T ==> set image type to 'type' “kernel或是ramdisk”
-C ==> set compression type 'comp'
-a ==> set load address to 'addr' (hex)
-e ==> set entry point to 'ep' (hex)(內(nèi)核啟動時在此位置查詢完整的內(nèi)核印象)
-n ==> set image name to 'name'
-d ==> use image data from 'datafile'
-x ==> set XIP (execute in place,即不進行文件的拷貝,在當前位置執(zhí)行)
對于ARM linux內(nèi)核映象用法:
-A arm -------- 架構(gòu)是arm
-O linux -------- 操作系統(tǒng)是linux
-T kernel -------- 類型是kernel
-C none/bzip/gzip -------- 壓縮類型
-a 20008000 ---- image的載入地址(hex),通常為0xX00008000
-e 200080XX---- 內(nèi)核的入口地址(hex),XX為0x40或者0x00
-n linux-XXX --- image的名字,任意
-d nameXXX ---- 無頭信息的image文件名,你的源內(nèi)核文件
uImageXXX ---- 加了頭信息之后的image文件名,任意取
Bootm命令在/common/cmd_bootm.c中do_bootm函數(shù)
》》》》》》》》》》》獲取當前內(nèi)核的地址,默認地址或者bootm的第一個參數(shù)
默認的加載地址或傳遞給bootm命令(優(yōu)先)與實際的內(nèi)核存放地址需要一致
if (argc < 2) {
addr = load_addr; // load_addr = CFG_LOAD_ADDR;
} else {
addr = simple_strtoul(argv[1], NULL, 16);
}
printf ("## Booting image at %08lx ...\n", addr);
》》》》》》》》》》》》獲得image頭,沒有mkimage的就返回了
memmove (&header, (char *)addr, sizeof(image_header_t));
》》》》》》》》》》》》打印頭部信息
print_image_hdr ((image_header_t *)addr);
實例:
Image Name: dd-kernel-
Image Type: ARM Linux Kernel Image (gzip compressed)
Data Size: 869574 Bytes = 849.2 kB
Load Address: 20008000
Entry Point: 20008000
》》》》》》》》》》》》校驗image頭部
printf (" Verifying Checksum ... "); printf ("OK\n");
》》》》》》》》》》》》檢查image支持的體系結(jié)構(gòu)即—A 選項是否為arm或者ppc等
》》》》》》》》》》》》檢查image的類型
TYPE_MULTI 是否指內(nèi)核與文件系統(tǒng)一起,內(nèi)核后面有個分界線
switch (hdr->ih_type)
case IH_TYPE_KERNEL:
name = "Kernel Image";
break;
case IH_TYPE_MULTI:
》》》》》》》》》》判斷內(nèi)核的壓縮類型
此處的內(nèi)核是否壓縮非zImage和Image的概念,而是指內(nèi)核在被mkimage處理前是否用gunzip等壓縮過
switch (hdr->ih_comp) {
case IH_COMP_NONE: // 非壓縮內(nèi)核
if(ntohl(hdr->ih_load) == addr) {
// 當前內(nèi)核存放的地址與-a指定的一致,則不搬動,-e必須必-a大0x40
printf (" XIP %s ... ", name);
} else {
//當前內(nèi)核存放的地址與-a指定的不一致,則將內(nèi)核搬到-a地址,此時-a與-e必相同
memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len);
。。。。
case IH_COMP_GZIP:
printf (" Uncompressing %s ... ", name);
if (gunzip ((void *)ntohl(hdr->ih_load), unc_len,
//壓縮內(nèi)核,將除去頭部的內(nèi)核解壓到-a 指定的地址了,要求-a與-e相同
// 為防止解壓縮時覆蓋,對于壓縮內(nèi)核,內(nèi)核存放地址最好在—a后面
(uchar *)data, (int *)&len) != 0) {
do_reset (cmdtp, flag, argc, argv);
}
break;
》》》》》》》》》》》》》》》》判斷操作系統(tǒng)類型
switch (hdr->ih_os) {
default: /* handled by (original) Linux case */
case IH_OS_LINUX:
do_bootm_linux (cmdtp, flag, argc, argv, addr, len_ptr, verify);
//前四個為傳給bootm的,addr為內(nèi)核最初的存放地址,沒有用處
break;
#ifdef CONFIG_PPC
static boot_os_Fcn do_bootm_linux;
#else
extern boot_os_Fcn do_bootm_linux;
由上可知,對于ppc和其他體系結(jié)構(gòu)的do_bootm_linux函數(shù)實現(xiàn)是不一樣的
》》》》》》》》》》》》》》啟動Linux內(nèi)核
do_bootm_linux (cmd_tbl_t *cmdtp, int flag,
int argc, char *argv[],
ulong addr,
ulong *len_ptr,
int verify)
》》》》》》》》》》》》獲取命令行參數(shù)
if ((s = getenv("bootargs")) == NULL)
s = "";
strcpy (cmdline, s);
》》》》》》》》》》》》賦內(nèi)核啟動地址
kernel = (void (*)(bd_t *, ulong, ulong, ulong, ulong))hdr->ih_ep;
注意,對于壓縮過的內(nèi)核,會將內(nèi)核解壓到-a指定的地址,此時-a 與-e 地址必須相同
》》》》》》》》》》》判斷bootm的命令參數(shù)中是否有initrd
if (argc >= 3) {
addr = simple_strtoul(argv[2], NULL, 16);
printf ("## Loading RAMDisk Image at %08lx ...\n", addr);
若有initrd則賦值,否則為0
》》》》》》》》》》》》》》》啟動Linux內(nèi)核
/*
* Linux Kernel Parameters:
* r3: ptr to board info data
* r4: initrd_start or 0 if no initrd
* r5: initrd_end - unused if r4 is 0
* r6: Start of command line string
* r7: End of command line string
*/
//*kbd = *(gd->bd); 在上面賦值的
(*kernel) (kbd, initrd _start, initrd_end, cmd_start, cmd_end);
啟動流程的總結(jié):
對于非gzip壓縮的內(nèi)核,bootm命令會首先判斷bootm xxxx 這個指定的地址xxxx是否與-a指定的加載地址相同。
(1)如果不同的話會從這個地址開始提取出這個64byte的頭部,對其進行分析,然后把去掉頭部的內(nèi)核復(fù)制到-a指定的load地址中去運行之(此時-e選型必須同-a)
(2)如果相同的話那就讓其原封不動的放在那,但-e指定的入口地址會推后64byte,以跳過這64byte的頭部。
對于gzip壓縮過的內(nèi)核,因為u-boot要對其解壓,因此運行地址是不能等于-a指定的地址的,且必須有一定的間隔,否則解壓到-a的內(nèi)核會覆蓋當前運行的程序。此時要求-a等于-e指定的地址。
1> mkimage 如何指定入口參數(shù) ( -e 0xxxxxx)
2> mkimage 指定了入口參數(shù)后, 你用tftpboot 下載kernel到哪個地址?
3> -c 如何指定?
u-boot里面的解壓和內(nèi)核自解壓的區(qū)別: u-boot 里面的解壓實際上是bootm 實現(xiàn)的 , 把 mkimage -C bzip2或者gzip 生成的 uImage進行解壓 ; 而kernel的自解壓是對zImage進行解壓,發(fā)生在bootm解壓之后。
U-boot 對內(nèi)核添加頭部時,前面已經(jīng)用gzip壓縮過一次內(nèi)核了,而不是指原有的內(nèi)核印象是否是壓縮內(nèi)核。指uImage 本身被壓縮了,即對原來的zImage/Image添加了U-boot的壓縮方式,使得生成的uImage變小了。此時-c gzip
若沒有對zImage/Image用gzip命令壓縮過,則-c none。
綜合上面分析,mkimage的影響因子為:
-e,內(nèi)核的入口地址是否與-a相同
Tftpaddr,即將內(nèi)核加載到RAM中運行的地址,決定是否搬運或解壓內(nèi)核
-c,內(nèi)核是否經(jīng)過gzip壓縮過,決定了是搬運還是解壓
另外內(nèi)核本身為非壓縮的Image或zImage也是一個影響因子。組合情況共2^4 =16種
(1)Mkimage 之前用gzip對Image進行壓縮
<1> -a=-e = 0x20008000,tftpaddr= 0x21000000
解壓到-a指定的地址,成功啟動
Uboot> tftp 21000000 uImage-zip-8000;tftp 21100000 ramdisk;bootm 21000000
## Booting image at 21000000 ...
Image Name: dd-kernel-
Image Type: ARM Linux Kernel Image (gzip compressed)
Data Size: 869629 Bytes = 849.2 kB
Load Address: 20008000
Entry Point: 20008000
Verifying Checksum ... OK
Uncompressing Kernel Image ... OK
Starting kernel ...
Linux version
AT91RM9200DK login: root
<2> -a=-e = 0x20008000, tftpaddr= 0x20008000
解壓失敗,啟動失敗
Uboot> tftp 20008000 uImage-zip-zImage-8000;tftp 21100000 ramdisk;bootm 20008000
Uboot> tftp 20008000 uImage-zip-8000;tftp 21100000 ramdisk;bootm 20008000
## Booting image at 20008000 ...
Image Name: dd-kernel-
Image Type: ARM Linux Kernel Image (gzip compressed)
Data Size: 869629 Bytes = 849.2 kB
Load Address: 20008000
Entry Point: 20008000
Verifying Checksum ... OK
Uncompressing Kernel Image ... Error: inflate() returned -3
GUNZIP ERROR - must RESET board to recover
由于當前運行地址tftpaddr與解壓縮后的地址-a重合了,導(dǎo)致解壓縮失敗,因此二者必須相隔一定的距離
<3> -a=0x20008000,-e = 0x20008040 ,tftpaddr= 0x21000000
能夠解壓到-a地址,但-e指定的入口不對,啟動失敗
Uboot> tftp 21000000 uImage-zip-8040;tftp 21100000 ramdisk;bootm 21000000
TFTP from server 192.168.0.12; our IP address is 192.168.0.15
Filename 'uImage-zip-8040'.
Load address: 0x21000000
。。。。。。。。。
## Booting image at 21000000 ...
Image Name: dd-kernel-
Image Type: ARM Linux Kernel Image (gzip compressed)
Data Size: 869629 Bytes = 849.2 kB
Load Address: 20008000
Entry Point: 20008040
Verifying Checksum ... OK
Uncompressing Kernel Image ... OK
Starting kernel ... 死了
<4> -a=-e = 0x20008000, tftpaddr= 0x20008000
解壓失敗,入口也不對,啟動失敗
(2)Mkimage 之前未對Image進行壓縮
<1> -a=-e = 0x20008000 tftpaddr= 0x21000000
搬動到-a指定的地址,成功啟動
Uboot> tftp 21000000 uImage-nzip-8000;tftp 21100000 ramdisk;bootm 21000000
## Booting image at 21000000 ...
Image Name: dd-kernel-
Image Type: ARM Linux Kernel Image (uncompressed)
Data Size: 1873059 Bytes = 1.8 MB
Load Address: 20008000
Entry Point: 20008000
Verifying Checksum ... Bad Data CRC
為什么總是校驗失敗呢?當前的內(nèi)核印象為
下面未拷貝ramdisk,校驗成功,成功啟動,無法安裝跟文件系統(tǒng),是因為無ramdisk。說明上面確實是覆蓋了,因此要對于大的內(nèi)核印象要合理設(shè)置tftpaddr的地址和ramdisk的地址
Addr(ramdisk)= 0x2110 0000
Addr(tftpaddr)= 0x2100 0000
Addr(ramdisk)-Addr(tftpaddr)= 0x10 0000 =
Uboot> tftp 21000000 uImage-nzip-8000;bootm 21000000
## Booting image at 21000000 ...
Image Name: dd-kernel-
Image Type: ARM Linux Kernel Image (uncompressed)
Data Size: 1873059 Bytes = 1.8 MB
Load Address: 20008000
Entry Point: 20008000
Verifying Checksum ... OK
OK
Starting kernel ...
Linux version
。。。。。。。
Kernel panic: VFS: Unable to mount root fs on 01:00
Addr(ramdisk)-
Uboot> tftp
## Booting image at
Image Name: dd-kernel-
Image Type: ARM Linux Kernel Image (uncompressed)
Data Size: 1873059 Bytes = 1.8 MB
Load Address: 20008000
Entry Point: 20008000
Verifying Checksum ... OK
OK
Starting kernel ...
Linux version
AT91RM9200DK login: root
<2> -a=-e = 0x20008000, tftpaddr= 0x20008000
不搬動,但-e地址不對,失敗
<3> -a=0x20008000,-e = 0x20008040 ,tftpaddr= 0x20008000
不搬動,但未成功啟動,入口地址對的???????
Uboot> tftp 20008000 uImage-nzip-8040;tftp 21100000 ramdisk;bootm 20008000
## Booting image at 20008000 ...
Image Name: dd-kernel-
Image Type: ARM Linux Kernel Image (uncompressed)
Data Size: 1873059 Bytes = 1.8 MB
Load Address: 20008000
Entry Point: 20008040
Verifying Checksum ... OK
XIP Kernel Image ... OK
Starting kernel ...死了????
<4> -a=0x20008000,-e = 0x20008040 ,tftpaddr= 0x21000000
搬動,但-e地址不對,失敗
(1)Mkimage 之前用gzip對zImage進行壓縮,即-c gzip
<1> -a=-e = 0x20008000,tftpaddr= 0x21000000
解壓到-a指定的地址,成功啟動
Uboot> tftp 21000000 uImage-zip-zImage-8000;tftp 21100000 ramdisk;bootm 21000000
TFTP from server 192.168.0.12; our IP address is 192.168.0.15
Filename 'uImage-zip-zImage-8000'.
Load address: 0x21000000
## Booting image at 21000000 ...
Image Name: dd-zip-zImage-8000
Image Type: ARM Linux Kernel Image (gzip compressed)
Data Size: 876753 Bytes = 856.2 kB
Load Address: 20008000
Entry Point: 20008000
Verifying Checksum ... OK
Uncompressing Kernel Image ... OK // U-boot對內(nèi)核解壓
Starting kernel ...
Uncompressing Linux..............壓縮內(nèi)核zImage自解壓......................... done, booting the kernel.
Linux version
AT91RM9200DK login: root
[root@AT91RM9200DK /root]$ls
<2> -a=-e = 0x20008000, tftpaddr= 0x20008000
解壓失敗,啟動失敗
Uboot> tftp 20008000 uImage-zip-zImage-8000;tftp 21100000 ramdisk;bootm 20008000
TFTP from server 192.168.0.12; our IP address is 192.168.0.15
Filename 'uImage-zip-zImage-8000'.
Load address: 0x20008000
## Booting image at 20008000 ...
Image Name: dd-zip-zImage-8000
Image Type: ARM Linux Kernel Image (gzip compressed)
Data Size: 876753 Bytes = 856.2 kB
Load Address: 20008000
Entry Point: 20008000
Verifying Checksum ... OK
Uncompressing Kernel Image ... Error: inflate() returned -3
GUNZIP ERROR - must RESET board to recover
由于當前運行地址tftpaddr與解壓縮后的地址-a重合了,導(dǎo)致解壓縮失敗,因此二者必須相隔一定的距離
<3> -a=0x20008000,-e = 0x20008040 ,tftpaddr= 0x21000000,失敗
Uboot> tftp 21000000 uImage-zip-zImage-8040;tftp 21000000 ramdisk;bootm 21000000
TFTP from server 192.168.0.12; our IP address is 192.168.0.15
Filename 'uImage-zip-zImage-8040'.
Load address: 0x21000000
。。。。。。。。。
## Booting image at 21000000 ...
Bad Magic Number 難道對于壓縮內(nèi)核,幻數(shù)對的條件是-a==-e地址??即壓縮內(nèi)核默認-a==-e??
此法肯定失敗,但問題出在這,還真不對啊,不試了,感興趣的朋友可以玩下
(2)Mkimage 之前未對zImage進行壓縮,即-c none
<1> -a=-e = 0x20008000 tftpaddr= 0x21000000
搬動到-a指定的地址,成功啟動
Uboot> tftp 21000000 uImage-nzip-zImage-8000;tftp 21100000 ramdisk;bootm 21000000
## Booting image at 21000000 ...
Image Name: dd-nzip-zImage-8000
Image Type: ARM Linux Kernel Image (uncompressed)
Data Size: 881748 Bytes = 861.1 kB
Load Address: 20008000
Entry Point: 20008000
Verifying Checksum ... OK
OK
Starting kernel ...
Uncompressing Linux............................................................. done, booting the kernel.
Linux version
AT91RM9200DK login:
<2> -a=-e = 0x20008000, tftpaddr= 0x20008000
不搬動,但-e地址不對,失敗
Uboot> tftp 20008000 uImage-nzip-zImage-8000;tftp 21100000 ramdisk;bootm 20008000
## Booting image at 20008000 ...
Image Name: dd-nzip-zImage-8000
Image Type: ARM Linux Kernel Image (uncompressed)
Data Size: 881748 Bytes = 861.1 kB
Load Address: 20008000
Entry Point: 20008000
Verifying Checksum ... OK
XIP Kernel Image ... OK
Starting kernel ... 死了。。。
<3> -a=0x20008000,-e = 0x20008040 ,tftpaddr= 0x20008000
不搬動,成功啟動
Uboot> tftp 20008000 uImage-nzip-zImage-8040;tftp 21100000 ramdisk;bootm 20008000
## Booting image at 20008000 ...
Image Name: dd-nzip-zImage-8040
Image Type: ARM Linux Kernel Image (uncompressed)
Data Size: 881748 Bytes = 861.1 kB
Load Address: 20008000
Entry Point: 20008040
Verifying Checksum ... OK
XIP Kernel Image ... OK
Starting kernel ...
Uncompressing Linux............................................................. done, booting the kernel.
Linux version
AT91RM9200DK login:
<4> -a=0x20008000,-e = 0x20008040 ,tftpaddr= 0x21000000
搬動,但-e地址不對,失敗
由上面的16個例子,我們可以看出,能夠啟動內(nèi)核的由以下幾種情況:
各種情況對應(yīng)的統(tǒng)一ramdiskaddr= 0x21100000
<1>非壓縮的Image內(nèi)核:
-a=-e = 0x20008000 ,–c=none,tftpaddr= 0x
此法主要由于內(nèi)核太大,導(dǎo)致tftpaddr做了一定的修正
-a= 0x20008000 ,-e = 0x20008040,–c=none,tftpaddr=0x20008000
此法理論上可行,但我未試驗成功,有興趣的朋友可以探究下
對于非壓縮的Image內(nèi)核,mkimage之前不壓縮的話,內(nèi)核印象較大,此法不常用
-a=-e = 0x20008000 ,–c=gzip,tftpaddr= 0x21000000
–c=gzip壓縮內(nèi)核必須解壓,只有這種情況成功;其他解壓覆蓋或者-e入口不對
<2>壓縮的zImage內(nèi)核:
-a=-e = 0x20008000 ,–c=none,tftpaddr= 0x21000000
-a= 0x20008000 ,-e = 0x20008040,–c=none,tftpaddr=0x20008000
-a=-e = 0x20008000 ,–c=gzip,tftpaddr= 0x21000000
–c=gzip壓縮內(nèi)核必須解壓,只有這種情況成功;其他解壓覆蓋或者-e入口不對
zImage已經(jīng)壓縮過一次了,一般無需再壓縮,此法不常用
常見方法:
<1>非壓縮的Image內(nèi)核:
-a=-e = 0x20008000 ,–c=gzip,tftpaddr= 0x21000000
<2>壓縮的zImage內(nèi)核:
-a=-e = 0x20008000 ,–c=none,tftpaddr= 0x21000000
-a= 0x20008000 ,-e = 0x20008040,–c=none,tftpaddr=0x20008000
待續(xù):
U-boot如何向Linux內(nèi)核傳遞命令行參數(shù)?
Go引導(dǎo)內(nèi)核的詳細方法?
Ramdisk與initrd怎么傳給內(nèi)核?