uboot 启动内核代码分析

news2024/11/18 23:47:36

0、uboot和内核区别

uboot的本质就是一个复杂点的裸机程序。内核本身也是一个"裸机程序“,和uboot、和其他裸机程序并没有本质区别。
区别就是操作系统运行起来后在软件上分为内核层和应用层,分层后两层的权限不同,在内存访问和设备操作的管理上更加精细(内核可以随便访问各种硬件,而应用程序只能被限制的访问硬件和内存地址)。
直观来看:uboot的镜像是u-boot.bin,linux系统的镜像是zImage,这两个东西其实都是两个裸机程序镜像。从系统的启动角度来讲,内核其实就是一个大的复杂点裸机程序。

1、嵌入式系统部署在SD卡中特定分区内

(1)一个完整的嵌入式系统,静止时(未上电时)bootloader、kernel、rootfs等软件都以镜像的形式存储在启动介质中(X210中是iNand/SD卡);运行时都是在DDR内存中运行的,与存储介质无关。从静止态到运行态的过程,就是uboot启动内核的过程。

(2)启动过程就是一个将内核镜像从SD卡搬移到DDR内存去运行,最终达到稳定状态的过程。

(3)静止时u-boot.bin zImage rootfs都在SD卡中,他们不可以在SD卡中胡乱存放,而是存放在各自的分区中,这样在启动过程中uboot、内核等就知道到哪里去找。在uboot和kernel中都设置了一张分区表,这张分区表必须保持一致,并且要和实际烧写在SD卡中的位置一致。
譬如uboot的BL1从SD卡49扇区开始重定位整个uboot,那么事先就应该将uboot.bin烧写在49扇区。

2、内核运行必须加载到DDR中的链接地址处

(1)uboot在第一阶段中进行重定位时将第二阶段(整个uboot镜像)加载到DDR的0xc3e00000地址处,这个地址就是uboot的链接地址。(0xc3e00000是个虚拟地址,真实的物理地址是0x33e00000)

(2)内核也有类似要求,uboot启动内核时将内核从SD卡读取放到DDR中(其实就是个重定位的过程),不能随意放置,必须放在内核的链接地址处才能运行,否则内核启动不起来。我们使用的内核链接地址是0x30008000。

3、内核启动需要uboot传递必要的启动参数

(1)uboot是无条件自启动的。

(2)内核是不能开机自动启动的,uboot不仅要重定位内核到DDR内存,还要给内核提供必要启动参数才能运行(譬如uboot对内存的配置信息(维护在gd->bd的数据结构中)以及 uboot的环境变量bootargs 就是将来要传递给内核去使用的)。

4、启动内核第一步:加载内核到DDR中

(1)uboot要启动内核,首先就是要将内核加载到DDR中。uboot实现了重定位自己,但内核没有,内核代码从没考虑重定位自己,因为内核知道会有uboot之类的bootloader将自己加载到DDR中的链接地址处,所以内核直接就是从链接地址处开始运行的。

5、内核镜像在哪里?

(1)SD卡/iNand/Nand/NorFlash等启动介质:raw分区
启动时各种镜像都在SD卡中,内核镜像在SD卡的kernel分区,uboot使用movi命令将内核镜像从这个分区读到DDR中(譬如X210的iNand版本是movi命令,X210的Nand版本就是Nand命令)
使用命令:movi read kernel 30008000 将内核从iNand读取到DDR。其中kernel指的是uboot中的kernel分区(uboot在SD卡中划分了一个区域范围,这个区域范围专门用来存放kernel镜像)

(2)tftp、nfs等网络下载方式从远端服务器获取镜像
uboot还支持远程启动,也就是内核镜像不烧录到开发板的SD卡中,而是放在主机的服务器中,然后uboot通过网络从服务器中下载镜像到开发板的DDR中。
分析总结:
最终目的是要将内核镜像放到DDR中的特定地址,不管内核镜像是怎么到DDR中的。
以上2种方式各有优劣。
产品出厂时会设置为从SD卡中启动内核(客户不会还要搭建tftp服务器才能使用···);
tftp下载远程启动这种方式一般用来开发。

6、镜像要放在DDR的什么地址?

(1)内核一定要放在链接地址处,链接地址去内核源代码的链接脚本或者Makefile中去查找。X210中是0x30008000。

7、zImage和uImage的区别联系

7.1、bootm命令对应do_bootm函数

(1)命令名前加do_即可构成这个命令对应的函数,因此当我们bootm命令执行时,uboot实际执行的函数叫do_bootm函数,在cmd_bootm.c。

