U-BOOT 全線移植分析系列之四 ―― U-boot 如何引導Linux 內核啓動?
Sailor_forever [email protected] 轉載請註明
http://blog.csdn.net/sailor_8318/archive/2008/08/05/2773412.aspx
【摘要】本節介紹了 U-boot 使用 go 或 bootm 啓動 linux 內核的方法。首先 介紹了 mkimage 的參數意義和 bootm 的詳細執行流程。然後分析瞭如何利用 mkimage 生成內核映象的方法。 對於 bootm 方式的內核是否壓縮、- a 、- e 、運行地址等 16 種組合情況,給出了詳細的測試過程,提出了 6 種可用方法種的三種最優解。
【關鍵字】: U-boot ; AT91RM9200 ; bootm ; mkimage ;- a ;- e ;- c
四 U-boot 如何引導 Linux 內核啓動?
4.1 GO 命令引導未用 mkimage 生成的內核
1) 運行地址!= 鏈接地址 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 ,能啓動,內核自解壓成功, 但是解壓後的內核運行錯誤
Uboot> tftp 21000000 zImage;tftp 21100000 ramdisk;go 21000000
。。。。。。。。。。。
done
Bytes transferred = 6993691 (6ab71b hex)
## Starting application at 0x21000000 ...
Uncompressing Linux............................................................. don e, booting the kernel.
€?~?? 鄜屈
2) 運行地址==鏈接地址 0x20008000 ,能啓動,內核自解壓成功, 但是解壓後的內核運行錯誤
Uboot> tftp 20008000 zImage;tftp 21100000 ramdisk; go 20008000
## Starting application at 0x20008000 ...
Uncompressing Linux............................................................. done, booting the kernel .
€?~?? 鄜屈
上面的 ramdisk 都是添加了 uboot 的頭的,去掉頭部再試試。去掉了還是不行, go 方法的 ramdisk 的地址是怎麼設置的??要詳細看下 uboot 在 ramdisk 這塊是如何跟內核交互的?
4.2 Mkimage 參數意義解析
通過 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 )
這裏解釋一下參數的意義:
-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 ==> set image name to 'name'
-d ==> use image d ata from 'datafile'
-x ==> set XIP (execute in place ,即不進行文件的拷貝,在當前位置執行 )
對於 ARM linux 內核映象用法:
-A arm --------
架構是
arm
-O linux --------
操作系統是
linux
-T kernel --------
類型是
kernel
-C none/bzip/gzip --------
壓縮類型
-a 20008000 ---- image
的載入地址
(hex)
,通常爲
0xX00008000
-e 200080XX----
內核的入口地址
(hex)
,
XX
爲
0x40
或者
0x00
-n linux-XXX --- image
的名字,任意
-d nameXXX ----
無頭信息的
image
文件名,你的源內核文件
uImageXXX ----
加了頭信息之後的
image
文件名,任意取
4.3 Bootm 的流程分析
Bootm 命令在 /common/cmd_bootm.c 中 do_bootm 函數
》》》》》》》》》》》獲取當前內核的地址,默認地址或者 bootm 的第一個參數
默認的加載地址或傳遞給 bootm 命令(優先)與實際的內核存放地址需要一致
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-2.4.19
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 支持的體系結構即 —A 選項是否爲 arm 或者 ppc 等
》》》》》》》》》》》》檢查 image 的類型
TYPE_MULTI 是否指內核與文件系統一起,內核後面有個分界線
switch (hdr->ih_type)
case IH_TYPE_KERNEL:
name = "Kernel Image";
break;
case IH_TYPE_MULTI:
》》》》》》》》》》判斷內核的壓縮類型
此處的內核是否壓縮非 zImage 和 Image 的概念,而是指內核在被 mkimage 處理前是否用 gunzip 等壓縮過
switch (hdr->ih_comp) {
case IH_COMP_NONE: // 非壓縮內核
if(ntohl(hdr->ih_load) == addr) {
// 當前內核存放的地址與- a 指定的一致,則不搬動,- e 必須必- a 大 0x40
printf (" XIP %s ... ", name);
} else {
// 當前內核存放的地址與- a 指定的不一致,則將內核搬到- 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,
// 壓縮內核,將除去頭部的內核解壓到 -a 指定的地址了,要求- a 與- e 相同
// 爲防止解壓縮時覆蓋,對於壓縮內核,內核存放地址最好在 —a 後面
(uchar *)data, (int *)&len) != 0) {
do_reset (cmdtp, flag, argc, argv);
}
break;
》》》》》》》》》》》》》》》》判斷操作系統類型
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 爲內核最初的存放地址,沒有用處
break;
#ifdef CONFIG_PPC
static boot_os_Fcn do_bootm_linux;
#else
extern boot_os_Fcn do_bootm_linux;
由上可知,對於 ppc 和其他體系結構的 do_bootm_linux 函數實現是不一樣的
》》》》》》》》》》》》》》啓動 Linux 內核
do_bootm_linux (cmd_tbl_t *cmdtp, int flag,
int argc, char *argv[],
ulong addr,
ulong *len_ptr,
int verify)
》》》》》》》》》》》》獲取命令行參數
if ((s = getenv("bootargs")) == NULL)
s = "";
strcpy (cmdline, s);
》》》》》》》》》》》》賦內核啓動地址
kernel = (void (*)(bd_t *, ulong, ulong, ulong, ulong)) hdr->ih_ep;
注意,對於壓縮過的內核,會將內核解壓到 -a 指定的地址, 此時 -a 與 -e 地址必須相同
》》》》》》》》》》》判斷 bootm 的命令參數中是否有 initrd
if (argc >= 3) {
addr = simple_strtoul(argv[2], NULL, 16);
printf ("## Loading RAMDisk Image at %08lx .../n", addr);
若有 initrd 則賦值,否則爲 0
》》》》》》》》》》》》》》》啓動 Linux 內核
/*
* 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);
啓動流程的總結:
對於 非 gzip 壓縮的內核 , bootm 命令會首先判斷 bootm xxxx 這個指定的地址 xxxx 是否與 -a 指定的加載地址相同。
(1) 如果不同的話會從這個地址開始提取出這個 64byte 的頭部,對其進行分析,然後把去掉頭部的內核複製到 -a 指定的 load 地址中去運行之(此時- e 選型必須同 -a )
(2) 如果相同的話那就讓其原封不動的放在那,但 -e 指定的入口地址會推後 64byte ,以跳過這 64byte 的頭部。
對於 gzip 壓縮過的內核,因爲 u-boot 要對其解壓, 因此運行地址是不能等於- a 指定的地址的,且必須有一定的間隔, 否則解壓到- a 的內核會覆蓋當前運行的程序。此時要求- a 等於- e 指定的地址。
4.4 如何用 mkimage 生成 uImage
1> mkimage 如何指定入口參數 ( -e 0xxxxxx)
2> mkimage 指定了入口參數後, 你用 tftpboot 下載 kernel 到哪個地址?
3> -c 如何指定?
u - boot 裏面的解壓和內核自解壓的區別: u-boot 裏面的解壓實際上是 bootm 實現的 , 把 mkimage -C bzip2 或者 gzip 生成的 uImage 進行解壓 ; 而 kernel 的自解壓是對 zImage 進行解壓,發生在 bootm 解壓之後。
U-boot 對內核添加頭部時,前面已經用 gzip 壓縮過一次內核了,而不是指原有的內核印象是否是壓縮內核。 指 uImage 本身被壓縮了,即對原來的 zImage/Image 添加了 U-boot 的壓縮方式,使得生成的 uImage 變小了。 此時- c gzip
若沒有對 zImage/Image 用 gzip 命令壓縮過,則 -c none 。
綜合上面分析, mkimage 的影響因子爲:
- e ,內核的入口地址是否與- a 相同
Tftpaddr ,即將內核加載到 RAM 中運行的地址,決定是否搬運或解壓內核
- c ,內核是否經過 gzip 壓縮過,決定了是搬運還是解壓
另外內核本身爲非壓縮的 Image 或 zImage 也是一個影響因子。組合情況共 2^4 =16 種
4.5 Bootm 命令引導 mkimage 生成的內核全程解析
( 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-2.4.19-zip-8000
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 2.4.19-rmk7 (root@dding) (gcc version 2.95.3 20010315 (release)) #42 四 10 月 11 14:15:35 CST 2007
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-2.4.19-zip-8000
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 重合了,導致解壓縮失敗,因此二者必須相隔一定的距離
<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-2.4.19-zip-8040
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-2.4.19-nzip-8000
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
爲什麼總是校驗失敗呢?當前的內核印象爲 1.8M ,難道太大了,後面的 ramdisk 將其覆蓋??
下面未拷貝 ramdisk ,校驗成功,成功啓動,無法安裝跟文件系統,是因爲無 ramdisk 。說明上面確實是覆蓋了,因此要對於大的內核印象要合理設置 tftpaddr 的地址和 ramdisk 的地址
Addr ( ramdisk )= 0x2110 0000
Addr ( tftpaddr )= 0x2100 0000
Addr ( ramdisk )- Addr ( tftpaddr )= 0x10 0000 = 1M < 1.8M
Uboot> tftp 21000000 uImage-nzip-8000;bootm 21000000
## Booting image at 21000000 ...
Image Name: dd-kernel-2.4.19-nzip-8000
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 2.4.19-rmk7 (root@dding) (gcc version 2.95.3 20010315 (release)) #44 四 10 月 11 17:27:24 CST 2007
。。。。。。。
Kernel panic: VFS: Unable to mount root fs on 01:00
Addr ( ramdisk ) - 2M = 0x20f0 0000 = Addr ( tftpaddr )成功啓動
Uboot> tftp 20f00000 uImage-nzip-8000;tftp 21100000 ramdisk;bootm 20f00000
## Booting image at 20f00000 ...
Image Name: dd-kernel-2.4.19-nzip-8000
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 2.4.19-rmk7 (root@dding) (gcc version 2.95.3 20010315 (release)) #44 四 10 月 11 17:27:24 CST 2007
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-2.4.19-nzip-8040
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 對內核解壓
Starting kernel ...
Uncompressing Linux.. ............ 壓縮內核 zImage 自解壓 ......................... done, booting the kernel.
Linux version 2.4.19-rmk7 (root@dding) (gcc version 2.95.3 20010315 (release)) #43 四 10 月
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 重合了,導致解壓縮失敗,因此二者必須相隔一定的距離
<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 難道對於壓縮內核,幻數對的條件是- a ==- e 地址??即壓縮內核默認- 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 2.4.19-rmk7 (root@dding) (gcc version 2.95.3 20010315 (release)) #43 四 10 月 11 14:25:14 CST 2007
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 2.4.19-rmk7 (root@dding) (gcc version 2.95.3 20010315 (release)) #43 四 10 月 11 14:25:14 CST 2007
AT91RM9200DK login:
<4> -a=0x20008000 , -e = 0x20008040 , tftpaddr= 0x21000000
搬動,但- e 地址不對,失敗
4.5.3 關於壓縮及非壓縮內核 bootm 啓動的全面總結
由上面的 16 個例子,我們可以看出,能夠啓動內核的由以下幾種情況:
各種情況對應的統一 ramdiskaddr= 0x21100000
<1> 非壓縮的 Image 內核:
-a=-e = 0x20008000 , –c=none , tftpaddr= 0x20f00000
此法主要由於內核太大,導致 tftpaddr 做了一定的修正
-a= 0x20008000 , -e = 0x20008040 , –c=none , tftpaddr=0x20008000
此法理論上可行,但我未試驗成功,有興趣的朋友可以探究下
對於非壓縮的 Image 內核, mkimage 之前不壓縮的話,內核印象較大,此法不常用
-a=-e = 0x20008000 , –c=gzip , tftpaddr= 0x21000000
–c=gzip 壓縮內核必須解壓,只有這種情況成功;其他解壓覆蓋或者- e 入口不對
<2> 壓縮的 zImage 內核:
-a=-e = 0x20008000 , –c=none , tftpaddr= 0x21000000
-a= 0x20008000 , -e = 0x20008040 , –c=none , tftpaddr=0x20008000
-a=-e = 0x20008000 , –c=gzip , tftpaddr= 0x21000000
–c=gzip 壓縮內核必須解壓,只有這種情況成功;其他解壓覆蓋或者- e 入口不對
zImage 已經壓縮過一次了,一般無需再壓縮,此法不常用
常見方法:
<1> 非壓縮的 Image 內核:
-a=-e = 0x20008000 , –c=gzip , tftpaddr= 0x21000000
<2> 壓縮的 zImage 內核:
-a=-e = 0x20008000 , –c=none , tftpaddr= 0x21000000
-a= 0x20008000 , -e = 0x20008040 , –c=none , tftpaddr=0x20008000
待續:
U-boot 如何向 Linux 內核傳遞命令行參數?
Go 引導內核的詳細方法?
Ramdisk 與 initrd 怎麼傳給內核?