• UBOOT——启动内核


    1:什么是UBOOT,为什么要有UBOOT?

      UBOOT的主要作用是用来启动linux内核,因为CPU不能直接从块设备中执行代码,需要把块设备中的程序复制到内存中,而复制之前还需要进行很多初始化工作,如时钟、串口、dram等;

      如要想让CPU启动linux内核,只能通过另外的程序,进行必要的初始化工作,在把linux内核中代码复制到内存中,并执行这块内存中的代码,即可启动linux内核;一般情况下,我们把linux

      镜像储存在块设备中如SD卡、iNand、Nandflash等块设备中,首先执行UBOOT带码,在UBOOT中把块设备中的内核代码复制到内存地址0x30008000地址处,然后在执行bootm 0x30008000

      命令来执行内核代码;

    整个过程大致如上述所讲,下面我们详细分析一下UBOOT启动内核的代码:

     2:在启动UBOOT时候会出现看机倒计时,如果没有按键按下,会自动启动内核,我们来看一下这个是如何实现的:

    下面这段代码是在main_loop函数中:作用是执行完倒数计时函数以后启动linux内核,启动方式是 s = getenv ("bootcmd");我们假定不使用HUAH_PARSER的情况下 run_command (s, 0);

    实际上就是读取环境变量bootcmd,然后执行这个命令;

    s = getenv ("bootcmd");
    
        debug ("### main_loop: bootcmd="%s"
    ", s ? s : "<UNDEFINED>");
    
        if (bootdelay >= 0 && s && !abortboot (bootdelay)) {
    
    #ifndef CFG_HUSH_PARSER
            run_command (s, 0);
    #else
            parse_string_outer(s, FLAG_PARSE_SEMICOLON |
                        FLAG_EXIT_FROM_LOOP);

    看一下bootcmd命令:bootcmd=movi read kernel 30008000; movi read rootfs 30B00000 300000; bootm 30008000 30B00000

    movi read kernel 30008000 以及 bootm 30008000

    这两个命令来完成linux内核启动的:

    movi read kernel 30008000是把sd卡中kernel分区复制到30008000内存地址处,bootm 30008000即到内存地址处执行代码;

    下面详细分一下bootm这个命令对应的函数

    代码一步步分析:

    下面这段代码的作用是判断内核镜像是zImage、uImage、设备树

    在这里要解释一下zImage、uImage的区别:

    linux内核代码经过编译链接以后生成一个elf文件名叫vmlinuz文件,这个文件在经过arm-linux-objcopy编译以后会生成一个Image镜像文件,vmlinuz elf文件大小为70M以上

    而Image镜像文件为7M左右,然后Image文件在进一步经过压缩生成zImage文件,当zImage文件作为启动镜像来启动时,首先要解压这个文件,这个解压过程可以由uboot解压

    或者zImage文件本身可以自解压,zImage中除了linux内核的镜像以外,还有一些头文件以及这部分解压代码,所以内核实际上在addr地址中在加一个偏移量的位置;

    uImage是uboot自己专用的启动内核镜像,相对于zImage他们之间头文件有一定区别可以详细看代码是如何判断的;uImage现在基本上要属于过时的技术了,新一点的技术为

    设备树的启动方式;

    我们时这么使用bootm命令的:bootm 0x30008000

    走的是addr = simple_strtoul(argv[1], NULL, 16);

    addr中的值为0x30008000

    接下来判断0x30008000右偏移36字节以后,这个地址中的值如果为 0x016f2818这个魔数的话,说明启动镜像为zImage则 输出boot with zImage,

       hdr->ih_os = IH_OS_LINUX;      zImage header中 IH_os 赋值为 IH_OS_LINUX;

            hdr->ih_ep = ntohl(addr);      ih_ep 中存放的是point address 这个值实际上就是真正内核代码的地址;

    在看下面这句代码

    memmove (&images.legacy_hdr_os_copy, hdr, sizeof(image_header_t));

    把hdr中的值复制一份到 image.legacy_hdr_os_copy中,即把内存地址0x30008000处设置好的zImage头复制一份到uboot的data段,

    因为static bootm_headers_t images; images为uboot内定义的一个bootm_header_t格式的全局变量;

    看一下bootm_header_t类型为一个结构体,包含一个image_header_t类型的指针,这个指针最后指向了0x30008000处的zImage header

    还包含一个image_header_t类型的结构体,就是用上面那句代码把0x30008000处的zImage header在酯类复制了一份;

    还包含一个标志位 legacy_hdr_valid如果上面两个赋值以后,把legacy_hdr_valid赋值为1;

    typedef struct bootm_headers {
        
        image_header_t    *legacy_hdr_os;        /* image header pointer */
        image_header_t    legacy_hdr_os_copy;    /* header copy */
        ulong        legacy_hdr_valid;
    }

     uint8_t ih_os; /* Operating System */ 

    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;
    #ifdef CONFIG_ZIMAGE_BOOT
    #define LINUX_ZIMAGE_MAGIC    0x016f2818
        /* find out kernel image address */
        if (argc < 2) {
            addr = load_addr;
            debug ("*  kernel: default image load address = 0x%08lx
    ",
                    load_addr);
        } else {
            addr = simple_strtoul(argv[1], NULL, 16);
            debug ("*  kernel: cmdline image address = 0x%08lx
    ", img_addr);
        }
    
    
        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;
    
            goto after_header_check;
        }
    #endif

     直接跳转到after_header_check处,os为IH_OS_LINUX

    下面判断操作系统,然后调用do_bootm_linux函数;

    do_bootm_linux (cmdtp, flag, argc, argv, &images);

     1 #if defined(CONFIG_ZIMAGE_BOOT)
     2 after_header_check:
     3     os = hdr->ih_os;
     4 #endif
     5 
     6     switch (os) {
     7     default:            /* handled by (original) Linux case */
     8     case IH_OS_LINUX:
     9 #ifdef CONFIG_SILENT_CONSOLE
    10         fixup_silent_linux();
    11 #endif
    12         do_bootm_linux (cmdtp, flag, argc, argv, &images);
    13         break;
    14 
    15     case IH_OS_NETBSD:
    16         do_bootm_netbsd (cmdtp, flag, argc, argv, &images);
    17         break;
    18 
    19 #ifdef CONFIG_LYNXKDI
    20     case IH_OS_LYNXOS:
    21         do_bootm_lynxkdi (cmdtp, flag, argc, argv, &images);
    22         break;
    23 #endif
    24 
    25     case IH_OS_RTEMS:
    26         do_bootm_rtems (cmdtp, flag, argc, argv, &images);
    27         break;
    28 
    29 #if defined(CONFIG_CMD_ELF)
    30     case IH_OS_VXWORKS:
    31         do_bootm_vxworks (cmdtp, flag, argc, argv, &images);
    32         break;
    33 
    34     case IH_OS_QNX:
    35         do_bootm_qnxelf (cmdtp, flag, argc, argv, &images);
    36         break;
    37 #endif
    38 
    39 #ifdef CONFIG_ARTOS
    40     case IH_OS_ARTOS:
    41         do_bootm_artos (cmdtp, flag, argc, argv, &images);
    42         break;
    43 #endif
    44     }
    45 
    46     show_boot_progress (-9);
    47 #ifdef DEBUG
    48     puts ("
    ## Control returned to monitor - resetting...
    ");
    49     do_reset (cmdtp, flag, argc, argv);
    50 #endif
    51     if (iflag)
    52         enable_interrupts();
    53 
    54     return 1;
    55 }

    下面看一下do_bootm_linux都做了哪些事情

    #ifdef CONFIG_CMDLINE_TAG
        char *commandline = getenv ("bootargs");
    #endif

    首先获取环境变量bootargs:

    if (images->legacy_hdr_valid) {
            ep = image_get_ep (&images->legacy_hdr_os_copy)

    else {
    puts ("Could not find kernel entry point! ");
    goto error;
    }

     

    在判断全局变量images中的legacy_hdr_valid是否为1,如果为1 获取ep 值;如果为1读出ep的值,如果不为1则erro

    theKernel = (void (*)(int, int, uint))ep;
    
        s = getenv ("machid");
        if (s) {
            machid = simple_strtoul (s, NULL, 16);
            printf ("Using machid 0x%x from environment
    ", machid);
        }

    把ep强制类型换换为函数指针类型复制给thekernel;

    从环境变量中读取machid的值,赋值给s,如果s不空 则machid = 环境变量中machid的值,并打印machid;

    在看一下uboot如何给内核传参: 

     传参主要是uboot把与硬件有关的信息传给linux内核,如memory信息几bank size 起始地址、命令行信息、lcd 串口、initrd、MTD等信息

    #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_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 ("
    Starting kernel ...
    
    ");
    
    #ifdef CONFIG_USB_DEVICE
        {
            extern void udc_disconnect (void);
            udc_disconnect ();
        }
    #endif
    
        cleanup_before_linux ();
    
        theKernel (0, machid, bd->bi_boot_params);
        /* does not return */

    首先:如要定义了任意一个CONFIG_XXXXX的话

    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);
    }

     

    struct tag_header {
        u32 size;
        u32 tag;
    };

     

    首先要setup_start_tag(bd); 这个函数的作用 

      params = (struct tag *) bd->bi_boot_params; 给params赋值,gd->bd->bi_boot_params = (PHYS_SDRAM_1+0x100);

      这句代码的作用就是把uboot全局变量中设定好的bi_boot_params内存地址处强制转换为stuct tag* 类型赋值给params

      分析一下struct tag结构体:它是由一个stuct tag_header类型的结构体加上一个由一系列结构体组成的union联合体组成;

      这一系列结构体中存放的就是与board有关的参数;

      把PHYS_SDRAM_1+0x100这个地址设置为传参的起始地址;

      params->hdr.tag = ATAG_CORE;

      params->hdr.size = tag_size (tag_core);

      hdr.tag 与hdr.size赋值;

      params->u.core.flags = 0;

      params->u.core.pagesize = 0;

      params->u.core.rootdev = 0;

      然后对联合体中的结构体参数赋值;

       #define tag_next(t) ((struct tag *)((u32 *)(t) + (t)->hdr.size))

      params = tag_next (params);

      在把params向右移动sizeof(tag_core)大小

       继续赋值:

      #ifdef CONFIG_SETUP_MEMORY_TAGS

       setup_memory_tags (bd); 

      #endif

       这段代码是传递内存参数:

      把内存每个bank的信息放到这里:第一个扇区的起始地址和大小,第二个扇区的起始地址和大小

     1 #ifdef CONFIG_SETUP_MEMORY_TAGS
     2 static void setup_memory_tags (bd_t *bd)
     3 {
     4     int i;
     5 
     6     for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) {
     7         params->hdr.tag = ATAG_MEM;
     8         params->hdr.size = tag_size (tag_mem32);
     9 
    10         params->u.mem.start = bd->bi_dram[i].start;
    11         params->u.mem.size = bd->bi_dram[i].size;
    12 
    13         params = tag_next (params);
    14     }
    15 }
    16 #endif /* CONFIG_SETUP_MEMORY_TAGS */

      接着是传递命令行参数

     1 static void setup_commandline_tag (bd_t *bd, char *commandline)
     2 {
     3     char *p;
     4 
     5     if (!commandline)
     6         return;
     7 
     8     /* eat leading white space */
     9     for (p = commandline; *p == ' '; p++);
    10 
    11     /* skip non-existent command lines so the kernel will still
    12      * use its default command line.
    13      */
    14     if (*p == '')
    15         return;
    16 
    17     params->hdr.tag = ATAG_CMDLINE;
    18     params->hdr.size =
    19         (sizeof (struct tag_header) + strlen (p) + 1 + 4) >> 2;
    20 
    21     strcpy (params->u.cmdline.cmdline, p);
    22 
    23     params = tag_next (params);
      }

    前面我们分析了commandline是一个char *类型,指向环境变量中的bootargs的值;

    #define CONFIG_BOOTARGS     "root=/dev/mtdblock4 rootfstype=yaffs2 init=/init console=ttySAC0,115200"

     最后setup_end_tag (bd);结束传参

    再看最后uboot中最后一句代码

    theKernel (0, machid, bd->bi_boot_params);
        /* does not return */
        return;

    通过执行thekernel函数直接启动linux内核,传递三个参数0、machid、传参的首地址;

    这三个参数是通过r0、r1、r2三个寄存器来传递了,r0传递0、r1传递machid、r2传递传参的首地址;

    这样就启动起来linux内核了;

    -----------------------------------------------------------------------------------------------------------------------------------

    下面我们再来总结一下uboot启动linux内核的整个流程:

    开机时会出现倒计时,当没有按键按下的时候,uboot会读取出bootcmd这个环境变量,并使用rum_command函数来执行这个命令;

    实质是执行了movi read kernel 30008000;以后在执行bootm 30008000

    moviread kernel的作用是把sd卡中的kernel分区赋值到30008000内存处

    bootm 30008000就是真正的传参以及跳转到linux内核中执行;

    bootm 首先要做的事情是判断这个内核镜像为zImage、uImage、设备树

    通过对镜像文件的头文件的验证,确定是哪种内核镜像,然后再把必须的信息储存起来如是linux操作系统,ep的值等;

    确定好以后调用do_bootm_linux函数来对内核传参并且启动内核;

     
  • 相关阅读:
    又见Python<4>:Pandas之DataFrame对象的使用
    又见Python<3>:Pandas之Series对象的使用
    使用tdload工具将本地数据导入到Teradata数据库中
    解决ubuntu系统root用户下Chrome无法启动问题
    又见Python<2>:如何安装第三方库(Windows)
    又见Python<1>:使用Anaconda搭建Python开发环境(Windows7)
    数据仓库原理<4>:联机分析处理(OLAP)
    数据仓库原理<3>:数据仓库与ODS
    配置hibernate,Struts。文件
    jQuery +ajax +json+实现分页
  • 原文地址:https://www.cnblogs.com/biaohc/p/6403863.html
Copyright © 2020-2023  润新知