(2)do_bootm函数刚开始定义了一些变量,然后用宏来条件编译执行了secureboot的一些代码(主要进行签名认证),x210的uboot中并没有使用安全启动,先不管他;然后进行了一些数据结构的赋值操作,也不管他。然后到了CONFIG_ZIMAGE_BOOT,用这个宏来条件编译一段代码,这段代码是用来支持zImage格式的内核启动的。

7.2、vmlinuz和zImage和uImage

在这里插入图片描述
(1)uboot经过编译生成了elf格式的可执行程序u-boot,这个程序类似于windows下的exe格式,在Ubutun下是可以直接运行的。但是这种格式不能用来烧录下载。我们用来烧录下载使用的是u-boot.bin,它是对elf格式的u-boot使用arm-linux-objcopy工具进行加工处理(主要目的是去掉一些无用的)得到的。这个u-boot.bin就是镜像(image),镜像就是用来烧录到iNand中执行的。
在这里插入图片描述
(2)linux内核经过编译后也会生成一个elf格式的可执行程序,叫vmlinux或vmlinuz,这个就是原始的未经任何处理加工的内核文件;嵌入式系统部署时烧录的一般不是这个vmlinuz/vmlinux,而是要用objcopy工具去制作成烧录镜像格式(就是u-boot.bin这种,但是内核没有.bin后缀),内核经过objcopy工具加工得到的烧录镜像的文件就叫Image(把78M大的精简成了7.5M,因此这个制作烧录镜像主要目的就是缩减大小,节省磁盘)。

(3)原则上Image就可以直接被烧录到Flash上进行启动执行(类似于u-boot.bin),但是实际上并不是这么简单。实际上linux的作者们觉得Image还是太大了所以对Image进行了压缩,并且在image压缩后的文件的前端附加了一部分解压缩代码,这段代码是未经压缩的。两者一起构成的镜像就叫zImage。(因为当年Image大小刚好比一张软盘(软盘有2种,1.2M的和1.44MB两种)大,为了节省1张软盘的钱于是乎设计了这种压缩Image成zImage的技术)。
zImage = zImage信息头 + 解压缩代码 + image压缩后的文件

(4)uboot为了启动linux内核,还发明了一种内核格式叫uImage。uImage是由zImage加工得到的,uboot中有一个工具mkimage,可以将zImage加工生成uImage。
注意:uImage不关linux内核的事,linux内核只管生成zImage即可,所以在linux内核源码目录中是没有这个mkimage工具的,因此需要将这个工具从uboot目录中复制给linux内核去使用,然后使用mkimage工具将zImage加工生成uImage来给uboot启动。这个加工过程其实就是在zImage前面加上64字节的uImage的头信息。
uImage = uImage信息头 + zImage信息头 + 解压缩代码 + image压缩后的文件
在这里插入图片描述
(5)原则上uboot启动时应该给他uImage格式的内核镜像,但是实际上uboot中也可以支持zImage,是否支持就看x210_sd.h中是否定义了CONFIG_ZIMAGE_BOOT这个宏。
因此,有些uboot是支持zImage启动的,有些则不支持。但是所有的uboot肯定都支持uImage启动。

7.3、将内核编译成uImage格式去启动

(1)如果直接在kernel底下去make uImage会提供mkimage command not found错误。解决方案是去uboot/tools下cp mkimage /usr/local/bin/,复制mkimage工具到系统环境变量包含的目录下。再去make uImage即可。

8、zImage启动细节

(1)do_bootm函数中一直到397行的after_header_check这个符号处,都是在进行镜像的头部信息校验。校验时就要根据不同种类的image类型进行不同的校验。所以do_bootm函数的核心就是去分辨传进来的image到底是什么类型,然后按照这种类型的头信息格式去校验。校验通过则进入下一步准备启动内核;如果校验失败则认为镜像有问题,所以不能启动。
zImage = zImage头信息+解压缩代码+压缩后的image
uImage = uImage头信息+zImage头信息+解压缩代码+压缩后的image

8.1、LINUX_ZIMAGE_MAGIC

//cmd_bootm.c下的do_bootm函数第196~225行
#ifdef CONFIG_ZIMAGE_BOOT
#define LINUX_ZIMAGE_MAGIC    0x016f2818
    /* find out kernel image address */
    if (argc < 2) {
        addr = load_addr;                            //最终值为0x30000000
        debug ("*  kernel: default image load address = 0x%08lx\n",
                load_addr);
    } else {
        addr = simple_strtoul(argv[1], NULL, 16);    //argv[1] = 0x30008000
        debug ("*  kernel: cmdline image address = 0x%08lx\n", img_addr);
    }
