zoukankan      html  css  js  c++  java
  • 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函数来对内核传参并且启动内核;

     
  • 相关阅读:
    当Django模型迁移时,报No migrations to apply 问题时
    django--各个文件的含义
    django--创建项目
    1013. Battle Over Cities (25)
    1011. World Cup Betting (20)
    1009. Product of Polynomials (25)
    1007. Maximum Subsequence Sum (25)
    1006. Sign In and Sign Out (25)
    1008. Elevator (20)
    1004. Counting Leaves (30)
  • 原文地址:https://www.cnblogs.com/biaohc/p/6403863.html
Copyright © 2011-2022 走看看