u-boot啟動Linux內核解析
一、我們從上一節(jié)命令解析可以知道,u-boot啟動啟動Linux內核有兩種方法: 第一種u-boot等待無空格按下自啟內核:
?s?=?getenv?("bootcmd");
????if?(bootdelay?>=?0?&&?s?&&?!abortboot?(bootdelay))?{
????????......
????????run_command?(s,?0);
????????......
????????}第二種在u-boot控制臺輸入boot命令啟動:
int?do_bootd?(cmd_tbl_t?*cmdtp,?int?flag,?int?argc,?char?*argv[])
{
????int?rcode?=?0;
#ifndef?CFG_HUSH_PARSER
????if?(run_command?(getenv?("bootcmd"),?flag)?<?0)?rcode?=?1;
#else
????if?(parse_string_outer(getenv("bootcmd"),
????????FLAG_PARSE_SEMICOLON?|?FLAG_EXIT_FROM_LOOP)?!=?0?)?rcode?=?1;
#endif
????return?rcode;
}
U_BOOT_CMD(
????boot,???1,??1,??do_bootd,
????"boot????-?boot?default,?i.e.,?run?'bootcmd'n",
????NULL
);這兩種方式本質上都是一樣的,通過最終調用run_command(bootcmd)來啟動
比如我這里bootcmd=nand read.jffs2 0x30007FC0 kernel; bootm 0x30007FC0
二、nand read.jffs2解析
在run_command中會將命令分離并找對應的處理函數。
第一條命令是從調用(do_nand)把內核把讀到到一個地址上去;第二條命令是從內核里面調用相應的函數(do_bootm)來啟動內核; ① 在哪里來讀 從哪里讀?從kernel分區(qū)讀; 讀到哪里去?放到指定地址(0x30007fc0)去; 在PC機上,每一個硬盤前面都有一個分區(qū)表。對于嵌入式Linux來說,flash上面沒有分區(qū)表,顯然這個分區(qū)就和PC機上不一樣;既然沒有分區(qū)表,這些分區(qū)怎么體現?只能在源碼里面寫死的; 定義分區(qū)的源碼如下:(includeconfigs100ask24x0.h)
#define?MTDIDS_DEFAULT?"nand0=nandflash0" #define?MTDPARTS_DEFAULT?"mtdparts=nandflash0:256k@0(bootloader),"? ????????????????????????????"128k(params),"? ????????????????????????????"2m(kernel),"? ????????????????????????????"-(root)"
上面定義了各個分區(qū)的起始地址; ② 通過什么讀
在/common/cmd_nand.c中do_nand函數中
/*?read?write?*/ ?if?(strncmp(cmd,?"read",?4)?==?0?||?strncmp(cmd,?"write",?5)?==?0)
三、bootm 0x30007FC0解析 ① 在/common/cmd_bootm.c中do_bootm函數中啟動內核。啟動內核的時候,首先對內核的頭部要進行操作,原因是在Flash中保存的內核是由兩部分構成的,第一部分是頭部,第二部分是真正的內核。而頭部的結構如下:
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;ih_load是加載地址 ih_ep是入口地址
下面要開始分析如何啟動內核,主要是do_bootm函數:
int?do_bootm?(cmd_tbl_t?*cmdtp,?int?flag,?int?argc,?char?*argv[])??
{??
????......??
????image_header_t?*hdr?=?&header;??//uimage?是內核加了一個4K的頭部,這個頭部的內容是按照結構體image_header_t來放在,是image傳遞給Uboot的信息。??
????......??
????if?(argc?<?2)?{??
????????addr?=?load_addr;???//如果bootm的參數小于2?則使用默認的加載地址??
????}?else?{??
????????addr?=?simple_strtoul(argv[1],?NULL,?16);??
????}??
????......??
????switch?(hdr->ih_comp)?{??
????case?IH_COMP_NONE:??
????????if(ntohl(hdr->ih_load)?==?addr)?{??????
/*?
????這里判斷“uimage頭部里指定的加載地址”與bootm指定的加載地址是否相等,不相等則需要移動?
????判斷的方式有兩種?
????1、判斷?uimage頭部里指定的加載地址?==?bootm指定的加載地址?(hdr->ih_load?==?addr)?
????此時:?
????????實際存放的地址??==?uimage加載地址?==?uimage連接地址-64字節(jié)?
????????bootm?==?實際存放的地址?
????例子:?
????????實際存放在??0x30007fc0?
????????bootm???????0x30007fc0?
????????加載地址????0x30007fc0?
????????連接地址????0x30008000?
????????1、uboot根據Bootm指定的0x30007fc0去找image,實際地址為0x30007fc0,找到頭部?
????????2、讀出頭部里的加載地址,判斷是否和Bootm相等,相等則不移動,啟動?
????2、判斷?uimage頭部里指定的加載地址?==?bootm指定的加載地址?+?64字節(jié)?(hdr->ih_load?==?addr+64字節(jié)/data)?
????此時:??
????????實際存放地址+64字節(jié)?==?uimage加載地址?==?uimage連接地址?
????????bootm?==?實際存放的地址?
????例子:??
????????實際存放在??0x30007fc0?
????????bootm???????0x30007fc0?
????????加載地址????0x30008000?
????????連接地址????0x30008000?
????????1、uboot根據Bootm指定的0x30007fc0去找image,實際地址為0x30007fc0,找到頭部?
????????2、讀出頭部里的加載地址,判斷是否和Bootm?+?字節(jié)相等,相等則不移動,啟動?
????首先bootm的地址要和我們?實際存放(不管它的加載地址是多少)?整個uimage的首地址吻合,這樣就可以找到頭部。?
????這里存在兩種情況,我們可以看到?Uboot源碼里?
????1、?hdr->ih_load?==?addr??
????也就是說判斷的是bootm_addr?與uimage里的ih_load加載地址是否相等,這樣的話,我們在制作uimage的時候就應該讓ih_load=bootmaddr?
????那么uimage里的另一個參數,連接地址就應該等于bootm+64字節(jié)?
????2、hdr->ih_load?==?addr+64字節(jié)??友善以及韋東山老師的uboot里判斷條件都被改成了這樣?
????那么,uimage里的Load地址和連接地址應該相等,等于bootm+64字節(jié)?
*/??
????????????printf?("???XIP?%s?...?",?name);??
????????}?else?{??
????......do_bootm_linux??(cmdtp,?flag,?argc,?argv,addr,?len_ptr,?verify);??
}總結:do_bootm有兩個作用: 作用1:讀取內核頭部將內核移動到合適地方,還有一些校驗 作用2:啟動內核,用的是do_bootm_linux函數。在跳到ih_ep入口之前還要uboot設置內核啟動參數,然后才是跳到ih_ep啟動內核。 ② do_bootm_linux函數有用的代碼如下:
//uboot參數設置
#if?defined?(CONFIG_SETUP_MEMORY_TAGS)?||???
????defined?(CONFIG_CMDLINE_TAG)?||?
????defined?(CONFIG_INITRD_TAG)?||?
????defined?(CONFIG_SERIAL_TAG)?||?
????defined?(CONFIG_REVISION_TAG)?||?
????defined?(CONFIG_LCD)?||?
????defined?(CONFIG_VFD)
?setup_start_tag?(bd);
#ifdef?CONFIG_SERIAL_TAG
?setup_serial_tag?(¶ms);
#endif
#ifdef?CONFIG_REVISION_TAG
?setup_revision_tag?(¶ms);
#endif
#ifdef?CONFIG_SETUP_MEMORY_TAGS
?setup_memory_tags?(bd);
#endif
#ifdef?CONFIG_CMDLINE_TAG
?setup_commandline_tag?(bd,?commandline);
#endif
#ifdef?CONFIG_INITRD_TAG
?if?(initrd_start?&&?initrd_end)
??setup_initrd_tag?(bd,?initrd_start,?initrd_end);
#endif
#if?defined?(CONFIG_VFD)?||?defined?(CONFIG_LCD)
?setup_videolfb_tag?((gd_t?*)?gd);
#endif
?setup_end_tag?(bd);
#endif
?printf?("nStarting?kernel?...nn");
//啟動內核
?theKernel?(0,?bd->bi_arch_number,?bd->bi_boot_params);theKernel定義:
theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);
總結:do_bootm_linux有兩個作用:
作用1:設置內核啟動參數,參數的格式是tag(標記列表),對于韋東山的開發(fā)板標記列表開始地址是0x30000100,一般來說有四個標記傳參函數。setup_start_tag (bd);setup_memory_tags (bd);setup_commandline_tag (bd, commandline);setup_end_tag (bd);作用2:跳到入口地址去是
theKernel?=?(void?(*)(int,?int,?uint))ntohl(hdr->ih_ep);? theKernel?(0,?bd->bi_arch_number,?bd->bi_boot_params);
這樣就啟動內核了?。?! 我們再來分析一下theKernel的三個參數 (0, bd->bi_arch_number, bd->bi_boot_params); 第一個參數是固定的
通過看來自于linux-2.6.30.4DocumentationarmBooting:
?
第二個參數是機器類型ID
看board100ask24x0100ask24x0.c
第三個參數是標記列表的開始地址
看board100ask24x0100ask24x0.c
?
1.啟動標記:
setup_start_tag:
static?void?setup_start_tag?(bd_t?*bd)
{
?params?=?(struct?tag?*)?bd->bi_boot_params;
?params->hdr.tag?=?ATAG_CORE;
?params->hdr.size?=?tag_size?(tag_core);
?params->u.core.flags?=?0;
?params->u.core.pagesize?=?0;
?params->u.core.rootdev?=?0;
?params?=?tag_next?(params);
}由代碼可以得到tag是一個結構體,bi_boot_params為0x300000100,ATAG_CORE為54410001
2.內存標記:
setup_memory_tag:
#ifdef?CONFIG_SETUP_MEMORY_TAGS
static?void?setup_memory_tags?(bd_t?*bd)
{
?int?i;
?for?(i?=?0;?i?<?CONFIG_NR_DRAM_BANKS;?i++)?{
??params->hdr.tag?=?ATAG_MEM;
??params->hdr.size?=?tag_size?(tag_mem32);
??params->u.mem.start?=?bd->bi_dram[i].start;
??params->u.mem.size?=?bd->bi_dram[i].size;
??params?=?tag_next?(params);
?}
}
#endifbi_dram[i].start;內存的初始地址,bi_dram[i].start;內存的大小
?這兩個參數的初始值在start_armboot()函數中dram_init可以設置。
3.命令行標記:
static?void?setup_commandline_tag?(bd_t?*bd,?char?*commandline)
{
?char?*p;
?if?(!commandline)
??return;
?把命令前面的空格給干掉
?for?(p?=?commandline;?*p?==?'?';?p++);
?if?(*p?==?'
