U-BOOT全线移植分析系列之四 ――U-boot如何引导Linux内核启动? Sailor_forever sailing_9806@163.com 转载请注明 【摘要】本节介绍了U-boot使用go或bootm启动linux内核的方法。首先介绍了mkimage的参数意义和bootm的详细执行流程。然后分析了如何利用mkimage生成内核映象的方法。对于bootm方式的内核是否压缩、-a、-e、运行地址等16种组合情况,给出了详细的测试过程,提出了6种可用方法种的三种最优解。 【关键字】:U-boot;AT91RM9200;bootm;mkimage;-a;-e;-c 四 U-boot如何引导Linux内核启动?
4.1 GO命令引导未用mkimage生成的内核
1) 运行地址!=链接地址0x20008000,不能启动 Uboot> tftp 21000000 Image;tftp 21100000 ramdisk;go 21000000 。。。。 done Bytes transferred = 6993691 (6ab71b hex) ## Starting application at 0x21000000 ... Error: a 在哪提示的? 2) 运行地址=链接地址0x20008000,不能启动,难道是ramdisk的问题 Uboot> tftp 20008000 Image;tftp 21100000 ramdisk;go 20008000 。。。。 done Bytes transferred = 6993691 (6ab71b hex) ## Starting application at 0x21000000 ... Error: a 1) 运行地址!=链接地址0x20008000,能启动,内核自解压成功,但是解压后的内核运行错误 Uboot> tftp 21000000 zImage;tftp 21100000 ramdisk;go 21000000 。。。。。。。。。。。 done Bytes transferred = 6993691 (6ab71b hex) ## Starting application at 0x21000000 ... Uncompressing Linux............................................................. done, booting the kernel. €?~??鄜屈 2) 运行地址==链接地址0x20008000,能启动,内核自解压成功,但是解压后的内核运行错误 Uboot> tftp 20008000 zImage;tftp 21100000 ramdisk; go 20008000 ## Starting application at 0x20008000 ... Uncompressing Linux............................................................. done, booting the kernel. €?~??鄜屈 上面的ramdisk都是添加了uboot的头的,去掉头部再试试。去掉了还是不行,go方法的ramdisk的地址是怎么设置的??要详细看下uboot在ramdisk这块是如何跟内核交互的? 4.2 Mkimage参数意义解析
通过mkimage这个tool可以给zImage添加一个header: 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; 此header是如何生成的?利用u-boot里面的mkimage工具来生成uImage (u-boot源码包/tools/mkimage.c ) 这里解释一下参数的意义: -A ==> set architecture to 'arch' -O ==> set operating system to 'os' -T ==> set image type to 'type' “kernel或是ramdisk” -C ==> set compression type 'comp' -a ==> set load address to 'addr' (hex) -e ==> set entry point to 'ep' (hex)(内核启动时在此位置查询完整的内核印象) -n ==> set image name to 'name' -d ==> use image data from 'datafile' -x ==> set XIP (execute in place,即不进行文件的拷贝,在当前位置执行) 对于ARM linux内核映象用法: -A arm -------- 架构是arm 4.3 Bootm的流程分析
Bootm命令在/common/cmd_bootm.c中do_bootm函数 》》》》》》》》》》》获取当前内核的地址,默认地址或者bootm的第一个参数 默认的加载地址或传递给bootm命令(优先)与实际的内核存放地址需要一致 if (argc < 2) { addr = load_addr; // load_addr = CFG_LOAD_ADDR; } else { addr = simple_strtoul(argv[1], NULL, 16); } printf ("## Booting image at %08lx ...\n", addr); 》》》》》》》》》》》》获得image头,没有mkimage的就返回了 memmove (&header, (char *)addr, sizeof(image_header_t)); 》》》》》》》》》》》》打印头部信息 print_image_hdr ((image_header_t *)addr); 实例: Image Name: dd-kernel- Image Type: ARM Linux Kernel Image (gzip compressed) Data Size: 869574 Bytes = 849.2 kB Load Address: 20008000 Entry Point: 20008000 》》》》》》》》》》》》校验image头部 printf (" Verifying Checksum ... "); printf ("OK\n"); 》》》》》》》》》》》》检查image支持的体系结构即—A 选项是否为arm或者ppc等 》》》》》》》》》》》》检查image的类型 TYPE_MULTI 是否指内核与文件系统一起,内核后面有个分界线 switch (hdr->ih_type) case IH_TYPE_KERNEL: name = "Kernel Image"; break; case IH_TYPE_MULTI: 》》》》》》》》》》判断内核的压缩类型 此处的内核是否压缩非zImage和Image的概念,而是指内核在被mkimage处理前是否用gunzip等压缩过 switch (hdr->ih_comp) { case IH_COMP_NONE: // 非压缩内核 if(ntohl(hdr->ih_load) == addr) { // 当前内核存放的地址与-a指定的一致,则不搬动,-e必须必-a大0x40 printf (" XIP %s ... ", name); } else { //当前内核存放的地址与-a指定的不一致,则将内核搬到-a地址,此时-a与-e必相同 memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len); 。。。。 case IH_COMP_GZIP: printf (" Uncompressing %s ... ", name); if (gunzip ((void *)ntohl(hdr->ih_load), unc_len, //压缩内核,将除去头部的内核解压到-a 指定的地址了,要求-a与-e相同 // 为防止解压缩时覆盖,对于压缩内核,内核存放地址最好在—a后面 (uchar *)data, (int *)&len) != 0) { do_reset (cmdtp, flag, argc, argv); } break; 》》》》》》》》》》》》》》》》判断操作系统类型 switch (hdr->ih_os) { default: /* handled by (original) Linux case */ case IH_OS_LINUX: do_bootm_linux (cmdtp, flag, argc, argv, addr, len_ptr, verify); //前四个为传给bootm的,addr为内核最初的存放地址,没有用处 break; #ifdef CONFIG_PPC static boot_os_Fcn do_bootm_linux; #else extern boot_os_Fcn do_bootm_linux; 由上可知,对于ppc和其他体系结构的do_bootm_linux函数实现是不一样的 》》》》》》》》》》》》》》启动Linux内核 do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[], ulong addr, ulong *len_ptr, int verify) 》》》》》》》》》》》》获取命令行参数 if ((s = getenv("bootargs")) == NULL) s = ""; strcpy (cmdline, s); 》》》》》》》》》》》》赋内核启动地址 kernel = (void (*)(bd_t *, ulong, ulong, ulong, ulong))hdr->ih_ep; 注意,对于压缩过的内核,会将内核解压到-a指定的地址,此时-a 与-e 地址必须相同 》》》》》》》》》》》判断bootm的命令参数中是否有initrd if (argc >= 3) { addr = simple_strtoul(argv[2], NULL, 16); printf ("## Loading RAMDisk Image at %08lx ...\n", addr); 若有initrd则赋值,否则为0 》》》》》》》》》》》》》》》启动Linux内核 /* * Linux Kernel Parameters: * r3: ptr to board info data * r4: initrd_start or 0 if no initrd * r5: initrd_end - unused if r4 is 0 * r6: Start of command line string * r7: End of command line string */ //*kbd = *(gd->bd); 在上面赋值的 (*kernel) (kbd, initrd _start, initrd_end, cmd_start, cmd_end); 启动流程的总结: 对于非gzip压缩的内核,bootm命令会首先判断bootm xxxx 这个指定的地址xxxx是否与-a指定的加载地址相同。 (1)如果不同的话会从这个地址开始提取出这个64byte的头部,对其进行分析,然后把去掉头部的内核复制到-a指定的load地址中去运行之(此时-e选型必须同-a) (2)如果相同的话那就让其原封不动的放在那,但-e指定的入口地址会推后64byte,以跳过这64byte的头部。 对于gzip压缩过的内核,因为u-boot要对其解压,因此运行地址是不能等于-a指定的地址的,且必须有一定的间隔,否则解压到-a的内核会覆盖当前运行的程序。此时要求-a等于-e指定的地址。 4.4 如何用mkimage生成uImage
1> mkimage 如何指定入口参数 ( -e 0xxxxxx) 2> mkimage 指定了入口参数后, 你用tftpboot 下载kernel到哪个地址? 3> -c 如何指定? u-boot里面的解压和内核自解压的区别: u-boot 里面的解压实际上是bootm 实现的 , 把 mkimage -C bzip2或者gzip 生成的 uImage进行解压 ; 而kernel的自解压是对zImage进行解压,发生在bootm解压之后。 U-boot 对内核添加头部时,前面已经用gzip压缩过一次内核了,而不是指原有的内核印象是否是压缩内核。指uImage 本身被压缩了,即对原来的zImage/Image添加了U-boot的压缩方式,使得生成的uImage变小了。此时-c gzip 若没有对zImage/Image用gzip命令压缩过,则-c none。 综合上面分析,mkimage的影响因子为: -e,内核的入口地址是否与-a相同 Tftpaddr,即将内核加载到RAM中运行的地址,决定是否搬运或解压内核 -c,内核是否经过gzip压缩过,决定了是搬运还是解压 另外内核本身为非压缩的Image或zImage也是一个影响因子。组合情况共2^4 =16种 4.5 Bootm命令引导mkimage生成的内核全程解析
(1)Mkimage 之前用gzip对Image进行压缩 <1> -a=-e = 0x20008000,tftpaddr= 0x21000000 解压到-a指定的地址,成功启动 Uboot> tftp 21000000 uImage-zip-8000;tftp 21100000 ramdisk;bootm 21000000 ## Booting image at 21000000 ... Image Name: dd-kernel- Image Type: ARM Linux Kernel Image (gzip compressed) Data Size: 869629 Bytes = 849.2 kB Load Address: 20008000 Entry Point: 20008000 Verifying Checksum ... OK Uncompressing Kernel Image ... OK Starting kernel ... Linux version AT91RM9200DK login: root <2> -a=-e = 0x20008000, tftpaddr= 0x20008000 解压失败,启动失败 Uboot> tftp 20008000 uImage-zip-zImage-8000;tftp 21100000 ramdisk;bootm 20008000 Uboot> tftp 20008000 uImage-zip-8000;tftp 21100000 ramdisk;bootm 20008000 ## Booting image at 20008000 ... Image Name: dd-kernel- Image Type: ARM Linux Kernel Image (gzip compressed) Data Size: 869629 Bytes = 849.2 kB Load Address: 20008000 Entry Point: 20008000 Verifying Checksum ... OK Uncompressing Kernel Image ... Error: inflate() returned -3 GUNZIP ERROR - must RESET board to recover 由于当前运行地址tftpaddr与解压缩后的地址-a重合了,导致解压缩失败,因此二者必须相隔一定的距离 <3> -a=0x20008000,-e = 0x20008040 ,tftpaddr= 0x21000000 能够解压到-a地址,但-e指定的入口不对,启动失败 Uboot> tftp 21000000 uImage-zip-8040;tftp 21100000 ramdisk;bootm 21000000 TFTP from server 192.168.0.12; our IP address is 192.168.0.15 Filename 'uImage-zip-8040'. Load address: 0x21000000 。。。。。。。。。 ## Booting image at 21000000 ... Image Name: dd-kernel- Image Type: ARM Linux Kernel Image (gzip compressed) Data Size: 869629 Bytes = 849.2 kB Load Address: 20008000 Entry Point: 20008040 Verifying Checksum ... OK Uncompressing Kernel Image ... OK Starting kernel ... 死了 <4> -a=-e = 0x20008000, tftpaddr= 0x20008000 解压失败,入口也不对,启动失败 (2)Mkimage 之前未对Image进行压缩 <1> -a=-e = 0x20008000 tftpaddr= 0x21000000 搬动到-a指定的地址,成功启动 Uboot> tftp 21000000 uImage-nzip-8000;tftp 21100000 ramdisk;bootm 21000000 ## Booting image at 21000000 ... Image Name: dd-kernel- Image Type: ARM Linux Kernel Image (uncompressed) Data Size: 1873059 Bytes = 1.8 MB Load Address: 20008000 Entry Point: 20008000 Verifying Checksum ... Bad Data CRC 为什么总是校验失败呢?当前的内核印象为 下面未拷贝ramdisk,校验成功,成功启动,无法安装跟文件系统,是因为无ramdisk。说明上面确实是覆盖了,因此要对于大的内核印象要合理设置tftpaddr的地址和ramdisk的地址 Addr(ramdisk)= 0x2110 0000 Addr(tftpaddr)= 0x2100 0000 Addr(ramdisk)-Addr(tftpaddr)= 0x10 0000 = Uboot> tftp 21000000 uImage-nzip-8000;bootm 21000000 ## Booting image at 21000000 ... Image Name: dd-kernel- Image Type: ARM Linux Kernel Image (uncompressed) Data Size: 1873059 Bytes = 1.8 MB Load Address: 20008000 Entry Point: 20008000 Verifying Checksum ... OK OK Starting kernel ... Linux version 。。。。。。。 Kernel panic: VFS: Unable to mount root fs on 01:00 Addr(ramdisk)- Uboot> tftp ## Booting image at Image Name: dd-kernel- Image Type: ARM Linux Kernel Image (uncompressed) Data Size: 1873059 Bytes = 1.8 MB Load Address: 20008000 Entry Point: 20008000 Verifying Checksum ... OK OK Starting kernel ... Linux version AT91RM9200DK login: root <2> -a=-e = 0x20008000, tftpaddr= 0x20008000 不搬动,但-e地址不对,失败 <3> -a=0x20008000,-e = 0x20008040 ,tftpaddr= 0x20008000 不搬动,但未成功启动,入口地址对的啊????? Uboot> tftp 20008000 uImage-nzip-8040;tftp 21100000 ramdisk;bootm 20008000 ## Booting image at 20008000 ... Image Name: dd-kernel- Image Type: ARM Linux Kernel Image (uncompressed) Data Size: 1873059 Bytes = 1.8 MB Load Address: 20008000 Entry Point: 20008040 Verifying Checksum ... OK XIP Kernel Image ... OK Starting kernel ...死了???? <4> -a=0x20008000,-e = 0x20008040 ,tftpaddr= 0x21000000 搬动,但-e地址不对,失败 (1)Mkimage 之前用gzip对zImage进行压缩,即-c gzip <1> -a=-e = 0x20008000,tftpaddr= 0x21000000 解压到-a指定的地址,成功启动 Uboot> tftp 21000000 uImage-zip-zImage-8000;tftp 21100000 ramdisk;bootm 21000000 TFTP from server 192.168.0.12; our IP address is 192.168.0.15 Filename 'uImage-zip-zImage-8000'. Load address: 0x21000000 ## Booting image at 21000000 ... Image Name: dd-zip-zImage-8000 Image Type: ARM Linux Kernel Image (gzip compressed) Data Size: 876753 Bytes = 856.2 kB Load Address: 20008000 Entry Point: 20008000 Verifying Checksum ... OK Uncompressing Kernel Image ... OK // U-boot对内核解压 Starting kernel ... Uncompressing Linux..............压缩内核zImage自解压......................... done, booting the kernel. Linux version AT91RM9200DK login: root [root@AT91RM9200DK /root]$ls <2> -a=-e = 0x20008000, tftpaddr= 0x20008000 解压失败,启动失败 Uboot> tftp 20008000 uImage-zip-zImage-8000;tftp 21100000 ramdisk;bootm 20008000 TFTP from server 192.168.0.12; our IP address is 192.168.0.15 Filename 'uImage-zip-zImage-8000'. Load address: 0x20008000 ## Booting image at 20008000 ... Image Name: dd-zip-zImage-8000 Image Type: ARM Linux Kernel Image (gzip compressed) Data Size: 876753 Bytes = 856.2 kB Load Address: 20008000 Entry Point: 20008000 Verifying Checksum ... OK Uncompressing Kernel Image ... Error: inflate() returned -3 GUNZIP ERROR - must RESET board to recover 由于当前运行地址tftpaddr与解压缩后的地址-a重合了,导致解压缩失败,因此二者必须相隔一定的距离 <3> -a=0x20008000,-e = 0x20008040 ,tftpaddr= 0x21000000,失败 Uboot> tftp 21000000 uImage-zip-zImage-8040;tftp 21000000 ramdisk;bootm 21000000 TFTP from server 192.168.0.12; our IP address is 192.168.0.15 Filename 'uImage-zip-zImage-8040'. Load address: 0x21000000 。。。。。。。。。 ## Booting image at 21000000 ... Bad Magic Number 难道对于压缩内核,幻数对的条件是-a==-e地址??即压缩内核默认-a==-e?? 此法肯定失败,但问题出在这,还真不对啊,不试了,感兴趣的朋友可以玩下 (2)Mkimage 之前未对zImage进行压缩,即-c none <1> -a=-e = 0x20008000 tftpaddr= 0x21000000 搬动到-a指定的地址,成功启动 Uboot> tftp 21000000 uImage-nzip-zImage-8000;tftp 21100000 ramdisk;bootm 21000000 ## Booting image at 21000000 ... Image Name: dd-nzip-zImage-8000 Image Type: ARM Linux Kernel Image (uncompressed) Data Size: 881748 Bytes = 861.1 kB Load Address: 20008000 Entry Point: 20008000 Verifying Checksum ... OK OK Starting kernel ... Uncompressing Linux............................................................. done, booting the kernel. Linux version AT91RM9200DK login: <2> -a=-e = 0x20008000, tftpaddr= 0x20008000 不搬动,但-e地址不对,失败 Uboot> tftp 20008000 uImage-nzip-zImage-8000;tftp 21100000 ramdisk;bootm 20008000 ## Booting image at 20008000 ... Image Name: dd-nzip-zImage-8000 Image Type: ARM Linux Kernel Image (uncompressed) Data Size: 881748 Bytes = 861.1 kB Load Address: 20008000 Entry Point: 20008000 Verifying Checksum ... OK XIP Kernel Image ... OK Starting kernel ... 死了。。。 <3> -a=0x20008000,-e = 0x20008040 ,tftpaddr= 0x20008000 不搬动,成功启动 Uboot> tftp 20008000 uImage-nzip-zImage-8040;tftp 21100000 ramdisk;bootm 20008000 ## Booting image at 20008000 ... Image Name: dd-nzip-zImage-8040 Image Type: ARM Linux Kernel Image (uncompressed) Data Size: 881748 Bytes = 861.1 kB Load Address: 20008000 Entry Point: 20008040 Verifying Checksum ... OK XIP Kernel Image ... OK Starting kernel ... Uncompressing Linux............................................................. done, booting the kernel. Linux version AT91RM9200DK login: <4> -a=0x20008000,-e = 0x20008040 ,tftpaddr= 0x21000000 搬动,但-e地址不对,失败 由上面的16个例子,我们可以看出,能够启动内核的由以下几种情况: 各种情况对应的统一ramdiskaddr= 0x21100000 <1>非压缩的Image内核: -a=-e = 0x20008000 ,–c=none,tftpaddr= 0x 此法主要由于内核太大,导致tftpaddr做了一定的修正 -a= 0x20008000 ,-e = 0x20008040,–c=none,tftpaddr=0x20008000 此法理论上可行,但我未试验成功,有兴趣的朋友可以探究下 对于非压缩的Image内核,mkimage之前不压缩的话,内核印象较大,此法不常用 -a=-e = 0x20008000 ,–c=gzip,tftpaddr= 0x21000000 –c=gzip压缩内核必须解压,只有这种情况成功;其他解压覆盖或者-e入口不对 <2>压缩的zImage内核: -a=-e = 0x20008000 ,–c=none,tftpaddr= 0x21000000 -a= 0x20008000 ,-e = 0x20008040,–c=none,tftpaddr=0x20008000 -a=-e = 0x20008000 ,–c=gzip,tftpaddr= 0x21000000 –c=gzip压缩内核必须解压,只有这种情况成功;其他解压覆盖或者-e入口不对 zImage已经压缩过一次了,一般无需再压缩,此法不常用 常见方法: <1>非压缩的Image内核: -a=-e = 0x20008000 ,–c=gzip,tftpaddr= 0x21000000 <2>压缩的zImage内核: -a=-e = 0x20008000 ,–c=none,tftpaddr= 0x21000000 -a= 0x20008000 ,-e = 0x20008040,–c=none,tftpaddr=0x20008000 待续: U-boot如何向Linux内核传递命令行参数? Go引导内核的详细方法? Ramdisk与initrd怎么传给内核? |
|