• u-boot kernel启动


    u-boot kernel启动

      目前uboot已经可以在开发板上成功使用了,下一步,就是最关键的一步了——加载Linux内核到DDR上并启动内核。

    一、加载内核并启动

      uboot加载内核的方法一般分为两种。

    1.SD卡加载

      如果kernel在SD卡中,那么需要使用movi read kernel 30008000将内核加载到DDR上0x30008000的位置。
      uboot如何知道kernel在SD卡的那个扇区呢?根据iNand分区表得到的。

    2.tftp加载

      确保网络联通后,可以在虚拟机下构建tftp客户端实现烧录,加载时采用命令tftp 0x30008000 zImage-qt就可以完成内核到DDR的加载。

    3.启动

      可以使用bootm 0x30008000启动内核。

    二、uImage与zImage

      比较u-boot以及u-boot.bin

    u-boot
    u-boot

    u-boot.bin
    u-boot.bin

      可以看到直接连接编译得到的u-boot文件为ELF格式,是Linux下的可执行文件。值得注意的是该文件属于not stripped状态。该状态说明这个程序没有剔除符号表信息。用objcopy剔除之后生成文件u-boot.bin,可以烧录。
      剔除符号表信息前,文件有956KB,而剔除后,仅剩下384K。很明显,小体积的文件更适合烧录。
      Linux内核经过编译也会生成elf可执行程序,一般叫做vmlinuxvmlinuz。但是这个内核文件太大了,经过strip之后,就产生了Image文件。其实这个Image文件已经可以被烧录执行了。但是为了进一步压缩成本,所以Linux参与者再对这个Image进行了压缩,并且在压缩后的文件前端,加入了一部分解压缩代码。构成了压缩格式的zImage。
      uboot为了启动内核,就又发明了一种内核格式——uImage。加工的过程其实就是用uboot中的mkimage工具再zImage前加上64Bytes的头信息即可。


      在kernel目录下,执行make x210ii_qt_defconfig即可完成内核编译第一步配置,接下来使用make menuconfig进行内核模块化删改,最后make,即可得到zImage。

    三、do_bootm分析

      do_bootm是执行bootm的主要函数。

    关键结构体image_header_tbootm_headers_t
    • image_header_t
    /*
     * Legacy format image header,
     * all data in network byte order (aka natural aka bigendian).
     */
    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;
    • bootm_headers_t
    typedef struct bootm_headers {
    	/*
    	 * Legacy os image header, if it is a multi component image
    	 * then boot_get_ramdisk() and get_fdt() will attempt to get
    	 * data from second and third component accordingly.
    	 */
    	image_header_t	*legacy_hdr_os;		/* image header pointer */
    	image_header_t	legacy_hdr_os_copy;	/* header copy */
    	ulong		legacy_hdr_valid;
    
    	int		verify;		/* getenv("verify")[0] != 'n' */
    	struct lmb	*lmb;		/* for memory mgmt */
    } bootm_headers_t;
    流程图

      从这个流程图中可以看出,do_bootm函数至少经历了三次大改。第一次是初始的情况下,使用的是uImage。后来因为内核的更新,用条件编译在判断uImage原本的函数里加入设备树的相关配置。最后一次,则是直接独立出来判断zImage,用goto语句跳转。
      所以目前uboot支持三种镜像加载方式,分别为zImageuImage设备树

    zImage
    	if (*(ulong *)(addr + 9*4) == LINUX_ZIMAGE_MAGIC) {
    		printf("Boot with zImage
    ");
    		addr = virt_to_phys(addr);
    		hdr = (image_header_t *)addr;
    		hdr->ih_os = IH_OS_LINUX;
    		hdr->ih_ep = ntohl(addr);
    
    		memmove (&images.legacy_hdr_os_copy, hdr, sizeof(image_header_t));
    
    		/* save pointer to image header */
    		images.legacy_hdr_os = hdr;
    
    		images.legacy_hdr_valid = 1;

    zImage加载
    zImage加载

    uImage
    • boot_get_kernel
        检查输入参数,然后获取image的format type,并打印信息。经过image_check_magic,检测37到40字节的数是否是0x27051956,打印出检测到的Image格式,1代表uImage。
        因为是uImage格式,所以输出一些信息。
        接着使用image_get_type获得OS相关信息。
        image_get_kernel,先打印image的信息,然后确认内核的crc校验。并输出。
        设置bootm_headers结构体中的legacy_hdr_oslegacy_hdr_valid
      format
      加载地址
      image信息以及crc校验

    • genimg_get_format
        判断image的格式,然后获取OS相关信息。例如image的类型,压缩方式,OS名称,image_start,image_end,image_size,load_start,load_end。
      format

    • 判断压缩格式
        判断未使用压缩,所以做相应输出。
      压缩格式判断

    • 检查加载地址与Image地址

    	if ((load_start < image_end) && (load_end > image_start)) {
    		debug ("image_start = 0x%lX, image_end = 0x%lx
    ", image_start, image_end);
    		debug ("load_start = 0x%lx, load_end = 0x%lx
    ", load_start, load_end);
    
    		if (images.legacy_hdr_valid) {
    			if (image_get_type (&images.legacy_hdr_os_copy) == IH_TYPE_MULTI)
    				puts ("WARNING: legacy format multi component "
    					"image overwritten
    ");
    		} else {
    			puts ("ERROR: new format image overwritten - "
    				"must RESET the board to recover
    ");
    			show_boot_progress (-113);
    			do_reset (cmdtp, flag, argc, argv);
    		}
    	}
    四、do_bootm_linux分析

      do_bootm函数最终执行do_bootm_linux来完成内核加载的最后一步。

    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;
                    struct tag_mtdpart      mtdpart_info;
            } u;
    };

      tag_header中有这个tag的size和类型编码,kernel拿到一个tag后先分析tag_header得到tag的类型和大小,然后将tag中剩余部分当作一个tag_xxx来处理。
      内核传参是依靠上述结构体实现的。传参时,实际为一个结构体序列,以tag_start开始,以tag_end结束

    传参宏
    #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);
    #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
    #ifdef CONFIG_MTDPARTITION
    	setup_mtdpartition_tag();
    #endif
    	setup_end_tag (bd);
    #endif

      CONFIG_SETUP_MEMORY_TAGS,tag_mem,传参内容是内存配置信息。
      CONFIG_CMDLINE_TAG,tag_cmdline,传参内容是启动命令行参数,也就是uboot环境变量的bootargs.
    (  CONFIG_INITRD_TAG
      CONFIG_MTDPARTITION,传参内容是iNand/SD卡的分区表。
      起始tag是ATAG_CORE、结束tag是ATAG_NONE,其他的ATAG_XXX都是有效信息tag。

    如何传参

      先打印Starting kernel ...,然后开始准备传参。

    //	函数指针
    void	(*theKernel)(int zero, int arch, uint params);
    
    //	entry_point
    theKernel = (void (*)(int, int, uint))ep;
    
    //	开始执行内核,u-boot结束
    theKernel (0, machid, bd->bi_boot_params);
    /* does not return */
    return;

      uboot最终是调用theKernel函数来执行linux内核的,uboot调用这个函数(其实就是linux内核)时传递了3个参数。这3个参数就是uboot直接传递给linux内核的3个参数,通过寄存器来实现传参的。(第1个参数就放在r0中,第二个参数放在r1中,第3个参数放在r2中)第1个参数固定为0,第2个参数是机器码,第3个参数传递的就是大片传参tag的首地址。

  • 相关阅读:
    Python连接MySQL乱码(中文变问号)
    mysql学习04 pymysql 学习
    mysql学习03
    多态与多态性
    重用父类功能的两种方式
    菱形继承问题
    组合
    继承的应用和派生的概念引出
    类的继承
    类与类型
  • 原文地址:https://www.cnblogs.com/0nism/p/12380551.html
Copyright © 2020-2023  润新知