//以上这段代码告诉我们镜像放在内存的什么位置

    if (*(ulong *)(addr + 9*4) == LINUX_ZIMAGE_MAGIC) {    //zImage头部开始的第37-40字节处存放着zImage的标志位,这里是判断这个镜像是不是zImage
        printf("Boot with zImage\n");    
        addr = virt_to_phys(addr);              //将虚拟地址转化为物理地址 c3e00000或33e00000 -> 33e00000
        hdr = (image_header_t *)addr;           
  /*image_header_t是一个结构体,zImage头信息也是image_header_t这种类型的数据结构,
   *所以这里是要将镜像文件addr中的zImage头信息单独取出,用hdr这个指针指向zImage头
。    
   */
          hdr->ih_os = IH_OS_LINUX;
               //改造zImage头信息中的ih_os和ih_ep
        hdr->ih_ep = ntohl(addr);

        memmove (&images.legacy_hdr_os_copy, hdr, sizeof(image_header_t));
                                                        //用zImage头信息构建images                                                          

        /* save pointer to image header */
        images.legacy_hdr_os = hdr;

        images.legacy_hdr_valid = 1;

        goto after_header_check;
    }
#endif

(1)LINUX_ZIMAGE_MAGIC这是一个定义的魔数,这个数等于0x016f2818,代表这个镜像是一个zImage。在zImage的头信息格式中某个位置定义了一个标志,这个标志用来表示镜像类型,如果这个标志等于0x016f2818,则说明这个镜像是zImage格式的。这里定义这个宏是为了之后和zImage的头信息中的标志进行比对。

(2)uboot命令行下使用命令 bootm 0x30008000 启动内核,所以do_bootm函数的argc=2,argv[0]=bootm argv[1]=0x30008000。但是实际bootm命令还可以不带参数执行。直接bootm,则会从load_addr这个默认地址去执行,load_addr = CFG_LOAD_ADDR = MEMORY_BASE_ADDRESS = 0x30000000(定义在x210_sd.h中)。

(3)zImage头部开始的第37-40字节处存放着标志类型的魔数,从这个位置取出后对比LINUX_ZIMAGE_MAGIC,相等则说明这个镜像文件是zImage格式,不相等则为其它格式。可以用二进制阅读软件来打开zImage查看,就可以证明,如winhex、UltraEditor。
在这里插入图片描述

8.2、image_header_t 212行

在这里插入图片描述

//do_bootm函数(209~224行)
 if (*(ulong *)(addr + 9*4) == LINUX_ZIMAGE_MAGIC) {    //zImage头部开始的第37-40字节处存放着zImage的标志位,这里是判断这个镜像是不是zImage
        printf("Boot with zImage\n");    
        addr = virt_to_phys(addr);              //将虚拟地址转化为物理地址 c3e00000或33e00000 -> 33e00000
        hdr = (image_header_t *)addr;           
  /*image_header_t是一个结构体,zImage头信息也是image_header_t这种类型的数据结构,
   *所以这里是要将镜像文件addr中的zImage头信息单独取出,用hdr这个指针指向zImage头
。    
   */
          hdr->ih_os = IH_OS_LINUX;
               //改造zImage头信息中的ih_os和ih_ep
        hdr->ih_ep = ntohl(addr);

        memmove (&images.legacy_hdr_os_copy, hdr, sizeof(image_header_t));
                                                        //用zImage头信息构建images                                                          

        /* save pointer to image header */
        images.legacy_hdr_os = hdr;

        images.legacy_hdr_valid = 1;

        goto after_header_check;
    }

(1)image_header_t是一个结构体,zImage头信息也是image_header_t这种类型的数据结构,所以这里是要将镜像文件addr中的zImage头信息单独取出,用hdr这个指针指向zImage头

后续还进行了一些改造。hdr->ih_os = IH_OS_LINUX; hdr->ih_ep = ntohl(addr);这两句就是在进行改造zImage头信息。
然后再将Image头信息构建成images这个变量,后续会用到。
在这里插入图片描述
(2)images全局变量仅仅只是在do_bootm函数中使用。zImage的校验过程其实就是先确认是不是zImage格式的镜像文件,确认后再修改zImage的头信息到合适,修改后用头信息去初始化images这个全局变量,然后就完成了校验,images这个变量再后面会被使用。

9、uImage启动细节

9.1、uImage启动

