U-BOOT啟動linux的過程
一、概述
linux核心映象常見到的有兩種形式,zImage和uImage。這兩種檔案的格式稍有差別,所以啟動這兩種格式的核心映象也會有所不同。目前,uboot只支援啟動uImage型別的映象,對zImage還不支援(但是可以移植,TQ2440就是這樣做的)。
二、uImage和zImage
1、zImage
zImage是用命令“#make zImage”生成的,我截取了生成資訊最後部分的內容如下:
OBJCOPY arch/arm/boot/Image Kernel: arch/arm/boot/Image is ready GZIP arch/arm/boot/compressed/piggy.gz AS arch/arm/boot/compressed/piggy.o LD arch/arm/boot/compressed/vmlinux OBJCOPY arch/arm/boot/zImage Kernel: arch/arm/boot/zImage is ready
從中可以看到,zImage是經過gzip壓縮過的,所以在核心啟動過程(不屬於u-boot控制範圍,在核心映象的頭部嵌有解壓函式)中必然會對應一個解壓過程。
2、uImage
(1) 生成方法
uImage是u-boot專用的核心映象,可用命令“#make uImage”生成。生成資訊最後部分的內容如下:
Kernel: arch/arm/boot/Image isready Kernel: arch/arm/boot/zImage is ready UIMAGE arch/arm/boot/uImage Image Name: Linux-2.6.30.4-EmbedSky Created: Thu Mar 20 19:53:32 2014 Image Type: ARM Linux Kernel Image (uncompressed) Data Size: 2314736 Bytes = 2260.48 kB = 2.21 MB Load Address: 0x30008000 Entry Point: 0x30008000 Image arch/arm/boot/uImage isready
事實上,uImage是呼叫mkimage(uboot製作的工具)這個工具生成的。
[email protected]:/opt/EmbedSky# mkimage -n 'linux-2.6.30' -A arm -O linux -T kernel -C none -a 0x30008000 -e 0x30008000 -d zImage uImage Image Name: linux-2.6.30 Created: Thu Mar 20 19:59:36 2014 Image Type: ARM Linux Kernel Image (uncompressed) Data Size: 2314736 Bytes = 2260.48 kB = 2.21 MB Load Address: 0x30008000 Entry Point: 0x30008000
(2)特點
在原來的可執行映象檔案zImage的前面加上一個0x40位元組的頭, 記錄引數所指定的資訊,這樣uboot才能識別這個映象是針對哪個CPU體系結構的,哪個OS的, 哪種型別,載入記憶體中的哪個位置,入口點在記憶體的那個位置以及映象名是什麼。
(3)image_header
頭部的結構是在include/image.h中定義的,如下所示:
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;
開啟上邊生成的uImage檔案,可以看到對應的資料。
(1)ih_magic 0x27051956 magic值,我覺得是uImage的頭部開始值,根據這個值,判斷是否是uImage
(2)ih_crc 0x19dbf9c6 頭部校驗
(3)ih_time 0x74295319 建立時間
(4)ih_size 0x002351f0 映象大小為2260.48KB
(5)ih_load 0x30008000 核心載入地址
(6)ih_ep 0x30008000 核心執行地址,“theKernel”指向該地址,說明這裡藏著進入第一個函式--解壓
(7)ih_dcrc 0x38fc654e 核心校驗
(8)ih_os 0x05 #define IH_OS_LINUX 5 /* Linux */
(9)ih_arch 0x02 #define IH_CPU_ARM 2 /* ARM */
(10)ih_type 0x02 #define IH_TYPE_KERNEL 2 /* OS Kernel Image */
(11)ih_comp 0x00 #define IH_COMP_NONE 0 /* No Compression Used */
(12)ih_name Linux_2.6.30.4-EmbedSky
三、u-boot核心啟動流程概述
前文已經說明u-boot只支援uImage,步驟三、四都是針對uImage的。
另外宣告一點,步驟三四的測試uboot程式碼是韋東山視訊提供的。
1、從NandFlash中讀取核心到RAM中
2、在RAM中,給核心進行重定位
3、給核心傳遞引數
4、啟動核心
四、u-boot啟動核心細節分析
1、啟動命令
從環境變數中檢視啟動命令:
2、從NandFlash中讀取核心到RAM中
nand read.jffs2 0x30007FC0 kernel
此命令會啟用(common/cmd_nand.c)中的do_nand函式,從而將nandflash上的kernel分割槽載入到0x30007fc0位置處。
OpenJTAG> mtd device nand0 <nandflash0>, # parts = 4 #: name size offset mask_flags 0: bootloader 0x00040000 0x00000000 0 1: params 0x00020000 0x00040000 0 2: kernel 0x00200000 0x00060000 0 3: root 0x0fda0000 0x00260000 0 active partition: nand0,0 - (bootloader) 0x00040000 @ 0x00000000 defaults: mtdids : nand0=nandflash0 mtdparts: mtdparts=nandflash0:[email protected]0(bootloader),128k(params),2m(kernel),-(root)
從分割槽表中,可以看出kernel分割槽的起始地址是0x60000,大小是0x200000(2M),這條命令實際上等效於
nand read.jffs2 0x30007FC0 0x60000 0x200000
也可以使用命令
nand read 0x30007FC0 0x60000 0x200000
nand read.jffs2可以自動頁對齊,所以大小可以是非頁整的;如果使用nand read的大小必須是頁對齊的。
3、讀取uImage頭部
bootm 0x30007fc0
此命令會啟用(common/cmd_bootm.c)中的do_bootm函式,從而開始執行
2、在RAM中,給核心進行重定位 3、給核心傳遞引數 4、啟動核心
image_header_t header; 定義一個全域性變數header,是讀取頭部的緩衝區
addr = simple_strtoul(argv[1], NULL, 16); 定位頭部地址,將字串“0x30007fc0”轉化為整型
printf ("## Booting image at %08lx ...\n", addr); 顯示從哪兒啟動
memmove (&header, (char *)addr, sizeof(image_header_t)); 讀取頭部到header變數中
4、判斷當前的記憶體區是否是uImage的開始位置
if (ntohl(hdr->ih_magic) != IH_MAGIC) { { puts ("Bad Magic Number\n"); SHOW_BOOT_PROGRESS (-1); return 1; } }
注意到:
#define IH_MAGIC 0x27051956 /* Image Magic Number */(include/image.h)
5、校驗頭部
data = (ulong)&header; len = sizeof(image_header_t); checksum = ntohl(hdr->ih_hcrc); hdr->ih_hcrc = 0; if (crc32 (0, (uchar *)data, len) != checksum) { puts ("Bad Header Checksum\n"); SHOW_BOOT_PROGRESS (-2); return 1; }
6、列印頭部資訊
/* for multi-file images we need the data part, too */ print_image_hdr ((image_header_t *)addr);
7、核查核心資料
data = addr + sizeof(image_header_t); len = ntohl(hdr->ih_size); if (verify) { puts (" Verifying Checksum ... "); if (crc32 (0, (uchar *)data, len) != ntohl(hdr->ih_dcrc)) { printf ("Bad Data CRC\n"); SHOW_BOOT_PROGRESS (-3); return 1; } puts ("OK\n"); } SHOW_BOOT_PROGRESS (4);
注意到data已經跳過了uImage的頭部,指向了真正的核心首部,也即0x30008000。
8、核查架構、核心型別、壓縮型別等資訊,其中會涉及到重定位
len_ptr = (ulong *)data; #if defined(__PPC__) if (hdr->ih_arch != IH_CPU_PPC) #elif defined(__ARM__) if (hdr->ih_arch != IH_CPU_ARM) #elif defined(__I386__) if (hdr->ih_arch != IH_CPU_I386) #elif defined(__mips__) if (hdr->ih_arch != IH_CPU_MIPS) #elif defined(__nios__) if (hdr->ih_arch != IH_CPU_NIOS) #elif defined(__M68K__) if (hdr->ih_arch != IH_CPU_M68K) #elif defined(__microblaze__) if (hdr->ih_arch != IH_CPU_MICROBLAZE) #elif defined(__nios2__) if (hdr->ih_arch != IH_CPU_NIOS2) #elif defined(__blackfin__) if (hdr->ih_arch != IH_CPU_BLACKFIN) #elif defined(__avr32__) if (hdr->ih_arch != IH_CPU_AVR32) #else # error Unknown CPU type #endif { printf ("Unsupported Architecture 0x%x\n", hdr->ih_arch); SHOW_BOOT_PROGRESS (-4); return 1; } SHOW_BOOT_PROGRESS (5); switch (hdr->ih_type) { case IH_TYPE_STANDALONE: name = "Standalone Application"; /* A second argument overwrites the load address */ if (argc > 2) { hdr->ih_load = htonl(simple_strtoul(argv[2], NULL, 16)); } break; case IH_TYPE_KERNEL: name = "Kernel Image"; break; case IH_TYPE_MULTI: name = "Multi-File Image"; len = ntohl(len_ptr[0]); /* OS kernel is always the first image */ data += 8; /* kernel_len + terminator */ for (i=1; len_ptr[i]; ++i) data += 4; break; default: printf ("Wrong Image Type for %s command\n", cmdtp->name); SHOW_BOOT_PROGRESS (-5); return 1; } SHOW_BOOT_PROGRESS (6); /* * We have reached the point of no return: we are going to * overwrite all exception vector code, so we cannot easily * recover from any failures any more... */ iflag = disable_interrupts(); #ifdef CONFIG_AMIGAONEG3SE /* * We've possible left the caches enabled during * bios emulation, so turn them off again */ icache_disable(); invalidate_l1_instruction_cache(); flush_data_cache(); dcache_disable(); #endif switch (hdr->ih_comp) { case IH_COMP_NONE: if(ntohl(hdr->ih_load) == data) { printf (" XIP %s ... ", name); } else { #if defined(CONFIG_HW_WATCHDOG) || defined(CONFIG_WATCHDOG) size_t l = len; void *to = (void *)ntohl(hdr->ih_load); void *from = (void *)data; printf (" Loading %s ... ", name); while (l > 0) { size_t tail = (l > CHUNKSZ) ? CHUNKSZ : l; WATCHDOG_RESET(); memmove (to, from, tail); to += tail; from += tail; l -= tail; } #else /* !(CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG) */ memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len); #endif /* CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG */ } break; case IH_COMP_GZIP: printf (" Uncompressing %s ... ", name); if (gunzip ((void *)ntohl(hdr->ih_load), unc_len, (uchar *)data, &len) != 0) { puts ("GUNZIP ERROR - must RESET board to recover\n"); SHOW_BOOT_PROGRESS (-6); do_reset (cmdtp, flag, argc, argv); } break; #ifdef CONFIG_BZIP2 case IH_COMP_BZIP2: printf (" Uncompressing %s ... ", name); /* * If we've got less than 4 MB of malloc() space, * use slower decompression algorithm which requires * at most 2300 KB of memory. */ i = BZ2_bzBuffToBuffDecompress ((char*)ntohl(hdr->ih_load), &unc_len, (char *)data, len, CFG_MALLOC_LEN < (4096 * 1024), 0); if (i != BZ_OK) { printf ("BUNZIP2 ERROR %d - must RESET board to recover\n", i); SHOW_BOOT_PROGRESS (-6); udelay(100000); do_reset (cmdtp, flag, argc, argv); } break; #endif /* CONFIG_BZIP2 */ default: if (iflag) enable_interrupts(); printf ("Unimplemented compression type %d\n", hdr->ih_comp); SHOW_BOOT_PROGRESS (-7); return 1; } puts ("OK\n"); SHOW_BOOT_PROGRESS (7); switch (hdr->ih_type) { case IH_TYPE_STANDALONE: if (iflag) enable_interrupts(); /* load (and uncompress), but don't start if "autostart" * is set to "no" */ if (((s = getenv("autostart")) != NULL) && (strcmp(s,"no") == 0)) { char buf[32]; sprintf(buf, "%lX", len); setenv("filesize", buf); return 0; } appl = (int (*)(int, char *[]))ntohl(hdr->ih_ep); (*appl)(argc-1, &argv[1]); return 0; case IH_TYPE_KERNEL: case IH_TYPE_MULTI: /* handled below */ break; default: if (iflag) enable_interrupts(); printf ("Can't boot image type %d\n", hdr->ih_type); SHOW_BOOT_PROGRESS (-8); return 1; } SHOW_BOOT_PROGRESS (8);
在這部分程式碼中,有這麼一部分關於壓縮型別的:
switch (hdr->ih_comp) { case IH_COMP_NONE: if(ntohl(hdr->ih_load) == data) { printf (" XIP %s ... ", name); } else { #if defined(CONFIG_HW_WATCHDOG) || defined(CONFIG_WATCHDOG) size_t l = len; void *to = (void *)ntohl(hdr->ih_load); void *from = (void *)data; printf (" Loading %s ... ", name); while (l > 0) { size_t tail = (l > CHUNKSZ) ? CHUNKSZ : l; WATCHDOG_RESET(); memmove (to, from, tail); to += tail; from += tail; l -= tail; } #else /* !(CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG) */ memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len); #endif /* CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG */ } break;
可以看到,u-boot會判斷當前去除uImage頭部核心程式碼所處的位置(7步驟已經說明地址是data)是否與編譯時安排的重定位位置(hdr->ih_load)一致。
如果一致,就列印一句話例如:"XIP Kernel Image ... OK"(XIP表示的是Execute In Place就是就地執行的意思,因為不用搬運)。
如果不一致,則需要呼叫 memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len);進行核心的重定位,要知道它有2M多的大小,會花費一些時間。儘量使讀取核心的時候,就讀取到hdr->ih_load-64的位置上,這樣就不必再搬運一次。
9、根據作業系統型別,啟動對應的作業系統
switch (hdr->ih_os) { default: /* handled by (original) Linux case */ case IH_OS_LINUX: #ifdef CONFIG_SILENT_CONSOLE fixup_silent_linux(); #endif do_bootm_linux (cmdtp, flag, argc, argv, addr, len_ptr,