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中會將命令分離并找對應的處理函數(shù)。
第一條命令是從調用(do_nand)把內核把讀到到一個地址上去;第二條命令是從內核里面調用相應的函數(shù)(do_bootm)來啟動內核; ① 在哪里來讀 從哪里讀?從kernel分區(qū)讀; 讀到哪里去?放到指定地址(0x30007fc0)去; 在PC機上,每一個硬盤前面都有一個分區(qū)表。對于嵌入式Linux來說,flash上面沒有分區(qū)表,顯然這個分區(qū)就和PC機上不一樣;既然沒有分區(qū)表,這些分區(qū)怎么體現(xiàn)?只能在源碼里面寫死的; 定義分區(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函數(shù)中
/*?read?write?*/ ?if?(strncmp(cmd,?"read",?4)?==?0?||?strncmp(cmd,?"write",?5)?==?0)
三、bootm 0x30007FC0解析 ① 在/common/cmd_bootm.c中do_bootm函數(shù)中啟動內核。啟動內核的時候,首先對內核的頭部要進行操作,原因是在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函數(shù):
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的參數(shù)小于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根據(jù)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根據(jù)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里的另一個參數(shù),連接地址就應該等于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函數(shù)。在跳到ih_ep入口之前還要uboot設置內核啟動參數(shù),然后才是跳到ih_ep啟動內核。 ② do_bootm_linux函數(shù)有用的代碼如下:
//uboot參數(shù)設置 #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:設置內核啟動參數(shù),參數(shù)的格式是tag(標記列表),對于韋東山的開發(fā)板標記列表開始地址是0x30000100,一般來說有四個標記傳參函數(shù)。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的三個參數(shù) (0, bd->bi_arch_number, bd->bi_boot_params); 第一個參數(shù)是固定的
通過看來自于linux-2.6.30.4DocumentationarmBooting:
?
第二個參數(shù)是機器類型ID
看board100ask24x0100ask24x0.c
第三個參數(shù)是標記列表的開始地址
看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); ?} } #endif
bi_dram[i].start;內存的初始地址,bi_dram[i].start;內存的大小
?這兩個參數(shù)的初始值在start_armboot()函數(shù)中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?==?'