//cmd_bootm.c下的do_bootm函数第227~244行
 /* get kernel image header, start address and length */
    os_hdr = boot_get_kernel (cmdtp, flag, argc, argv,
            &images, &os_data, &os_len);
    if (os_len == 0) {
        puts ("ERROR: can't get kernel image!\n");
        return 1;
    }

    /* get image parameters */
    switch (genimg_get_format (os_hdr)) {
    case IMAGE_FORMAT_LEGACY:
        type = image_get_type (os_hdr);
        comp = image_get_comp (os_hdr);
        os = image_get_os (os_hdr);

        image_end = image_get_image_end (os_hdr);
        load_start = image_get_load (os_hdr);
        break;

(1)do_bootm函数237行中的IMAGE_FORMAT_LEGACY宏,LEGACY(表示遗留的),在do_bootm函数中,这种方式指的就是uImage的方式。uImage方式是uboot本身最早使用的支持linux启动的镜像格式,但是后来这种方式由于一些缺陷被另一种新的方式替代,这个新的方式就是设备树方式(在do_bootm函数中叫FIT,从do_bootm函数245行开始)
(2)uImage的启动校验主要在boot_get_kernel函数中,主要任务就是校验uImage的头信息,以下为boot_get_kernel函数内容:

//在cmd_bootm.c下的do_bootm函数第228行调用,定义在该文件下的562~715行。
/* find out kernel image address */
if (argc < 2) {        //和zImage处相同
    img_addr = load_addr;
        debug ("*  kernel: default image load address = 0x%08lx\n",
    load_addr);
#if defined(CONFIG_FIT)     //这个是设备树传参方式才有的
} else if (fit_parse_conf (argv[1], load_addr, &img_addr,
&fit_uname_config)) {
        debug ("*  kernel: config '%s' from image at 0x%08lx\n",
    fit_uname_config, img_addr);
} else if (fit_parse_subimage (argv[1], load_addr, &img_addr,
&fit_uname_kernel)) {
        debug ("*  kernel: subimage '%s' from image at 0x%08lx\n",
    fit_uname_kernel, img_addr);
#endif
} else {        //这个是uImage启动 镜像放在内存中的位置,可以看到和zImage启动相同,都是0x30008000
    img_addr = simple_strtoul(argv[1], NULL, 16);
        debug ("*  kernel: cmdline image address = 0x%08lx\n", img_addr);
}
show_boot_progress (1);    //打印一个调试信息,告诉我们启动了百分之多少

/* copy from dataflash if needed */  //实际没有使用,这个是对镜像的重定位,把镜像在30008000复制到30000000
img_addr = genimg_get_image (img_addr);


/* check image type, for FIT images get FIT kernel node */    //检验镜像的类型
*os_data = *os_len = 0;
switch (genimg_get_format ((void *)img_addr)) {
    case IMAGE_FORMAT_LEGACY:
        printf ("## Booting kernel from Legacy Image at %08lx ...\n",
img_addr);
    hdr = image_get_kernel (img_addr, images->verify);//校验镜像的一系列标志位,并且打印image的相关信息(最终是可以看到的)
        if (!hdr)            //hdr = img_addr = 0x30008000
        return NULL;
        show_boot_progress (5);

        
         /* get os_data and os_len */
            switch (image_get_type (hdr)) {
        case IH_TYPE_KERNEL:            //说明这是一个内核镜像
            *os_data = image_get_data (hdr);
            *os_len = image_get_data_size (hdr);
            break;
        case IH_TYPE_MULTI:
            image_multi_getimg (hdr, 0, os_data, os_len);
            break;
        default:
            printf ("Wrong Image Type for %s command\n", cmdtp->name);
            show_boot_progress (-5);
            return NULL;
        }
}
    /*
  * copy image header to allow for image overwrites during kernel
  * decompression.
  */
    
    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的处理一样
    show_boot_progress (6);
    break;
}
return (void *)img_addr;

以下为genimg_get_image函数内容,这个函数实际并没有使用,这个是对镜像的重定位,把镜像在30008000复制到30000000:

