內(nèi)核啟動參數(shù)機制學(xué)習(xí)筆記
一、bootloader與內(nèi)核的通訊協(xié)議 內(nèi)核的啟動參數(shù)其實不僅僅包含在了cmdline中,cmdline不過是bootloader傳遞給內(nèi)核的信息中的一部分。bootloader和內(nèi)核的通信方式根據(jù)構(gòu)架的不同而異。對于ARM構(gòu)架來說,啟動相關(guān)的信息可以通過內(nèi)核文檔(Documentation/arm/Booting)獲得。其中介紹了bootloader與內(nèi)核的通信協(xié)議,我簡單總結(jié)如下: (1)數(shù)據(jù)格式:可以是標簽列表(tagged list)或設(shè)備樹(device tree)。 (2)存放地址:r2寄存器中存放的數(shù)據(jù)所指向的內(nèi)存地址。
在我所做過的開發(fā)中,都是使用tagged list的,所以下面以標簽列表為例來介紹信息從bootloader(U-boot)到內(nèi)核(Linux-3.0)的傳遞過程。
內(nèi)核文檔對此的說明,翻譯摘要如下: 4a. 設(shè)置內(nèi)核標簽列表--------------------------------
bootloader必須創(chuàng)建和初始化內(nèi)核標簽列表。一個有效的標簽列表以ATAG_CORE標簽開始,且以ATAG_NONE標簽結(jié)束。ATAG_CORE標簽可以是空的,也可以是非空。一個空ATAG_CORE標簽其 size 域設(shè)置為 '2' (0x00000002)。ATAG_NONE標簽的 size 域必須設(shè)置為 '0'。
在列表中可以保存任意數(shù)量的標簽。對于一個重復(fù)的標簽是追加到之前標簽所攜帶的信息之后,還是覆蓋原來整個信息,是未定義的。某些標簽的行為是前者,其他是后者。
bootloader必須傳遞一個系統(tǒng)內(nèi)存的位置和最小值,以及根文件系統(tǒng)位置。因此,最小的標簽列表如下所示:
基地址 ->+-----------+ | ATAG_CORE | | +-----------+ | | ATAG_MEM | | 地址增長方向 +-----------+ | | ATAG_NONE | | +-----------+ v
標簽列表應(yīng)該保存在系統(tǒng)的RAM中。
標簽列表必須置于內(nèi)核自解壓和initrd'bootp'程序都不會覆蓋的內(nèi)存區(qū)。建議放在RAM的頭16KiB中。 (內(nèi)核中關(guān)于ARM啟動的標準文檔為:Documentation/arm/Booting ,我翻譯的版本:《Linux內(nèi)核文檔翻譯:Documentation/arm/Booting》)
關(guān)于tagged list的數(shù)據(jù)結(jié)構(gòu)和定義在內(nèi)核與uboot中都存在,連路徑都相同:arch/arm/include/asm/setup.h。uboot的定義是從內(nèi)核中拷貝過來的,要和內(nèi)核一致的,以內(nèi)核為主。要了解標簽列表的具體結(jié)構(gòu)認真閱讀這個頭文件是必須的。 一個獨立的標簽的結(jié)構(gòu)大致如下:
struct tag+------------------------+| struct tag_header hdr; | || 標簽頭信息 | |+------------------------+ ||union { | || struct tag_core core; | || struct tag_mem32 mem; | || ...... | || } u; | || 標簽具體內(nèi)容 | || 此為聯(lián)合體 | | 地址增長方向| 根據(jù)標簽類型確定 | |+------------------------+ v
點擊(此處)折疊或打開
struct tag_header {__u32 size; //標簽總大小(包括tag_header)
__u32 tag; //標簽標識
};
比如一個ATAG_CORE在內(nèi)存中的數(shù)據(jù)為:
+----------+| 00000005 | || 54410001 | |+----------+ || 00000000 | || 00000000 | |地址增長方向| 00000000 | |+----------+ v
當(dāng)前在內(nèi)核中接受的標簽有:ATAG_CORE : 標簽列表開始標志ATAG_NONE : 標簽列表結(jié)束標志ATAG_MEM : 內(nèi)存信息標簽(可以有多個標簽,以標識多個內(nèi)存區(qū)塊)ATAG_VIDEOTEXT:VGA文本顯示參數(shù)標簽ATAG_RAMDISK :ramdisk參數(shù)標簽(位置、大小等)ATAG_INITRD :壓縮的ramdisk參數(shù)標簽(位置為虛擬地址)ATAG_INITRD2 :壓縮的ramdisk參數(shù)標簽(位置為物理地址)ATAG_SERIAL :板子串號標簽ATAG_REVISION :板子版本號標簽ATAG_VIDEOLFB :幀緩沖初始化參數(shù)標簽ATAG_CMDLINE :command line字符串標簽(我們平時設(shè)置的啟動參數(shù)cmdline字符串就放在這個標簽中)
特定芯片使用的標簽:ATAG_MEMCLK :給footbridge使用的內(nèi)存時鐘標簽ATAG_ACORN :acorn RiscPC 特定信息
二、參數(shù)從u-boot到特定內(nèi)存地址 使用uboot來啟動一個Linux內(nèi)核,通常情況下我們會按照如下步驟執(zhí)行: 設(shè)置內(nèi)核啟動的command line,也就是設(shè)置uboot的環(huán)境變量“bootargs”(非必須,如果你要傳遞給內(nèi)核cmdline才要設(shè)置)加載內(nèi)核映像文到內(nèi)存指定位置(從SD卡、u盤、網(wǎng)絡(luò)或flash)使用“bootm (內(nèi)核映像基址)”命令來啟動內(nèi)核 而這個uboot將參數(shù)按照協(xié)議處理好并放入指定內(nèi)存地址的過程就發(fā)生在“bootm”命令中,下面我們仔細分析下bootm命令的執(zhí)行。
1、bootm 命令主體流程 bootm命令的源碼位于common/cmd_bootm.c,其中的do_bootm函數(shù)就是bootm命令的實現(xiàn)代碼。
點擊(此處)折疊或打開
/*******************************************************************//* bootm - boot application image from image in memory */
/*******************************************************************/
int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
ulongiflag;
ulongload_end = 0;
intret;
boot_os_fn*boot_fn;
#ifdef CONFIG_NEEDS_MANUAL_RELOC
static int relocated = 0;
/* 重載啟動函數(shù)表 */if (!relocated) {
int i;
for (i = 0; i < ARRAY_SIZE(boot_os); i++)
if (boot_os[i] != NULL)
boot_os[i] += gd->reloc_off;
relocated = 1;
}
#endif
/* 確定我們是否有子命令 */ /* bootm其實是有子命令的,可以自己將bootm的功能手動分步進行,來引導(dǎo)內(nèi)核 */if (argc > 1) {
char *endp;
simple_strtoul(argv[1], &endp, 16);
/* endp pointing to NULL means that argv[1] was just a
* valid number, pass it along to the normal bootm processing
*
* If endp is ':' or '#' assume a FIT identifier so pass
* along for normal processing.
*
* Right now we assume the first arg should never be '-'
*/
if ((*endp != 0) && (*endp != ':') && (*endp != '#'))
return do_bootm_subcommand(cmdtp, flag, argc, argv);
}
if (bootm_start(cmdtp, flag, argc, argv))
return 1;
點擊(此處)折疊或打開
這句非常重要,使其這個就是bootm主要功能的開始。其主要的目的是從bootm命令指定的內(nèi)存地址中獲取內(nèi)核uImage的文件頭(也就是在用uboot的mkiamge工具處理內(nèi)核zImage時添加的那64B的數(shù)據(jù))。核對并顯示出其中包含的信息,并填充一個全局的static bootm_headers_t images結(jié)構(gòu)體的image_info_t os域:
點擊(此處)折疊或打開
typedef struct image_info {ulong start, end; /* start/end of blob */
ulong image_start, image_len; /* start of image within blob, len of image */
ulong load; /* load addr for the image */
uint8_t comp, type, os; /* compression, type of image, os type */
} image_info_t;
/*這個域保存OS映像的信息,包括uImage的起止地址、所包含內(nèi)核映像(可能被壓縮過)的起始地址和大小、(解壓后)內(nèi)核映像的加載位置以及壓縮方式、映像類型和OS類型。*/
平常我們在用bootm驅(qū)動內(nèi)核的時候所看到的如下信息:
## Booting kernel from Legacy Image at 50008000 ...Image Name: Linux-2.6.37.1Image Type: ARM Linux Kernel Image (uncompressed)Data Size: 3800644 Bytes = 3.6 MiBLoad Address: 50008000Entry Point: 50008040Verifying Checksum ... OK就是這個函數(shù)所調(diào)用的boot_get_kernel函數(shù)及其子函數(shù)根據(jù)uImage的文件頭打印出來的。
/*
*我們已經(jīng)到達了不可返回的位置: 我們正要覆蓋所有的異常向量代碼。 *所以我們再也無法簡單地從任何失敗中恢復(fù) *(突然讓我想到張信哲的歌詞:我們再也回不去了,對不對?)...*/
iflag = disable_interrupts();
#if defined(CONFIG_CMD_USB)
/*
* turn off USB to prevent the host controller from writing to the
* SDRAM while Linux is booting. This could happen (at least for OHCI
* controller), because the HCCA (Host Controller Communication Area)
* lies within the SDRAM and the host controller writes continously to
* this area (as The HccaFrameNumber is for example
* updated every 1 ms within the HCCA structure in For more
* details see the OpenHCI specification.
*/
usb_stop();
#endif
ret = bootm_load_os(images.os, &load_end, 1);
點擊(此處)折疊或打開
這里是第二個重要的啟動過程節(jié)點,這個函數(shù)的作用是通過獲取的文件頭信息,將文件頭后面所跟的內(nèi)核映像放置到文件頭信息規(guī)定的地址(如果是壓縮內(nèi)核,還在此函數(shù)中解壓。但這個和zImage壓縮內(nèi)核不是一個概念,不要混淆)。平常我們在用bootm驅(qū)動內(nèi)核的時候所看到的如下信息:XIP Kernel Image ... OKOK就是這個函數(shù)打印出來的。
if (ret < 0) {
if (ret == BOOTM_ERR_RESET)
do_reset (cmdtp, flag, argc, argv);
if (ret == BOOTM_ERR_OVERLAP) {
if (images.legacy_hdr_valid) {
if (image_get_type (&images.legacy_hdr_os_copy) == IH_TYPE_MULTI)
puts ("WARNING: legacy format multi component "
"image overwrittenn");
} else {
puts ("ERROR: new format image overwritten - "
"must RESET the board to recovern");
show_boot_progress (-113);
do_reset (cmdtp, flag, argc, argv);
}
}
if (ret == BOOTM_ERR_UNIMPLEMENTED) {
if (iflag)
enable_interrupts();
show_boot_progress (-7);
return 1;
}
}
lmb_reserve(&images.lmb, images.os.load, (load_end - images.os.load));
if (images.os.type == IH_TYPE_STANDALONE) {
if (iflag)
enable_interrupts();
/* This may return when 'autostart' is 'no' */
bootm_start_standalone(iflag, argc, argv);
return 0;
}
show_boot_progress (8);
#ifdef CONFIG_SILENT_CONSOLE
if (images.os.os == IH_OS_LINUX)
fixup_silent_linux();
#endif
boot_fn = boot_os[images.os.os];
點擊(此處)折疊或打開
這個語句是有是一個比較重要的節(jié)點,其功能是根據(jù)全局static bootm_headers_t images結(jié)構(gòu)體的image_info_t os域中記錄的os類型來將一個特定OS的內(nèi)核引導(dǎo)函數(shù)入口賦給boot_fn變量。比如我引導(dǎo)的是Linux內(nèi)核,那么boot_fn就是do_bootm_linux。if (boot_fn == NULL) {
if (iflag)
enable_interrupts();
printf ("ERROR: booting os '%s' (%d) is not supportedn",
genimg_get_os_name(images.os.os), images.os.os);
show_boot_progress (-8);
return 1;
}
arch_preboot_os();
boot_fn(0, argc, argv, &images);
點擊(此處)折疊或打開
如果不出錯的話,這個函數(shù)應(yīng)該是不會在返回了,因為在這個函數(shù)中會將控制權(quán)交由OS的內(nèi)核。對于引導(dǎo)Linux內(nèi)核來說,這里其實就是調(diào)用do_bootm_linux。show_boot_progress (-9);
#ifdef DEBUG
puts ("n## Control returned to monitor - resetting...n");
#endif
do_reset (cmdtp, flag, argc, argv);
return 1;
} 2、分析do_bootm_linux 對我們來說非常重要的do_bootm_linux函數(shù)位于:arch/arm/lib/bootm.c
Bootm.c (archarmlib):
點擊(此處)折疊或打開
int do_bootm_linux(int flag, int argc, char *argv[], bootm_headers_t *images){bd_t*bd = gd->bd;
char*s;
intmachid = bd->bi_arch_number;
void(*kernel_entry)(int zero, int arch, uint params);
#ifdef CONFIG_CMDLINE_TAG
char *commandline = getenv ("bootargs");
#endif
這里獲取了生成cmdline標簽所需要的字符串
if ((flag != 0) && (flag != BOOTM_STATE_OS_GO))
return 1;
s = getenv ("machid");
if (s) {
machid = simple_strtoul (s, NULL, 16);
printf ("Using machid 0x%x from environmentn", machid);
}
注意:這里設(shè)備ID號可以從環(huán)境變量中獲得!如果環(huán)境變量中有,就會覆蓋之前賦值過的設(shè)備ID(最終通過r1傳遞給內(nèi)核)。
show_boot_progress (15);
#ifdef CONFIG_OF_LIBFDT
if (images->ft_len)
return bootm_linux_fdt(machid, images);
#endif
kernel_entry = (void (*)(int, int, uint))images->ep;
這里讓函數(shù)指針指向內(nèi)核映像的入口地址
debug ("## Transferring control to Linux (at address %08lx) ...n",
(ulong) kernel_entry);
以下就是我們一直在找的內(nèi)核標簽列表生成代碼,從這里看出:U-boot原生只支持部分標簽。當(dāng)然,如果要加的話也很簡單。 #if defined (CONFIG_SETUP_MEMORY_TAGS) ||
defined (CONFIG_CMDLINE_TAG) ||
defined (CONFIG_INITRD_TAG) ||
defined (CONFIG_SERIAL_TAG) ||
defined (CONFIG_REVISION_TAG)
setup_start_tag (bd); //設(shè)置ATAG_CORE#ifdef CONFIG_SERIAL_TAG
setup_serial_tag (¶ms); //設(shè)置ATAG_SERIAL,依賴板級是否實現(xiàn)了get_board_serial函數(shù)#endif
#ifdef CONFIG_REVISION_TAG
setup_revision_tag (¶ms);//設(shè)置ATAG_REVISION,依賴板級是否實現(xiàn)了get_board_rev函數(shù)#endif
#ifdef CONFIG_SETUP_MEMORY_TAGS
setup_memory_tags (bd);//設(shè)置ATAG_MEM,依賴于uboot的全局變量bd->bi_dram[i]中的數(shù)據(jù)#endif
#ifdef CONFIG_CMDLINE_TAG
setup_commandline_tag (bd, commandline);//設(shè)置ATAG_CMDLINE,依賴上面的字符串commandline中的數(shù)據(jù)#endif
#ifdef CONFIG_INITRD_TAG
if (images->rd_start && images->rd_end)
setup_initrd_tag (bd, images->rd_start, images->rd_end);//設(shè)置ATAG_INITRD#endif
setup_end_tag(bd);//設(shè)置ATAG_NONE#endif
announce_and_cleanup();
在進入內(nèi)核前配置好芯片狀態(tài),以符合內(nèi)核啟動要求。主要是關(guān)閉和清理緩存
kernel_entry(0, machid, bd->bi_boot_params);
/*不會再返回了*/
跳入內(nèi)核入口地址:r1=0、r1=machid、r2=啟動參數(shù)指針
return 1;
}
點擊(此處)折疊或打開
static void announce_and_cleanup(void){
printf("nStarting kernel ...nn");
#ifdef CONFIG_USB_DEVICE
{
extern void udc_disconnect(void);
udc_disconnect();
}
#endif
cleanup_before_linux();
} Cpu.c (archarmcpuarmv7) 1958 2011-4-1
點擊(此處)折疊或打開
int cleanup_before_linux(void){
unsigned int i;
/*
* this function is called just before we call linux
* it prepares the processor for linux
*
* we turn off caches etc ...
*/
disable_interrupts();
/* turn off I/D-cache */
icache_disable();
dcache_disable();
/* invalidate I-cache */
cache_flush();
#ifndef CONFIG_L2_OFF
/* turn off L2 cache */
l2_cache_disable();
/* invalidate L2 cache also */
invalidate_dcache(get_device_type());
#endif
i = 0;
/* mem barrier to sync up things */
asm("mcr p15, 0, %0, c7, c10, 4": :"r"(i));
#ifndef CONFIG_L2_OFF
l2_cache_enable();
#endif
return 0; 對于上面啟動環(huán)境的設(shè)定,可參考Documentation/arm/Booting。節(jié)選Booting中文翻譯:
5. 調(diào)用內(nèi)核映像---------------------------
現(xiàn)有的引導(dǎo)加載程序:強制新開發(fā)的引導(dǎo)加載程序: 強制
調(diào)用內(nèi)核映像zImage有兩個選擇。如果zImge是保存在flash中的,且其為了在flash中直接運行而被正確鏈接。這樣引導(dǎo)加載程序就可以在flash中直接調(diào)用zImage。zImage也可以被放在系統(tǒng)RAM(任意位置)中被調(diào)用。注意:內(nèi)核使用映像基地址的前16KB RAM空間來保存頁表。建議將映像置于RAM的32KB處。
對于以上任意一種情況,都必須符合以下啟動狀態(tài):
- 停止所有DMA設(shè)備,這樣內(nèi)存數(shù)據(jù)就不會因為虛假網(wǎng)絡(luò)包或磁盤數(shù)據(jù)而被破壞。這可能可以節(jié)省你許多的調(diào)試時間。
- CPU 寄存器配置r0 = 0,r1 = (在上面 (3) 中獲取的)機器類型碼.r2 = 標簽列表在系統(tǒng)RAM中的物理地址,或設(shè)備樹塊(dtb)在系統(tǒng)RAM中的物理地址
- CPU 模式所有形式的中斷必須被禁止 (IRQs 和 FIQs)CPU 必須處于 SVC 模式。 (對于 Angel 調(diào)試有特例存在)
- 緩存, MMUsMMU 必須關(guān)閉。指令緩存開啟或關(guān)閉都可以。數(shù)據(jù)緩存必須關(guān)閉。
- 引導(dǎo)加載程序應(yīng)該通過直接跳轉(zhuǎn)到內(nèi)核映像的第一條指令來調(diào)用內(nèi)核映像。 3、標簽生成的函數(shù)舉例分析: 所有標簽生成函數(shù)都在arch/arm/lib/bootm.c文件中,其實原理很簡單,就是直接往指定的內(nèi)存地址中寫入標簽信息。以下以setup_start_tag和setup_memory_tags為例分析:
點擊(此處)折疊或打開
static void setup_start_tag (bd_t *bd)130 {
131 params = (struct tag *) bd->bi_boot_params; //params指向內(nèi)存中標簽列表中的基地址
132 //直接往內(nèi)存中按照內(nèi)核定義的標簽結(jié)構(gòu)寫入信息133 params->hdr.tag = ATAG_CORE;
134 params->hdr.size = tag_size (tag_core);
135
136 params->u.core.flags = 0;
137 params->u.core.pagesize = 0;
138 params->u.core.rootdev = 0;
139 //根據(jù)本標簽的大小數(shù)據(jù),params跳到下一標簽的起始地址
140 params = tag_next (params);
141 }
點擊(此處)折疊或打開
static void setup_memory_tags (bd_t *bd)146 {
147 int i;
148 //上一個標簽已經(jīng)將params指向了下一標簽的基地址,所以這里可以直接使用
149 for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) {
150 params->hdr.tag = ATAG_MEM;
151 params->hdr.size = tag_size (tag_mem32);
152 //根據(jù)配置信息和uboot全局變量中的信息創(chuàng)建標簽數(shù)據(jù)
153 params->u.mem.start = bd->bi_dram[i].start;
154 params->u.mem.size = bd->bi_dram[i].size;
155
156 params = tag_next (params);//根據(jù)本標簽的大小數(shù)據(jù),params跳到下一標簽的起始地址157 }
158 } bootloader完成了引導(dǎo)Linux內(nèi)核所需要的準備之后將通過直接跳轉(zhuǎn),將控制權(quán)交由內(nèi)核zImage。
三、內(nèi)核從特定內(nèi)存獲取參數(shù) 在內(nèi)核zImage開始運行后,首先是進行內(nèi)核自解壓,其過程在之前的博客中有詳細介紹:《Linux內(nèi)核源碼分析--內(nèi)核啟動之(1)zImage自解壓過程(Linux-3.0 ARMv7)》。其中對于內(nèi)核標簽列表的沒有處理。在完成內(nèi)核自解壓之后,系統(tǒng)又恢復(fù)了bootloader設(shè)定的啟動狀態(tài),將控制權(quán)交由解壓后的內(nèi)核。也就是說解壓前后,系統(tǒng)啟動環(huán)境不變。 解壓后的內(nèi)核開始運行后,首先是構(gòu)架相關(guān)的匯編代碼,其過程在之前的博客中有詳細介紹:《Linux內(nèi)核源碼分析--內(nèi)核啟動之(2)Image內(nèi)核啟動(匯編部分)(Linux-3.0 ARMv7)》。其中對于內(nèi)核標簽列表的處理就是判斷r2(內(nèi)核啟動參數(shù))指針的有效性:驗證指針指向的數(shù)據(jù)是否是有效的tagged list或者device tree,如果不是r2清零。 在運行完匯編代碼后,就跳入了構(gòu)架無關(guān)的C語言啟動代碼:init/main.c中的start_kernel函數(shù)。在這個函數(shù)中開始了對內(nèi)核啟動參數(shù)的真正處理。 首先內(nèi)核必須先要解析tagged list,而它的處理位于:
start_kernel-->setup_arch(&command_line);-->mdesc = setup_machine_tags(machine_arch_type);
點擊(此處)折疊或打開
static struct machine_desc * __init setup_machine_tags(unsigned int nr){
struct tag *tags = (struct tag *)&init_tags;
點擊(此處)折疊或打開
先讓tags指針指向內(nèi)核默認tagged list(init_tags)點擊(此處)折疊或打開
/** This holds our defaults.
*/
static struct init_tags {
struct tag_header hdr1;
struct tag_core core;
struct tag_header hdr2;
struct tag_mem32 mem;
struct tag_header hdr3;
} init_tags __initdata = {
{ tag_size(tag_core), ATAG_CORE },
{ 1, PAGE_SIZE, 0xff },
{ tag_size(tag_mem32), ATAG_MEM },
{ MEM_SIZE },
{ 0, ATAG_NONE }
}; 這個默認的tagged list實質(zhì)上只定義了內(nèi)存的參數(shù)
struct machine_desc *mdesc = NULL, *p;
char *from = default_command_line;
點擊(此處)折疊或打開
注意這個from的賦值,指向default_command_line,它是默認的內(nèi)核cmdline,在內(nèi)核配置的時候可指定。Boot options --->() Default kernel command string
init_tags.mem.start = PHYS_OFFSET;
點擊(此處)折疊或打開
對上面的內(nèi)核默認的tagged list中的內(nèi)存起始地址進行初始化。個人感覺這句有點奇怪,這個賦值為什么不直接放在變量定義的地方一起初始化呢?/*
*在支持的設(shè)備列表中找到當(dāng)前的設(shè)備。 */
for_each_machine_desc(p)
if (nr == p->nr) {
printk("Machine: %sn", p->name);
mdesc = p;
break;
}
點擊(此處)折疊或打開
內(nèi)核編譯的時候可能編譯進了多個設(shè)備的支持,所以可能存在多個設(shè)備的描述結(jié)構(gòu)體。這個通過bootloader傳遞進來的設(shè)備ID來匹配一個設(shè)備描述結(jié)構(gòu)體。if (!mdesc) {
early_print("nError: unrecognized/unsupported machine ID"
" (r1 = 0x%08x).nn", nr);
dump_machine_table(); /* does not return */
}
點擊(此處)折疊或打開
如果上面沒有找到匹配的設(shè)備描述結(jié)構(gòu)體,則打印出錯信息,并死循環(huán)。if (__atags_pointer)
tags = phys_to_virt(__atags_pointer);
else if (mdesc->boot_params) {
#ifdef CONFIG_MMU
/*
* 我們依然運行在最小的MMU映射上,
*這假設(shè)設(shè)備默認將標簽列表放在頭1MB的RAM中。 *任何其他的位置將可能失敗, *并在此處靜靜地掛起內(nèi)核。 */
if (mdesc->boot_params < PHYS_OFFSET ||
mdesc->boot_params >= PHYS_OFFSET + SZ_1M) {
printk(KERN_WARNING
"Default boot params at physical 0x%08lx out of reachn",
mdesc->boot_params);
} else
#endif
{
tags = phys_to_virt(mdesc->boot_params);
}
}
點擊(此處)折疊或打開
如果bootloader傳遞過來的tagged list有效,則將地址轉(zhuǎn)換成虛擬地址,賦給tags。否則使用設(shè)備描述結(jié)構(gòu)體中的數(shù)據(jù)。例如:從這里也可以知道,設(shè)備描述結(jié)構(gòu)體中的.boot_params數(shù)據(jù)是可選的,如果bootloader傳入的地址沒有問題,這里就不會用到。(其他地方是否用的,有待確定)點擊(此處)折疊或打開
MACHINE_START(MINI6410, "MINI6410")/* Maintainer: Darius Augulis#if defined(CONFIG_DEPRECATED_PARAM_STRUCT)
/*
*如果傳遞進來的是一個舊格式的參數(shù), 將他們轉(zhuǎn)換為 * 一個tag list.
*/
if (tags->hdr.tag != ATAG_CORE)
convert_to_tag_list(tags);
#endif
if (tags->hdr.tag != ATAG_CORE) {
#if defined(CONFIG_OF)
/*
* 如果定義了 CONFIG_OF , 那么就假假設(shè)一個合理的 * 現(xiàn)代系統(tǒng)應(yīng)該傳入一個啟動參數(shù)
*/
early_print("Warning: Neither atags nor dtb foundn");
#endif
tags = (struct tag *)&init_tags;
}
點擊(此處)折疊或打開
如果tagged list的第一個tag不是 ATAG_CORE,說明tagged list 不存在或者有問題,打印錯誤信息并使用默認tagged list。if (mdesc->fixup)
mdesc->fixup(mdesc, tags, &from, &meminfo);
點擊(此處)折疊或打開
如果此設(shè)備描述結(jié)構(gòu)體中定義了fixup函數(shù),就執(zhí)行。從這里看出似乎這個函數(shù)是用于處理tagged list、cmdline和meminfo數(shù)據(jù)的。if (tags->hdr.tag == ATAG_CORE) {
if (meminfo.nr_banks != 0)
squash_mem_tags(tags);
點擊(此處)折疊或打開
如果meminfo(其中保存了內(nèi)存的bank信息)中已經(jīng)初始化過了,就清除tagged list中mem_tags的信息(可導(dǎo)致跟在mem_tags之后的信息也一并失效)save_atags(tags);
點擊(此處)折疊或打開
備份tagged list信息到全局atags_copy。parse_tags(tags);
點擊(此處)折疊或打開
逐個解析tag,主要功能是將每個tag的信息保存到內(nèi)核全局變量中每個tag有對應(yīng)的內(nèi)核結(jié)構(gòu)體:
點擊(此處)折疊或打開
struct tagtable { __u32 tag; //tag標識編號int (*parse)(const struct tag *); //tag信息處理函數(shù)(一般是將其中的信息保存到內(nèi)核全局變量中)};內(nèi)核一般通過以下宏來定義一個tagtable結(jié)構(gòu)體:
點擊(此處)折疊或打開
#define __tag __used __attribute__((__section__(".taglist.init")))#define __tagtable(tag, fn) static struct tagtable __tagtable_##fn __tag = { tag, fn }也就是將所有定義好的tagtable結(jié)構(gòu)體放入一個獨立的".taglist.init"段中,使用時用一個for循環(huán)就可以遍歷了。
}
點擊(此處)折疊或打開
如果tagged list中的ATAG_CORE驗證通過,就保存并解析tag。/* parse_early_param 函數(shù)需要 boot_command_line */
strlcpy(boot_command_line, from, COMMAND_LINE_SIZE);
點擊(此處)折疊或打開
將form指向的字符串拷貝到boot_command_line字符數(shù)組中。return mdesc;
點擊(此處)折疊或打開
返回匹配的設(shè)備描述結(jié)構(gòu)體指針。} tag分析函數(shù)重點舉例 對內(nèi)存信息的處理
點擊(此處)折疊或打開
static int __init parse_tag_mem32(const struct tag *tag){
return arm_add_memory(tag->u.mem.start, tag->u.mem.size);
點擊(此處)折疊或打開
將tag中的信息添加到全局的meminfo中去:arch/arm/include/asm/setup.h點擊(此處)折疊或打開
/** Memory map description
*/
#define NR_BANKS 8
struct membank {
phys_addr_t start;
unsigned long size;
unsigned int highmem;
};
struct meminfo {
int nr_banks;
struct membank bank[NR_BANKS];
};
extern struct meminfo meminfo; 這些信息在內(nèi)存子系統(tǒng)初始化的時候是會用到的,比如確定高低端內(nèi)存的分界線。
}
__tagtable(ATAG_MEM, parse_tag_mem32); 對cmdline的保存
點擊(此處)折疊或打開
static int __init parse_tag_cmdline(const struct tag *tag){
#if defined(CONFIG_CMDLINE_EXTEND)
strlcat(default_command_line, " ", COMMAND_LINE_SIZE);
strlcat(default_command_line, tag->u.cmdline.cmdline,
COMMAND_LINE_SIZE);
點擊(此處)折疊或打開
如果定義了“CONFIG_CMDLINE_EXTEND”(cmdline擴展),內(nèi)核會將tag中cmdline和配置內(nèi)核時定義的cmdline合并到default_command_line字符數(shù)組中。#elif defined(CONFIG_CMDLINE_FORCE)
pr_warning("Ignoring tag cmdline (using the default kernel command line)n");
點擊(此處)折疊或打開
如果定義了“CONFIG_CMDLINE_FORCE”(強制使用配置內(nèi)核時定義的cmdline),內(nèi)核會忽略tag中cmdline#else
strlcpy(default_command_line, tag->u.cmdline.cmdline,
COMMAND_LINE_SIZE);
點擊(此處)折疊或打開
如果以上兩個配置都沒有定義,則使用tag中cmdline覆蓋到default_command_line字符數(shù)組中。#endif
return 0;
}
__tagtable(ATAG_CMDLINE, parse_tag_cmdline);
其他相關(guān)信息 其他所有的tag解析函數(shù)都是大同小異,都是將tag中的信息保存到各內(nèi)核全局變量結(jié)構(gòu)體中,以備后用。
四、內(nèi)核處理cmdline 對于所有的tag中,我們最常用的就是cmdine,所以這里詳細解析一下。 從上面的setup_machine_tags函數(shù)中我們知道,對于從tag傳遞到default_command_line中的cmdline字符串,內(nèi)核又將其復(fù)制了一份到boot_command_line中。 在回到了setup_arch函數(shù)中之后,內(nèi)核又把boot_command_line復(fù)制了一份到cmd_line字符數(shù)組中,并用cmdline_p指針指向這個cmd_line字符數(shù)組。 在完成了上面的工作后,cmdline已經(jīng)從tag中到了多個全局字符數(shù)組中,也就是在內(nèi)存中了,可以開始處理了。
這個cmdline的處理和tag的處理方法是一樣的,每個cmdline中的參數(shù)都有對應(yīng)的內(nèi)核結(jié)構(gòu)體:include/linux/init.h
點擊(此處)折疊或打開
struct obs_kernel_param {const char *str; //參數(shù)標識字符串指針int (*setup_func)(char *); //解析函數(shù)
int early; //早期解析標志
};
/*
* 僅用于真正的核心代碼. 正常情況下詳見 moduleparam.h.
*
* 強制對齊,使得編譯器不會將obs_kernel_param "數(shù)組"中的元素放置在離
* .init.setup較遠的地方.
*/
#define __setup_param(str, unique_id, fn, early)
static const char __setup_str_##unique_id[] __initconst
__aligned(1) = str;
static struct obs_kernel_param __setup_##unique_id
__used __section(.init.setup)
__attribute__((aligned((sizeof(long)))))
= { __setup_str_##unique_id, fn, early }
#define __setup(str, fn)
__setup_param(str, fn, fn, 0)
/* 注意: fn 是作為 module_param的, 不是
* 當(dāng)返回非零的時候發(fā)出警告!*/
#define early_param(str, fn)
__setup_param(str, fn, fn, 1)
/* 依賴 boot_command_line 被設(shè)置 */
void __init parse_early_param(void);
void __init parse_early_options(char *cmdline); 所有需要解析的參數(shù)都是通過__setup(str, fn)和early_param(str, fn)宏定義的,他們的差別僅在于是否為early參數(shù)。 解析函數(shù)的作用是根據(jù)cmdline中的參數(shù)值設(shè)置全局變量。例如對“init=”的定義如下: init/main.c
點擊(此處)折疊或打開
static int __init init_setup(char *str){
unsigned int i;
execute_command = str;
/*
* In case LILO is going to boot us with default command line,
* it prepends "auto" before the whole cmdline which makes
* the shell think it should execute a script with such name.
* So we ignore all arguments entered _before_ init=... [MJ]
*/
for (i = 1; i < MAX_INIT_ARGS; i++)
argv_init[i] = NULL;
return 1;
}
__setup("init=", init_setup); 其這樣目的是為了將已經(jīng)解析出的“init=”后的字符串指針賦給全局變量execute_command。而這個execute_command就是內(nèi)核初始化到最后執(zhí)行的用戶空間初始化程序。
內(nèi)核對于cmdline的處理分為兩個步驟:早期處理和后期處理。
1、cmdline的早期處理 對于ARM構(gòu)架,cmdline的早期處理是在setup_arch函數(shù)中的parse_early_param();,但這個函數(shù)定義在init/main.c:
點擊(此處)折疊或打開
/*檢查早期參數(shù). */static int __init do_early_param(char *param, char *val)
{
const struct obs_kernel_param *p;
for (p = __setup_start; p < __setup_end; p++) {
if ((p->early && strcmp(param, p->str) == 0) ||
(strcmp(param, "console") == 0 &&
strcmp(p->str, "earlycon") == 0)
) {
if (p->setup_func(val) != 0)
printk(KERN_WARNING
"Malformed early option '%s'n", param);
}
}
/*這個階段我們接受任何異常. */
return 0;
}
點擊(此處)折疊或打開
此函數(shù)通過解析好的參數(shù)名及參數(shù)值,在上面介紹的“.init.setup”段中搜索匹配的“struct obs_kernel_param”結(jié)構(gòu)體(必須標志為early,也就是用early_param(str, fn)宏定義的結(jié)構(gòu)體),并調(diào)用參數(shù)處理函數(shù)。void __init parse_early_options(char *cmdline)
{
parse_args("early options", cmdline, NULL, 0, do_early_param);
點擊(此處)折疊或打開
這里通過統(tǒng)一的parse_args函數(shù)處理,此函數(shù)原型如下:點擊(此處)折疊或打開
int parse_args(const char *name,char *args,
const struct kernel_param *params,
unsigned num,
int (*unknown)(char *param, char *val))
這個函數(shù)的處理方法主要是分離出每個類似“foo=bar,bar2”的形式,再給next_arg分離出參數(shù)名和參數(shù)值,并通過參數(shù)名在“const struct kernel_param *params”指向的地址中搜索對應(yīng)的數(shù)據(jù)結(jié)構(gòu),并調(diào)用其參數(shù)處理函數(shù)。如果沒有找到就調(diào)用最后一個參數(shù)“unknown”傳遞進來的未知參數(shù)處理函數(shù)。由于此處params為NULL,必然找不到對應(yīng)的數(shù)據(jù)結(jié)構(gòu),所有分離好的參數(shù)及參數(shù)名都由最后一個函數(shù)指針參數(shù)指定的函數(shù)do_early_param來處理。也就是上面那個函數(shù)。
}
/* 構(gòu)架相關(guān)代碼在早期調(diào)用這個函數(shù), 如果沒有, 會在解析其他參數(shù)前再次調(diào)用這個函數(shù)。*/
void __init parse_early_param(void)
{
static __initdata int done = 0;
static __initdata char tmp_cmdline[COMMAND_LINE_SIZE];
if (done)
return;
/* 最終調(diào)用do_early_param. */
strlcpy(tmp_cmdline, boot_command_line, COMMAND_LINE_SIZE);
點擊(此處)折疊或打開
再次將boot_command_line復(fù)制到一個臨時變量,并在下面的函數(shù)中使用parse_early_options(tmp_cmdline);
done = 1;
點擊(此處)折疊或打開
對這個靜態(tài)變量置1,標志著這個函數(shù)已經(jīng)執(zhí)行過。不需要再次執(zhí)行。} 一個典型的早期參數(shù)就是“mem=”,之所以會放在前期處理,是因為內(nèi)存參數(shù)對于系統(tǒng)初始化很重要,在這里處理完后,下面馬上就要用到這些數(shù)據(jù)了。 處理函數(shù)如下:
點擊(此處)折疊或打開
/** Pick out the memory size. We look for mem=size@start,
* where start and size are "size[KkMm]"
*/
static int __init early_mem(char *p)
{
static int usermem __initdata = 0;
unsigned long size;
phys_addr_t start;
char *endp;
/*
*如果此處指定內(nèi)存大小,
*我們會丟棄任何自動生成的大小
*
*/
if (usermem == 0) {
usermem = 1;
meminfo.nr_banks = 0;
}
點擊(此處)折疊或打開
這里自動情況原有的內(nèi)存配置信息,如果tagged list中有設(shè)置,這里就會清除并覆蓋原來的信息。start = PHYS_OFFSET;
size = memparse(p, &endp);
if (*endp == '@')
start = memparse(endp + 1, NULL);
arm_add_memory(start, size);
點擊(此處)折疊或打開
這個函數(shù)上面介紹過了,就是把獲取的內(nèi)存大小和基地址添加到全局的meminfo結(jié)構(gòu)體中。return 0;
}
early_param("mem", early_mem);
2、cmdline的后期分類處理 在上面的早期處理完成之后,系統(tǒng)就繼續(xù)初始化。在從setup_arch(&command_line);返回不久就將cmdline又進行了一次備份,使用的是bootmem內(nèi)存分配系統(tǒng):
點擊(此處)折疊或打開
setup_command_line(command_line);點擊(此處)折疊或打開
對cmdline進行備份和保存:/* 為處理的command line備份 (例如eg. 用于 /proc) */
char *saved_command_line;
/* 用于參數(shù)處理的command line */
static char *static_command_line;
之后就打印出內(nèi)核cmdline并解析后期參數(shù)和模塊參數(shù)。源碼如下:
點擊(此處)折疊或打開
printk(KERN_NOTICE "Kernel command line: %sn", boot_command_line);點擊(此處)折疊或打開
打印出完整的內(nèi)核cmdline parse_early_param();點擊(此處)折疊或打開
解析內(nèi)核早期參數(shù),但是對于ARM構(gòu)架來說,在setup_arch函數(shù)中已經(jīng)調(diào)用過了。所以這里什么都不做。parse_args("Booting kernel", static_command_line, __start___param,
__stop___param - __start___param,
&unknown_bootoption);
點擊(此處)折疊或打開
這里調(diào)用的parse_args就比較復(fù)雜了,我這里簡單地分析一下:在這個函數(shù)主要是一個循環(huán),逐一分析完整的cmdline中的每個參數(shù):
使用next_arg函數(shù)解析出類似“foo=bar,bar2”的形式中的參數(shù)名(foo)和參數(shù)值(bar和bar2)使用parse_one根據(jù)參數(shù)名在內(nèi)核內(nèi)建模塊的參數(shù)處理段(__param)中搜索每一個“struct kernel_param”,是否為某個內(nèi)核內(nèi)建模塊的參數(shù):
如果是,則使用搜索到的那個“struct kernel_param”結(jié)構(gòu)體中的參數(shù)設(shè)置函數(shù)“.ops->set”來設(shè)置模塊的參數(shù)如果不是,就使用unknown_bootoption函數(shù)處理,就是到內(nèi)核的“.init.setup”段搜索,看是不是“非早期”內(nèi)核啟動參數(shù)(使用__setup(str, fn)宏定義的參數(shù))。如果是的話,就用相應(yīng)的函數(shù)來處理,這個和“早期”參數(shù)處理是一樣的。如果不是,可能會打印錯誤信息。
到了這里,內(nèi)核的cmdline處理就到此結(jié)束了。只有內(nèi)置模塊才會獲取到cmdline中的參數(shù),因為內(nèi)建模塊無法通過其他形式獲取參數(shù),不像.ok模塊可以在掛載的時候從命令行獲取參數(shù)。
如果你自己的外置模塊(.ok)中需要參數(shù),就算是你在內(nèi)核啟動cmdline中加了參數(shù),模塊掛載的時候也是沒法自動獲取。你必須在使用insmod掛載模塊的時候,在最后加上你要的設(shè)置的參數(shù)信息?;蛘咄ㄟ^/proc/cmdline獲取啟動參數(shù),然后用shell命令過濾出需要的參數(shù)字符串,并加到insmod命令的最后。
Linux內(nèi)核在啟動的時候需要一些參數(shù),以獲得當(dāng)前硬件的信息或者啟動所需資源在內(nèi)存中的位置等等。這些信息可以通過bootloader傳遞給內(nèi)核,比較常見的就是cmdline。以前我在啟動內(nèi)核的時候習(xí)慣性的通過uboot傳遞一個cmdline給內(nèi)核,沒有具體的分析這個過程。最近在分析內(nèi)核啟動過程的時候,重新看了一下內(nèi)核啟動參數(shù)的傳遞過程,徹底解決一下在這方面的疑惑。一、bootloader與內(nèi)核的通訊協(xié)議 內(nèi)核的啟動參數(shù)其實不僅僅包含在了cmdline中,cmdline不過是bootloader傳遞給內(nèi)核的信息中的一部分。bootloader和內(nèi)核的通信方式根據(jù)構(gòu)架的不同而異。對于ARM構(gòu)架來說,啟動相關(guān)的信息可以通過內(nèi)核文檔(Documentation/arm/Booting)獲得。其中介紹了bootloader與內(nèi)核的通信協(xié)議,我簡單總結(jié)如下: (1)數(shù)據(jù)格式:可以是標簽列表(tagged list)或設(shè)備樹(device tree)。 (2)存放地址:r2寄存器中存放的數(shù)據(jù)所指向的內(nèi)存地址。
在我所做過的開發(fā)中,都是使用tagged list的,所以下面以標簽列表為例來介紹信息從bootloader(U-boot)到內(nèi)核(Linux-3.0)的傳遞過程。
內(nèi)核文檔對此的說明,翻譯摘要如下: 4a. 設(shè)置內(nèi)核標簽列表--------------------------------
bootloader必須創(chuàng)建和初始化內(nèi)核標簽列表。一個有效的標簽列表以ATAG_CORE標簽開始,且以ATAG_NONE標簽結(jié)束。ATAG_CORE標簽可以是空的,也可以是非空。一個空ATAG_CORE標簽其 size 域設(shè)置為 '2' (0x00000002)。ATAG_NONE標簽的 size 域必須設(shè)置為 '0'。
在列表中可以保存任意數(shù)量的標簽。對于一個重復(fù)的標簽是追加到之前標簽所攜帶的信息之后,還是覆蓋原來整個信息,是未定義的。某些標簽的行為是前者,其他是后者。
bootloader必須傳遞一個系統(tǒng)內(nèi)存的位置和最小值,以及根文件系統(tǒng)位置。因此,最小的標簽列表如下所示:
基地址 ->+-----------+ | ATAG_CORE | | +-----------+ | | ATAG_MEM | | 地址增長方向 +-----------+ | | ATAG_NONE | | +-----------+ v
標簽列表應(yīng)該保存在系統(tǒng)的RAM中。
標簽列表必須置于內(nèi)核自解壓和initrd'bootp'程序都不會覆蓋的內(nèi)存區(qū)。建議放在RAM的頭16KiB中。 (內(nèi)核中關(guān)于ARM啟動的標準文檔為:Documentation/arm/Booting ,我翻譯的版本:《Linux內(nèi)核文檔翻譯:Documentation/arm/Booting》)
關(guān)于tagged list的數(shù)據(jù)結(jié)構(gòu)和定義在內(nèi)核與uboot中都存在,連路徑都相同:arch/arm/include/asm/setup.h。uboot的定義是從內(nèi)核中拷貝過來的,要和內(nèi)核一致的,以內(nèi)核為主。要了解標簽列表的具體結(jié)構(gòu)認真閱讀這個頭文件是必須的。 一個獨立的標簽的結(jié)構(gòu)大致如下:
struct tag+------------------------+| struct tag_header hdr; | || 標簽頭信息 | |+------------------------+ ||union { | || struct tag_core core; | || struct tag_mem32 mem; | || ...... | || } u; | || 標簽具體內(nèi)容 | || 此為聯(lián)合體 | | 地址增長方向| 根據(jù)標簽類型確定 | |+------------------------+ v
點擊(此處)折疊或打開
struct tag_header {__u32 size; //標簽總大?。ò╰ag_header)
__u32 tag; //標簽標識
};
比如一個ATAG_CORE在內(nèi)存中的數(shù)據(jù)為:
+----------+| 00000005 | || 54410001 | |+----------+ || 00000000 | || 00000000 | |地址增長方向| 00000000 | |+----------+ v
當(dāng)前在內(nèi)核中接受的標簽有:ATAG_CORE : 標簽列表開始標志ATAG_NONE : 標簽列表結(jié)束標志ATAG_MEM : 內(nèi)存信息標簽(可以有多個標簽,以標識多個內(nèi)存區(qū)塊)ATAG_VIDEOTEXT:VGA文本顯示參數(shù)標簽ATAG_RAMDISK :ramdisk參數(shù)標簽(位置、大小等)ATAG_INITRD :壓縮的ramdisk參數(shù)標簽(位置為虛擬地址)ATAG_INITRD2 :壓縮的ramdisk參數(shù)標簽(位置為物理地址)ATAG_SERIAL :板子串號標簽ATAG_REVISION :板子版本號標簽ATAG_VIDEOLFB :幀緩沖初始化參數(shù)標簽ATAG_CMDLINE :command line字符串標簽(我們平時設(shè)置的啟動參數(shù)cmdline字符串就放在這個標簽中)
特定芯片使用的標簽:ATAG_MEMCLK :給footbridge使用的內(nèi)存時鐘標簽ATAG_ACORN :acorn RiscPC 特定信息
二、參數(shù)從u-boot到特定內(nèi)存地址 使用uboot來啟動一個Linux內(nèi)核,通常情況下我們會按照如下步驟執(zhí)行: 設(shè)置內(nèi)核啟動的command line,也就是設(shè)置uboot的環(huán)境變量“bootargs”(非必須,如果你要傳遞給內(nèi)核cmdline才要設(shè)置)加載內(nèi)核映像文到內(nèi)存指定位置(從SD卡、u盤、網(wǎng)絡(luò)或flash)使用“bootm (內(nèi)核映像基址)”命令來啟動內(nèi)核 而這個uboot將參數(shù)按照協(xié)議處理好并放入指定內(nèi)存地址的過程就發(fā)生在“bootm”命令中,下面我們仔細分析下bootm命令的執(zhí)行。
1、bootm 命令主體流程 bootm命令的源碼位于common/cmd_bootm.c,其中的do_bootm函數(shù)就是bootm命令的實現(xiàn)代碼。
點擊(此處)折疊或打開
/*******************************************************************//* bootm - boot application image from image in memory */
/*******************************************************************/
int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
ulongiflag;
ulongload_end = 0;
intret;
boot_os_fn*boot_fn;
#ifdef CONFIG_NEEDS_MANUAL_RELOC
static int relocated = 0;
/* 重載啟動函數(shù)表 */if (!relocated) {
int i;
for (i = 0; i < ARRAY_SIZE(boot_os); i++)
if (boot_os[i] != NULL)
boot_os[i] += gd->reloc_off;
relocated = 1;
}
#endif
/* 確定我們是否有子命令 */ /* bootm其實是有子命令的,可以自己將bootm的功能手動分步進行,來引導(dǎo)內(nèi)核 */if (argc > 1) {
char *endp;
simple_strtoul(argv[1], &endp, 16);
/* endp pointing to NULL means that argv[1] was just a
* valid number, pass it along to the normal bootm processing
*
* If endp is ':' or '#' assume a FIT identifier so pass
* along for normal processing.
*
* Right now we assume the first arg should never be '-'
*/
if ((*endp != 0) && (*endp != ':') && (*endp != '#'))
return do_bootm_subcommand(cmdtp, flag, argc, argv);
}
if (bootm_start(cmdtp, flag, argc, argv))
return 1;
點擊(此處)折疊或打開
這句非常重要,使其這個就是bootm主要功能的開始。其主要的目的是從bootm命令指定的內(nèi)存地址中獲取內(nèi)核uImage的文件頭(也就是在用uboot的mkiamge工具處理內(nèi)核zImage時添加的那64B的數(shù)據(jù))。核對并顯示出其中包含