uboot启动内核笔记

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/SilverFOX111/article/details/86756748


1. uboot与内核的关系

uboot其实全名叫Universal BootLoader(通用引导装载程序),是专门用来启动内核的引导代码。每个要启动内核的CPU必须先执行uboot代码初始化运行环境,然后将内核代码从启动介质(比如iNand)搬移到内核链接指定地址处,最后跳转执行内核完成启动内核的工作。uboot与内核起初都被烧录到启动介质指定扇区处,而烧录的文件(镜像)分别为u-boot.bin与zImage,在电源打开时,CPU会自动取走uboot到SRAM中,然后按照上述的步骤直到完成自己的使命。除了上面的两个镜像外,一般还有个rootfs。

2. 内核的各个版本

u-boot.bin镜像的前身其实是可执行程序u-boot(elf格式),u-boot通过arm-linux-objcopy工具修改为u-boot.bin。同样的,zImage最原先是vmlinux或vmlinuz文件,格式也是elf,这个文件78M,而且它不能被CPU直接执行,必须通过arm-linux-objcopy工具修改为Image,这个文件7.5M,但当时启动介质一般是软盘,大小为1.2M和1.44MB两种,为了能够节省软盘,开源社区将Image镜像压缩后,并在镜像前加了一段解压缩代码,构成了zImage镜像。然后,uboot开源社区为了启动内核,还发明了另外一种镜像uImage (zImage前面加上64字节的uImage的头信息) ,这个镜像是由zImage通过make uImage命令获得的,其中用到了一个文件mkimage ,这个文件可以在uboot的BSP中找到。此外究竟是使用zImage还是uImage(这两个都可以被uboot启动,但有些uboot不支持zImage,相反,所有uboot都支持uImage),可以通过x210_sd.h 中是否定义了LINUX_ZIMAGE_MAGIC这个宏来决定。

3. do_bootm函数(…/uboot/common/Cmd_bootm.c)

在uboot启动内核时会调用一个命令,这个命令就是bootm,它是由do_bootm函数实现的(一般所有命令的实现函数名都是do_xx)。这个函数包含了两种启动方式,一种是无头校验的zImage(实际上zImage不是官方的本意,大部分uboot还是uImage方式启动,只不过有的公司为了启动方便,在这个函数中强行增加了无头校验的代码,并使用goto语句跳过了头校验部分),另一种是有头校验的uImage,此外,在内核3.x版本又新增了设备树启动方式,这个可以通过定义宏CONFIG_FIT 来实现。

3.1 zImage的一些启动细节

要使用zImage方式,必须定义CONFIG_ZIMAGE_BOOT这个宏,在X210_sd.h 中可以定义。do_bootm函数的第197到224行是zImage的特殊启动部分,在最开始先定义了一个魔数#define LINUX_ZIMAGE_MAGIC 0x016f2818 ,这个数用来与zImage镜像的第37-40个字节进行对比,若是就执行zImage方式启动,否则执行uImage方式启动。而且,zImage支持无参数启动,内核地址选用默认地址MEMORY_BASE_ADDRESS(0x30000000)。另外 images 这个全局变量保存了zImage启动的头信息,以及镜像起始地址,检验成功标志位。在这部分代码中,会修改头信息比如os(操作系统类型),ep(运行地址),并会设置检验标志位为1,但这种方式坏处是在移植uboot的时候需要查看是否有必要修改这部分代码。在这部分中会打印Boot with zImage的启动信息。

3.2 uImage的一些启动细节

区别于zImage启动方式,uImage启动显然要更加繁琐些,这是因为uboot需要真正的校验了uImage头,除了魔数#define IH_MAGIC 0x27051956 的校验,还有镜像头和数据循环冗余码校验,以及校验是否是支持的架构,这些都在image_get_kernel 这个函数中完成。上面的函数是在boot_get_kernel 函数中调用,这个函数除了校验外,还对启动方式进行了甄别,获取了镜像地址,镜像头,长度和镜像实际地址。此外,uImage还提供了几种解压缩算法用于解压缩镜像。而且,在3.x版本uImage增加了FIT设备树的内容。

3.3 do_bootm_linux函数

在执行完zImage或uImage的独立代码后,do_bootm 会对系统类别进行甄别选择一种符合镜像的启动方式启动,比如linux内核会执行do_bootm_linux 函数。do_bootm_linux 函数会通过找到 images 变量找到内核实际运行开始地址ep,然后将ep强制类型转换为函数指针theKernel theKernel = (void (*)(int, int, uint))ep;。而且这个函数会获取机器码的环境变量值,打印出来并最后传参给内核。在检查好磁盘内容后,清除cache内容,执行theKernel (0, machid, bd->bi_boot_params);代码,启动内核,并打印uboot的最后一句话“\nStarting kernel …\n\n”。

3.4 theKernel函数的传参

在介绍 theKernel 函数的传参的传参之前,先引入一个特殊的数据结构tag:

struct tag_header {
	u32 size;
	u32 tag;
};
struct tag {
        struct tag_header hdr;
        union { 
                struct tag_core         core;
                struct tag_mem32        mem;
                struct tag_videotext    videotext;
                struct tag_ramdisk      ramdisk;
                struct tag_initrd       initrd;
                struct tag_serialnr     serialnr;
                struct tag_revision     revision;
                struct tag_videolfb     videolfb;
                struct tag_cmdline      cmdline;
                
                /*
                * Acorn specific
                */
                struct tag_acorn        acorn;
                
                /*
                 * DC21285 specific
                 */
                struct tag_memclk       memclk;
                
                struct tag_mtdpart      mtdpart_info;//iNand/SD卡的分区表
        } u;
};

正如代码所示,uboot传的每一个参数都是这样一个数据结构,他有一个头,里面放着当前这个参数的类型以及下一个参数的位置(当前的地址加当前数据结构的大小就是下一个参数的位置)。它还有一个专属的参数数据结构,比如有CPU的,内存的,还有命令行(用于自启动,也就是uboot环境变量的bootargs)的等等,这些必须定义各自的宏才能添加到参数列表中,代码如下:

#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) || \
    defined (CONFIG_MTDPARTITION)
	setup_start_tag(bd); //core
#ifdef CONFIG_SERIAL_TAG
	setup_serial_tag(&params);
#endif
#ifdef CONFIG_REVISION_TAG
	setup_revision_tag(&params);
#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

#ifdef CONFIG_MTDPARTITION
	setup_mtdpartition_tag();
#endif

	setup_end_tag(bd);
#endif

在这,我们用一个表格更形象的表示一下具体的参数结构:

position content
start tag ATAG_CORE
size (tag_core)
core.flags
core.pagesize
core.rootdev
memory1 tag ATAG_MEM
size (tag_mem32)
bd->bi_dram[i].start
bd->bi_dram[i].size
memory2 tag ATAG_MEM
size (tag_mem32)
bd->bi_dram[i].start
bd->bi_dram[i].size
… … … … … … … …
end tag ATAG_NONE
0

因此只要bd->bi_boot_params指针指向最开始tag的地址,然后传给内核,内核就能一个个的把参数读出来,另外说明一点,这儿的传参是通过寄存器r0,r1,r2传递的。

4. uboot 启动内核方法

  • 使用movi命令将内核搬移到启动位置,使用bootm命令启动内核
  • 设置bootargs环境变量值,开机自启动
  • 使用tftp下载内核到指定位置,使用bootm命令启动

猜你喜欢

转载自blog.csdn.net/SilverFOX111/article/details/86756748