ulong genimg_get_image (ulong img_addr)
{
    ulong ram_addr = img_addr;               //0x30008000
#ifdef CONFIG_HAS_DATAFLASH           //实际不使用
    ulong h_size, d_size;

    if (addr_dataflash (img_addr)){
        /* ger RAM address */
        ram_addr = CFG_LOAD_ADDR;           //ram_addr = 0x30000000

        /* get header size */
        h_size = image_get_header_size ();  //uImage方式的校验头大小为sizeof(fdt_header)=64B                
#if defined(CONFIG_FIT)                //设备树方式使用
    if (sizeof(struct fdt_header) > h_size)
        h_size = sizeof(struct fdt_header);    //设备树方式的校验头大小为sizeof(fdt_header)=40B            
#endif

    /* read in header */
    debug ("   Reading image header from dataflash address "
    "%08lx to RAM address %08lx\n", img_addr, ram_addr);

    read_dataflash (img_addr, h_size, (char *)ram_addr);//复制uImage的校验头到ram_addr缓存

    /* get data size */
    switch (genimg_get_format ((void *)ram_addr)) {    //校验是uImage方式还是设备树传参方式
        case IMAGE_FORMAT_LEGACY:                                //uImage方式
        d_size = image_get_data_size ((image_header_t *)ram_addr);    //获取镜像的大小
            debug ("   Legacy format image found at 0x%08lx, size 0x%08lx\n",
            ram_addr, d_size);
            break;
#if defined(CONFIG_FIT)                                        //设备树传参方式
            case IMAGE_FORMAT_FIT:                                    //设备树传参方式
        d_size = fit_get_size ((const void *)ram_addr) - h_size;
            debug ("   FIT/FDT format image found at 0x%08lx, size 0x%08lx\n",
            ram_addr, d_size);
            break;
#endif
        default:
        printf ("   No valid image found at 0x%08lx\n", img_addr);
        return ram_addr;
    }

    /* read in image data */
    debug ("   Reading image remaining data from dataflash address "
    "%08lx to RAM address %08lx\n", img_addr + h_size,
    ram_addr + h_size);

    read_dataflash (img_addr + h_size, d_size,
(char *)(ram_addr + h_size));
    }
#endif /* CONFIG_HAS_DATAFLASH */

    return ram_addr;
}

总结1:uboot本身设计时只支持uImage启动,原来uboot的代码也是这样写的。后来有了fdt(设备树)方式之后,就把uImage方式命令为LEGACY方式,fdt方式命令为FIT方式,于是乎多了些#if #endif添加的代码。后来移植的人又为了省事添加了zImage启动的方式,又为了省事把zImage启动方式直接写在了uImage和fdt启动方式之前,于是乎又有了一对#if #endif。于是乎整个的代码看起来很恶心。
总结2:第二阶段校验头信息结束,下面进入第三阶段,第三阶段主要任务是启动linux内核,调用do_bootm_linux函数来完成。

10、do_bootm_linux函数(do_bootm函数407处调用)

(1)函数定义在uboot/lib_arm/bootm.c中(61~165行)。

void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[],
             bootm_headers_t *images)
{
    
    int    machid = bd->bi_arch_number;           //机器码
#ifdef CONFIG_CMDLINE_TAG
    char *commandline = getenv ("bootargs");   //获取环境变量bootargs的值,后面会作为参数传给内核
#endif

    /* find kernel entry point */
    if (images->legacy_hdr_valid) {          //第二步时就已经赋值为1,说明当前我们的镜像是有效的
        ep = image_get_ep (&images->legacy_hdr_os_copy);//ep为操作系统程序入口,从头信息中获取程序入口 

    

    } 
    theKernel = (void (*)(int, int, uint))ep; //ep本身只是一个数字,强制类型转换成一个函数去访问

    s = getenv ("machid");       //获取环境变量中的机器码
    if (s) {                //如果环境变量中有机器码
        machid = simple_strtoul (s, NULL, 16);    
                //替代第一次bd->bi_arch_number的赋值,可以看出环境变量的优先级更高
        printf ("Using machid 0x%x from environment\n", machid);
    }
//以下开始传参,uboot传递参数以tag格式的数据结构,一tag一tag的发送到指定地址处(设定为0x30001000)
//那内核从指定地址处取参,从哪里开始读取参数,从哪里结束取参?以start_tag开始,以end_tag结束的参数空间
#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)     //以上这下宏,哪个定义了,哪个将来就会以xxx_tag的参数形式发送给内核
    setup_start_tag (bd);
#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

    /* we assume that the kernel is in place */
    printf ("\nStarting kernel ...\n\n");



    theKernel (0, machid, bd->bi_boot_params);
 //启动内核,不再返回,uboot生命周期结束
 //参数 0, machid, bd->bi_boot_params  意思分别是:0, 机器码, 参数在内存中存放的首地址(0x30001000)
 //那么寄存器r0 = 0 , r1 = machid , r2 = bd->bi_boot_params  
    /* does not return */



    return;
}

