• uboot分析:uboot启动内核


    (注:本文参考资料:朱有鹏嵌入式课程。本文为个人学习记录,如有错误,欢迎指正。)

    1. U-Boot启动内核概述

    • U-Boot启动完成后,最终进入到main_loop()循环中。若在bootdelay倒计时为0之前,U-Boot控制台有输入,则进入命令解析-执行的循环;若控制台无输入,U-Boot将启动内核。
    • U-Boot启动内核可归结为以下四个步骤:  

                               1)将内核搬移至DDR中;

                               2)校验内核格式、CRC;

                               3)准备传参;

                               4)跳转执行内核。

    2. U-Boot启动内核过程分析

    2.1 将内核搬移至DDR中

    • 在/uboot/lib_arm/board.c->start_armboot()函数调用/uboot/common/main.c->main_loop()函数,main_loop()函数中包含了内核的启动代码。
    s = getenv ("bootcmd");        //获取bootcmd环境变量的值
    debug ("### main_loop: bootcmd="%s"
    ", s ? s : "<UNDEFINED>");
    
    if (bootdelay >= 0 && s && !abortboot (bootdelay)) 
            {
                ................................
    
                #ifndef CFG_HUSH_PARSER
           run_command (s, 0);   //执行bootcmd环境变量中的命令
                ................................
            }
    • 环境变量bootcmd的值在开发板配置文件(/uboot/include/configs/x210_sd.h)中定义。
    #if defined(CFG_FASTBOOT_NANDBSP)
    #define CONFIG_BOOTCOMMAND    "nand read C0008000 600000 400000; nand read 30A00000 B00000 180000;  
                                                                     bootm C0008000 30A00000"
    #elif defined(CFG_FASTBOOT_SDMMCBSP)
    #define CONFIG_BOOTCOMMAND    "movi read kernel C0008000; movi read rootfs 30A00000 180000; 
                                                                     bootm C0008000 30A00000"
    #endif
    • 环境变量bootcmd包含如下命令:
    bootcmd=nand read C0008000 600000 400000;  /*将kernel(大小0x00400000字节)从nand中的0x00600000地址处拷贝到DDR中的 0xc0008000地址处*/
    nand read 30A00000 B00000 180000; /*将rootfs(大小0x00180000字节)从nand中的0x00B00000地址处拷贝到DDR中的 0x30A08000地址处*/
    bootm C0008000 30A00000 //启动kernel、rootfs
    • U-Boot能准确识别kernel、rootfs在NandFlash中的位置,是因为U-Boot中已对NandFlash进行分区。烧录时,严格按照分区要求将uboot、kernel、rootfs烧录进NandFlash中即可。
    • 在U-Boot下执行mtd命令,即可查看各个分区的情况。

    2.2 校验内核格式、CRC

    • 内核格式有两类:zImage和uImage。
    • 并不是所有U-Boot都支持zImage,是否支持就看其配置文件(x210_sd.h)中是否定义CONFIG_ZIMAGE_BOOT这个宏。所以有些uboot是支持zImage启动的,有些则不支持。但是所有的uboot肯定都支持uImage启动。
    zImage         

    Linux内核经过编译后生成一个ELF格式的可执行文件,vmlinux。再通过arm-linux-objcopy工具进行加工,最后进行压缩,得到zImage格式的内核镜像,可以烧录进启动介质中。

    uImage

    uImage是由zImage加工得到的。uboot中的mkimage工具将zImage加工生成uImage来给uboot启动。这个加工过程其实就是在zImage前面加上64字节的uImage的头信息即可。

    • ulmage格式的内核头部信息在/uboot/include/Image.h中定义。
    • ih_load是加载地址,即内核在DDR中的地址(运行地址);ih_ep是内核入口地址。
    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;
    • 执行环境变量bootcmd中的命令"bootm  C0008000  30A00000",实质是执行do_bootm()函数(/uboot/common/Cmd_bootm.c->do_bootm())。
    • do_bootm()函数在标号after_header_check之前,都是在校验内核的头部信息。根据头部信息,判断内核格式和进行CRC校验。
    • 实际上,从NandFlash中读取的uImage可以放置在DDR中的任意位置,只要不破坏U-Boot占有的内存空间即可。因为do_bootm()函数内部从头部获取内核的加载地址,当发现该uImage当前所处的地址与加载地址不同时,会将内核搬移至加载地址中。一般情况下,都会将内核搬移至加载地址中,便不用使用do_bootm()函数来搬移内核,提高效率。
    • do_bootm()函数将根据系统类型,启动内核。此处将调用/uboot/lib_arm/Bootm.c/->do_bootm_linux ()函数启动内核。
    after_header_check:
    os = hdr->ih_os;
    #endif
    
    switch (os) {
    default:    /* handled by (original) Linux case */
    
            case IH_OS_LINUX:
    #ifdef CONFIG_SILENT_CONSOLE
        fixup_silent_linux();
    #endif
        do_bootm_linux (cmdtp, flag, argc, argv, &images);
        break;
    
    case IH_OS_NETBSD:
        do_bootm_netbsd (cmdtp, flag, argc, argv, &images);
        break;
        .........................................
     

    2.3 准备传参

    • U-Boot中以标记链表(tagged list)形式来传递启动参数。
    • 标记是一种数据结构,标记链表就是挨着存放的多个标记。
    • 标记有多种类型,在/uboot/include/asm-arm/Setup.h中定义。标记链表以ATAG_CORE开始,以标记ATAG_NONE结束,中间包含其他标记。
    #define ATAG_CORE    0x54410001                    //起始标记
    #define ATAG_NONE    0x00000000                    //结束标记
    #define ATAG_MEM    0x54410002
    #define ATAG_VIDEOTEXT    0x54410003
    #define ATAG_RAMDISK    0x54410004
    #define ATAG_INITRD    0x54410005
    #define ATAG_INITRD2    0x54420005
    #define ATAG_SERIAL    0x54410006
    #define ATAG_REVISION    0x54410007
    #define ATAG_VIDEOLFB    0x54410008
    #define ATAG_CMDLINE    0x54410009
    #define ATAG_ACORN    0x41000101
    #define ATAG_MEMCLK    0x41000402
    #define ATAG_MTDPART    0x41001099
    • 标记的数据结构定义在/uboot/include/asm-arm/Setup.h中
    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;
            } u;
    };

    标记列表举例:

    static void setup_start_tag (bd_t *bd)
    {
    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);
    }
    
    #ifdef CONFIG_SETUP_MEMORY_TAGS
    static void setup_memory_tags (bd_t *bd)
    {
    int i;
    
    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);
    }
    }
    
    #endif
    
    ..........................................................//中间还有多个标记
    
    static void setup_end_tag (bd_t *bd)
    {
    params->hdr.tag = ATAG_NONE;   //结束标记
    params->hdr.size = 0;
    }
    • do_bootm_linux ()函数启动内核前,先将U-Boot中的启动参数传给内核。
    • 参数分析:

    1)0 : 相当于mov r0  #0。

    2)machid : U-Boot中的机器码,从全局变量bd中获取。内核机器码和U-Boot机器码必须一致才能启动内核。

    3)bd->bi_boot_params : 启动参数地址。

    void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[], bootm_headers_t *images)
    {
        .....................................
    
        //标记链表初始化
    
        setup_start_tag (bd);
        setup_serial_tag (&params);
        setup_revision_tag (&params);
        setup_memory_tags (bd);
        setup_commandline_tag (bd, commandline);
        setup_initrd_tag (bd, initrd_start, initrd_end);
        setup_videolfb_tag ((gd_t *) gd);
        setup_mtdpartition_tag();
        setup_end_tag (bd);
        .......................................
        void    (*theKernel)(int zero, int arch, uint params);       //定义函数指针theKernel
         ......................................
        theKernel = (void (*)(int, int, uint))ep;                             //将入口地址赋值给theKernel
        ......................................
        theKernel (0, machid, bd->bi_boot_params);                //传参,调用theKernel
        ......................................
    }

    2.4 跳转执行内核

    • theKernel()函数执行成功后,内核将读取启动参数,并开始启动。
  • 相关阅读:
    wait(),notify(),notifyAll()
    AsyncTask
    锻炼记忆力
    apache URL重写 标志表 以及 错误解决方法
    php如何判断字符串是否是字母和数字的组合
    linux 下screen 使用
    MongoDB运行状态、性能监控,分析
    批量 汉字 转 拼音方法
    mysql 数据库备份
    LINUX下 一句话添加用户并设置ROOT权限
  • 原文地址:https://www.cnblogs.com/linfeng-learning/p/9284315.html
Copyright © 2020-2023  润新知