uboot启动内核的流程分析

2019-07-12 23:38发布

先讲一下分区的概念:
嵌入式linux:flash上是没有分区表的。只能在源码里写死,我们关心的是地址。
对于分区,uboot的配置在includeconfigs100ask24x0.h里面进行配置的
#define MTDPARTS_DEFAULT "mtdparts=nandflash0:256k@0(bootloader),"
                            "128k(params),"
                            "2m(kernel),"
                            "-(root)"
mtdparts=nandflash0 :表示分区在nandflash0上
256k@0(bootloader) :表示从地址0开始 偏移256k的大小的空间为bootloader
128k(params) :从bootloader末尾地址开始 偏移128k的大小空间为启动参数所用
2m(kernel) :再后面2M的空间为kernel所使用
-(root) :后面的空间全为rootfs使用
可以在uboot命令行中使用mtd命令查看

 
启动内核需要两个命令:
nand read.jffs2 0x30007FC0 kernel (调用do_nand)
bootm 0x30007FC0                   (调用do_bootm)

烧写到flash上的内核是uImage
uImage组成:
一个头部 + 真正的内核
这个头部在uboot中有个结构体可以描述:image_header_t *hdr = &header;
image_header_t 这个结构体的重要成员为ih_load与in_ep。
ih_load:内核的加载地址,表示内核运行时要将它放在那里
in_ep:入口地址,运行内核的时候直接跳到这个地址就可以了
在执行bootm 地址的时候,bootm会去读uImage的头部,从而知道真正内核的加载地址和入口地址,如果它发现真正的内核不在加载地址的话,它就会把内核移到加载地址(in_load)上去,然后跳转到入口地址(in_ep)去执行

在我们使用的开发板真正内核的加载地址是0x30008000(问题:从哪得知的???),而我们将uImage读到了0x30007FC0(这个很奇怪的地址),原因是什么?
 使用0x30008000 -  0x30007FC0 = 64字节,这说明头部刚好就是64字节。这样就不用去移动内核了,加快了运行速度。


下面分析下do_bootm:
1.首先会判断下argc是不是小于2(bootm 0x30007FC0 ====> argc == 2)
if (argc < 2) {
  addr = load_addr;//不会使用默认的load_addr
 } else {
  addr = simple_strtoul(argv[1], NULL, 16);
 }
2.真正内核的地址:
data = addr + sizeof(image_header_t);
先分析一下image_header_t的成员在哪里进行赋值的。在函数fake_header中:
fake_header(hdr, (void*)addr, -1)
     fake_zimage_header(hdr, ptr, size)
          hdr->ih_magic = htonl(IH_MAGIC);
          hdr->ih_time  = 0;
          hdr->ih_size  = htonl(size);
          hdr->ih_load  = htonl(ZIMAGE_LOAD);
          hdr->ih_ep    = 0;
          hdr->ih_dcrc  = htonl(checksum);
          hdr->ih_os    = IH_OS_LINUX;
          hdr->ih_arch  = IH_CPU_I386;
          hdr->ih_type  = IH_TYPE_KERNEL;
          hdr->ih_comp  = IH_COMP_NONE;
          strncpy((char *)hdr->ih_name, "(none)", IH_NMLEN)
          hdr->ih_hcrc = htonl(checksum)
;
在下面的swith语句:
IH_TYPE_KERNEL  name = "Kernel Image";
IH_COMP_NONE  如果内核的加载地址和存放的地址一样的话,直接执行printf ("   XIP %s ... ", name);如果地址不一样,就会做搬移操作:memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len);
IH_OS_LINUX  do_bootm_linux  (cmdtp, flag, argc, argv,addr, len_ptr, verify);//到这里,内核就要启动了  

总结一下bootm的作用:
1.根据头部,移动真正的内核到合适的地方
2.启动内核:
重要函数;do_bootm_linux,作用:
1.uboot告诉内核一些参数--->设置启动参数
在某个地址(bd->bi_boot_params:3000100)按某种格式(TAG)保存数据。内核启动后,就会到0x30000100的地址读取这些参数

2.跳到入口地址启动内核
void (*theKernel)(int zero, int arch, uint params);//定义一个函数指针
theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);//指定了这个指针的地址为hdr->ih_ep
theKernel (0, bd->bi_arch_number, bd->bi_boot_params);//跳到ih_ep的地址执行
下面分析一下do_bootm_linux
1.获取bootargs的参数:
char *commandline = getenv ("bootargs");
2.设置内核启动的入口地址:
theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);
3.设置TAGS
setup_start_tag (bd);  //设置开始tags
     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);
setup_memory_tags (bd);  //设置内存的大小tags
 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);
 }
setup_commandline_tag (bd, commandline);//设置bootargs的TAG
     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);
setup_end_tag (bd);  //设置结束TAGS
     params->hdr.tag = ATAG_NONE;
     params->hdr.size = 0;
4.theKernel (0, bd->bi_arch_number, bd->bi_boot_params);
启动内核,并且将机器ID(bd->bi_arch_number)和启动参数(bd->bi_boot_params,存放tags的地址)传到内核中,以让内核知道。   bootcmd的设置: setenv bootcmd 'nand read.jffs2 0x30007FC0 kernel;bootm 0x30007FC0'
问题一:为什么setenv 'bootcmd nand read 0x30007FC0 60000 500000;bootm 0x30007FC0'可以启动?而setenv 'bootcmd nand read 0x30008000 60000 500000;bootm 0x30008000'启动不了,不是在内核中会进行搬移操作吗? 如果使用后者会在启动时停在start_kernel不动。求解。。。。