(2)SI找不到(是黑色的)不代表就没有,要搜索一下才能确定;搜索不到也不能代表就没有,因为我们在向SI工程中添加文件时,SI只会添加它能识别的文件格式的文件,有一些像Makefile、xx.conf等Makefile不识别的文件是没有被添加的。所以如果要搜索的关键字在makefile中或者脚本中,可能就是搜索不到的。(譬如TEXT_BASE)

10.1、镜像的entrypoint

(1)ep就是entrypoint的缩写,就是程序入口。这个程序入口地址由(内核镜像地址 + 偏移量)得到。
内核镜像地址 = 0x30008000,而偏移量则记录在头信息中。

(2)一般执行一个内核镜像都是:
第一步先读取头信息,然后在头信息的特定地址找MAGIC_NUM,由此来确定镜像种类;
第二步对镜像进行校验,判断内核是不是一个完整有效的内核。
第三步再次读取头信息,在特定地址处知道这个镜像的各种信息(镜像种类、镜像长度、程序入口地址);
第四步就去程序入口entrypoint处开始执行镜像。
至此,OS内核启动。

(3)theKernel = (void (*)(int, int, uint))ep;
将ep强制转化成函数地址并赋值给theKernel,则theKernel这个函数指针就指向了内存中加载的OS镜像的真正入口地址(就是操作系统的第一句执行的代码)。

10.2、机器码的再次确定

 s = getenv ("machid");       //获取环境变量中的机器码
    if (s) {                //如果环境变量中有机器码
        machid = simple_strtoul (s, NULL, 16);    
                //替代第一次bd->bi_arch_number的赋值,可以看出环境变量的优先级更高
        printf ("Using machid 0x%x from environment\n", machid);
    }

(1)uboot在启动内核时,机器码要传给内核。uboot传给内核的机器码是怎么确定的?第一备选是环境变量machid,第二备选是uboot的一个数据结构gd->bd->bi_arch_num(x210_sd.h中硬编码配置的)

10.3、传参并启动概述
(1)从110行到144行就是uboot将要传递给内核的参数放到“以某个地址为开始的内存区域”(uboot代码中设置为0x30001000)。之后再将首地址告诉内核,内核就会从这里取走参数。

(2)Starting kernel … 这个是uboot中最后一句打印出来的东西。这句如果能出现,说明uboot整个是成功的,也成功的加载了内核镜像,也校验通过了,也找到入口地址了,也试图去执行了。如果这句后串口就没输出了,说明内核并没有被成功执行。原因一般是:传参(80%)、内核在DDR中的加载地址·······

11、传参详解

11.1、tag方式传参

struct tag_header {
    u32 size;       //指定tag的大小 
    u32 tag;        //指定tag的类型, core、mem、videotext......之类的
};
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;
        } u;
};

(1)struct tag,tag是一个数据结构,在uboot和linux kernel中都有定义tag数据机构,而且定义是一样的。

(2)tag_header和tag_xxx。tag_header中有这个tag的size和类型编码,kernel拿到一个tag后先分析tag_header得到tag的类型和大小,然后将tag中剩余部分当作一个tag_xxx来处理,tar_xxx取决于hdrr->tag的类型。

(3)tag_start与tag_end。kernel接收到的传参是若干个tag构成的,这些tag以tag_start起始,到tag_end结束。

(4)tag传参的方式是由linux kernel发明的,kernel定义了bootloder向我传参的方式,uboot只是实现了这种传参方式给内核传参而已。

11.2、x210_sd.h中配置传参宏

(1)CONFIG_SETUP_MEMORY_TAGS,tag_mem,传参内容是内存配置信息。uboot将内存的配置信息维护在gd->bd这个数据结构中,tag_men就是传递的这个
(2)CONFIG_CMDLINE_TAG,tag_cmdline,传参内容是启动命令行参数,也就是uboot环境变量的bootargs.
(3)CONFIG_INITRD_TAG
(4)CONFIG_MTDPARTITION,传参内容是iNand/SD卡的分区表。
(5)起始tag是ATAG_CORE、结束tag是ATAG_NONE,其他的ATAG_XXX都是有效信息tag。
思考:内核如何拿到这些tag?
在这里插入图片描述
uboot最终是调用theKernel函数来执行linux内核的,uboot调用这个函数(其实就是linux内核)时传递了3个参数。这3个参数就是uboot直接传递给linux内核的3个参数,通过寄存器来实现传参的。(第1个参数就放在r0中,第二个参数放在r1中,第3个参数放在r2中)第1个参数固定为0,第2个参数是机器码,第3个参数传递的就是大片传参tag的首地址。

11.3、移植时注意事项

(1)uboot移植时一般只需要配置相应的宏即可
(2)kernel启动不成功,注意传参是否成功。传参不成功首先看uboot中bootargs设置是否正确,其次看uboot是否开启了相应宏以支持传参。

12、uboot启动内核的总结

12.1、启动4步骤

第一步:将内核搬移到DDR中
第二步:校验内核格式、CRC(判断内核是否完整)等
第三步:准备参数
第四步:跳转执行内核

12.2、涉及到的主要函数是:do_boom和do_bootm_linux

12.3、uboot能启动的内核格式:zImage uImage fdt方式

12.4、跳转与函数指针的方式运行内核

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/496132.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

iptables 防火墙进出控制

iptables 防火墙进出控制 iptables简介 iptables 是集成在 Linux 内核中的包过滤防火墙系统。使用 iptables 可以添加、删除具体的过滤规则&#xff0c;iptables 默认维护着 4 个表和 5 个链&#xff0c;所有的防火墙策略规则都被分别写入这些表与链中。 “四表”是指 iptab…

ES是如何解决高可用

https://www.cnblogs.com/crazymakercircle/p/15433680.html ES是一个分布式全文检索框架&#xff0c;隐藏了复杂的处理机制&#xff0c;核心数据分片机制、集群发现、分片负载均衡请求路由。 ES的高可用架构&#xff0c;总体如下图&#xff1a; 说明&#xff1a;本文会以pdf…

SQL 招聘网站岗位数据分析

数据清洗 1.删除包含空字段的行 create view v_data_clean_null as select * from data d where job_href is not null and job_href ! and job_name is not null and job_name ! and company_href is not null and company_href ! and company_name is not null and com…

OpenAI ChatGPT Unity接入

OpenAI ChatGPT Unity接入 OpenAI ChatGPT Unity接入OpenAi-API-Unity 方法OpenAi-API-Unity 下载本地配置Unity 模块URL接入gz 接入json 接入Open AIOpenAi-Api-Unity 插件文档 OpenAi 本地化接入 Unity 方法Unity 关键字识别语音合成 & 文字转语音音频记录 & 实时音频…

基于物联网及云计算技术的智慧充电桩平台设计方案

针对目前的充电桩监管难题&#xff0c;如何逐一击破各个痛点&#xff1f; TSINGSEE可提供基于"智能充电设备&#xff0b;云平台&#xff0b;APP小程序"一体化完整的解决方案&#xff0c;解决当前充电桩运营商面临的各种运营和管理难题。 一、方案介绍 方案充分利用…

容器技术的发展

容器技术的发展 近年来&#xff0c;随着计算机硬件、网络以及云计算等技术的迅速发展&#xff0c;云原生的概念也越来越受到业界人士的广泛关注&#xff0c;越来越多的应用场景开始拥抱云原生&#xff0c;其中容器技术的发展起着至关重要的作用。本章将介绍容器技术的基础知识…

瘦身必备!四款低卡美食狂掉20斤肥肉

夏天来了&#xff0c;想要减肥瘦身&#xff0c;却总是被高卡路里的食物所困扰&#xff1f;别担心&#xff0c;今天我为大家介绍四款低卡掉秤减脂美食&#xff0c;让你轻松享受美食的同时还能达到减肥的目的。 这四款美食简单易做&#xff0c;口感也十分好吃&#xff0c;适合各…

《花雕学AI》ChatGPT 的 Prompt 用法,不是随便写就行的,这 13 种才是最有效的

ChatGPT 是一款基于 GPT-3 模型的人工智能写作工具&#xff0c;它可以根据用户的输入和要求&#xff0c;生成各种类型和风格的文本内容&#xff0c;比如文章、故事、诗歌、对话、摘要等。ChatGPT 的强大之处在于它可以灵活地适应不同的写作场景和目的&#xff0c;只要用户给出合…

【STM32】定时器PWM模式详解

PWM模式&#xff1a; PWM模式1&#xff0c;向上计数时&#xff0c;PWM信号从有效电平变为无效电平 PWM模式2&#xff0c;向上计数时&#xff0c;PWM信号从无效电平变为有效电平 PWM极性&#xff1a; 极性为高时&#xff0c;高电平为有效电平&#xff0c;低电平为无效电平 极性…

【Android取证篇】Android设备USB调试打开方式(开发者模式)

【Android取证篇】Android设备USB调试打开方式(开发者模式) Android各个版本系统手机开启”USB调试”的入口不全相同&#xff0c;仅供参考—【蘇小沐】 1、【Android1.0-3.2】 路径&#xff1a;在应用列表选择「设置」->「应用程序」->「开发」->勾选「USB调试」选…

拿来吧你——一个类帮你搞定SpringBoot中的请求日志打印

拿来吧你——一个类帮你搞定SpringBoot中的请求日志打印 日常开发工作中避免不了要打印请求日志&#xff0c;这个功能几乎在所有的项目中都需要编写一次&#xff0c;重复的次数多了&#xff0c;难免会感觉繁琐&#xff0c;因此打算搞一个通用类把这块功能拆出来。 废话不多说—…

虹科方案|使用 HK-TRUENAS支持媒体和娱乐工作流程-1

一、摘要 开发和交付能够随时随地触及受众的媒体内容变得越来越重要和复杂。 在当今高度互联、娱乐驱动的世界中&#xff0c;媒体和娱乐 (M&E) 公司需要保持竞争力才能取得成功。 这些组织需要制作各种不同格式的信息和娱乐内容&#xff0c;以便在移动设备、台式机、工作站…

MySQL---基本操作DDL(SQL特点,数据类型,对数据库的操作,对表的操作)

1. SQL的特点 具有综合统一性&#xff0c;不同数据库的支持的SQL稍有不同 非过程化语言 语言简捷&#xff0c;用户容易接受 以一种语法结构提供两种使用方式 2. 对数据库的常用操作 功能 SQL 查看所有的数据库 show databases&#xff1b; 创建数据库 create databa…

设备驱动模型:总线-设备-驱动

1 设备驱动模型简介 参考 以下内容&#xff1a; Linux 笔记&#xff1a; https://xuesong.blog.csdn.net/article/details/109522945?spm1001.2014.3001.5502正点原子-左盟主 驱动开发网络资料&#xff1a;https://www.cnblogs.com/lizhuming/category/1859545.html 1.1 概…

Github的加速访问

文章目录 概述Steam的下载Steam的安装使用 概述 GitHub打开访问速度比较慢&#xff0c;这儿介绍一种加速访问的方式&#xff0c;是正规的方式&#xff0c;采用 Steam 来加速。 Steam的下载 浏览器输入框输入Watt Toolkit进行搜索&#xff0c; 选择官网进入&#xff0c;网址 …

蚂蚁安全科技 Nydus 与 Dragonfly 镜像加速实践 | 龙蜥技术

编者按&#xff1a;本文详细介绍蚂蚁安全科技使用龙蜥社区技术进行镜像加速的实践过程&#xff0c;可以让您了解如何基于龙蜥社区推出的容器镜像&#xff0c;Nydus 与 Dragonfly 镜像加速技术和 LifseaOS 为容器的启动加速。文章转自金融级分布式架构&#xff0c;以下为全文。 …

计算材料学有哪些SCI期刊推荐? - 易智编译EaseEditing

以下是一些计算材料学领域的SCI期刊推荐&#xff1a; Computational Materials Science&#xff1a; 该期刊发表计算材料科学的理论、计算和实验研究&#xff0c;包括材料结构、热力学、物理和化学性质以及材料的设计、制备和性能等方面的内容。 Materials Horizons&#xff1…

JavaWeb——HTML中的常用标签详解

目录 一、HTML 1、HTML标签结构 2、HTML文件结构 &#xff08;1&#xff09;、定义 &#xff08;2&#xff09;、标签层次结构 二、HTML常见标签 1、注释标签 2、标题标签 3、段落标签 4、换行标签 5、格式化标签 6、图片标签 &#xff08;1&#xff09;、定义 &a…

4。计算机组成原理(3)指令系统

嵌入式软件开发&#xff0c;非科班专业必须掌握的基本计算机知识 核心知识点&#xff1a;数据表示和运算、存储系统、指令系统、总线系统、中央处理器、输入输出系统 指令系统&#xff08;Instruction Set&#xff09;是计算机体系结构的关键组成部分之一&#xff0c;它定义了处…

Java面试题复习(1)

目录 1.mysql使用innodb引擎&#xff0c;请简述mysql索引的最左前缀&#xff0c;如何优化order by语句 2.在JVM内存模型中&#xff0c;为什么要区分新生去和老年代&#xff0c;对于新生代为什么要区分eden区和survial区&#xff1f; 3.常见的远程调用有几种 4.对于外部衔接的…