u-boot启动Linux内核解析
扫描二维码
随时随地手机看文章
一、我们从上一节命令解析可以知道,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分区读; 读到哪里去?放到指定地址(0x30007fc0)去; 在PC机上,每一个硬盘前面都有一个分区表。对于嵌入式Linux来说,flash上面没有分区表,显然这个分区就和PC机上不一样;既然没有分区表,这些分区怎么体现?只能在源码里面写死的; 定义分区的源码如下:(includeconfigs100ask24x0.h)
#define MTDIDS_DEFAULT "nand0=nandflash0" #define MTDPARTS_DEFAULT "mtdparts=nandflash0:256k@0(bootloader)," "128k(params)," "2m(kernel)," "-(root)"
上面定义了各个分区的起始地址; ② 通过什么读
在/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字节 bootm == 实际存放的地址 例子: 实际存放在 0x30007fc0 bootm 0x30007fc0 加载地址 0x30007fc0 连接地址 0x30008000 1、uboot根据Bootm指定的0x30007fc0去找image,实际地址为0x30007fc0,找到头部 2、读出头部里的加载地址,判断是否和Bootm相等,相等则不移动,启动 2、判断 uimage头部里指定的加载地址 == bootm指定的加载地址 + 64字节 (hdr->ih_load == addr+64字节/data) 此时: 实际存放地址+64字节 == uimage加载地址 == uimage连接地址 bootm == 实际存放的地址 例子: 实际存放在 0x30007fc0 bootm 0x30007fc0 加载地址 0x30008000 连接地址 0x30008000 1、uboot根据Bootm指定的0x30007fc0去找image,实际地址为0x30007fc0,找到头部 2、读出头部里的加载地址,判断是否和Bootm + 字节相等,相等则不移动,启动 首先bootm的地址要和我们 实际存放(不管它的加载地址是多少) 整个uimage的首地址吻合,这样就可以找到头部。 这里存在两种情况,我们可以看到 Uboot源码里 1、 hdr->ih_load == addr 也就是说判断的是bootm_addr 与uimage里的ih_load加载地址是否相等,这样的话,我们在制作uimage的时候就应该让ih_load=bootmaddr 那么uimage里的另一个参数,连接地址就应该等于bootm+64字节 2、hdr->ih_load == addr+64字节 友善以及韦东山老师的uboot里判断条件都被改成了这样 那么,uimage里的Load地址和连接地址应该相等,等于bootm+64字节 */ 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(标记列表),对于韦东山的开发板标记列表开始地址是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); } } #endif
bi_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 == ' ') return; params->hdr.tag = ATAG_CMDLINE; params->hdr.size = (sizeof (struct tag_header) + strlen (p) + 1 + 4) >> 2; strcpy (params->u.cmdline.cmdline, p); params = tag_next (params); }
*commandlinechar
的来源为*commandline = getenv ("bootargs");
那么在终端所获得的信息是bootargs=noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0
root=/dev/mtdblock3 根文件系统在Flash中的第三分区
init=/linuxrc 第一个进程为linuxrc
console=ttySAC0 内核打印信息从串口输出
4.结束标记:
setup_end_tag:
static void setup_end_tag (bd_t *bd) { params->hdr.tag = ATAG_NONE; params->hdr.size = 0; }
参数设置结束的时候将tag设置为空,size设置为0.
三、总结: