[uboot] uboot启动kernel篇(二)——bootm跳转到kernel的流程

转载地址:https://blog.youkuaiyun.com/ooonebook/article/details/53495021

一、bootm说明

bootm这个命令用于启动一个操作系统映像。它会从映像文件的头部取得一些信息,这些信息包括:映像文件的基于的cpu架构、其操作系统类型、映像的类型、压缩方式、映像文件在内存中的加载地址、映像文件运行的入口地址、映像文件名等。 
紧接着bootm将映像加载到指定的地址,如果需要的话,还会解压映像并传递必要有参数给内核,最后跳到入口地址进入内核。 
这里的描述参考(http://blog.chinaunix.net/uid-20799298-id-99666.html

需要打开的宏

CONFIG_BOOTM_LINUX=y
CONFIG_CMD_BOOTM=y

二、bootm使用方式

在《uboot启动kernel篇(一)——Legacy-uImage & FIT-uImage》中我们知道了uImage有两种格式。

  • Legacy-uImage 
    对于Legacy-uImage,我们需要另外加载ramdisk和fdt到RAM上面。 
    执行的命令如下
假设Legacy-uImage的加载地址是0x20008000,ramdisk的加载地址是0x21000000,fdt的加载地址是0x22000000

(1) 只加载kernel的情况下
bootm 0x20008000

(2) 加载kernel和ramdisk
bootm 0x20008000 0x21000000

(3) 加载kernel和fdt
bootm 0x20008000 - 0x22000000

(4) 加载kernel、ramdisk、fdt
bootm 0x20008000 0x21000000 0x22000000
  • FIT-uImage 
    对于FIT-uImage,kernel镜像、ramdisk镜像和fdt都已经打包到FIT-uImage的镜像中了。 
    执行的命令如下
假设FIT-uImage的加载地址是0x30000000,启动kernel的命令如下:
bootm 0x30000000

三、bootm执行流程

建议先参考《[uboot] (第六章)uboot流程——命令行模式以及命令处理介绍》。

  • 对应U_BOOT_CMD 
    我们找到bootm命令对应的U_BOOT_CMD如下: 
    cmd/bootm.c
U_BOOT_CMD(
    bootm,  CONFIG_SYS_MAXARGS, 1,  do_bootm,
    "boot application image from memory", bootm_help_text
);
当执行‘bootm 0x20008000 0x21000000 0x22000000’
int do_bootm(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
cmdtp:传递的是bootm的命令表项指针,也就是_u_boot_list_2_cmd_2_bootm的指针
argc=4
argv[0]="bootm", argv[1]=0x20008000, arv[2]=0x21000000, argv[3]=0x22000000
  • do_bootm实现 
    do_bootm实现如下:

/*******************************************************************/
/* bootm - boot application image from image in memory */
/*******************************************************************/

int do_bootm(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
    /* determine if we have a sub command */
    argc--; argv++;
    if (argc > 0) {
        char *endp;

        simple_strtoul(argv[0], &endp, 16);
        /* endp pointing to NULL means that argv[0] was just a
         * valid number, pass it along to the normal bootm processing
         *
         * If endp is ':' or '#' assume a FIT identifier so pass
         * along for normal processing.
         *
         * Right now we assume the first arg should never be '-'
         */
        if ((*endp != 0) && (*endp != ':') && (*endp != '#'))
            return do_bootm_subcommand(cmdtp, flag, argc, argv);
    }
        // 以上会判断是否有子命令,这里我们不管
        // 到这里,参数中的bootm参数会被去掉,
        // 也就是当'bootm 0x20008000 0x21000000 0x22000000'时
        // argc=3, argv[0]=0x20008000 , argv[1]=0x21000000, argv[2]=0x22000000
        // 当‘bootm 0x30000000’时
        // argc=1, argv[0]=0x30000000

    return do_bootm_states(cmdtp, flag, argc, argv, BOOTM_STATE_START |
        BOOTM_STATE_FINDOS | BOOTM_STATE_FINDOTHER |
        BOOTM_STATE_LOADOS |
        BOOTM_STATE_OS_PREP | BOOTM_STATE_OS_FAKE_GO |
        BOOTM_STATE_OS_GO, &images, 1);
        // 最终对调用到do_bootm_states,在do_bootm_states中执行的操作如states标识所示:
        // BOOTM_STATE_START
        // BOOTM_STATE_FINDOS 
        // BOOTM_STATE_FINDOTHER 
        // BOOTM_STATE_LOADOS 
        // BOOTM_STATE_OS_PREP 
        // BOOTM_STATE_OS_FAKE_GO 
        // BOOTM_STATE_OS_GO
}

所以bootm的核心是do_bootm_states,以全局变量bootm_headers_t images作为do_bootm_states的参数。 
下面详细说明这个函数。

四、do_bootm_states软件流程

1、数据结构说明

  • bootm_headers_t 
    bootm_headers_t用来表示bootm启动kernel的一些信息的结构体,其中包括了os/initrd/fdt images的信息。 
    bootm会根据参数以及参数指向的镜像来填充这个结构题里面的成员。 
    最终再使用这个结构体里面的信息来填充kernel启动信息并且到跳转到kernel中。 
    在uboot中使用了一个全局的bootm_headers_t images。
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 */  // Legacy-uImage的镜像头
    image_header_t  legacy_hdr_os_copy; /* header copy */ // Legacy-uImage的镜像头备份
    ulong       legacy_hdr_valid; // Legacy-uImage的镜像头是否存在的标记

#if IMAGE_ENABLE_FIT
    const char  *fit_uname_cfg; /* configuration node unit name */ // 配置节点名

    void        *fit_hdr_os;    /* os FIT image header */ // FIT-uImage中kernel镜像头
    const char  *fit_uname_os;  /* os subimage node unit name */ // FIT-uImage中kernel的节点名
    int     fit_noffset_os; /* os subimage node offset */ // FIT-uImage中kernel的节点偏移

    void        *fit_hdr_rd;    /* init ramdisk FIT image header */ // FIT-uImage中ramdisk的镜像头
    const char  *fit_uname_rd;  /* init ramdisk subimage node unit name */ // FIT-uImage中ramdisk的节点名
    int     fit_noffset_rd; /* init ramdisk subimage node offset */ // FIT-uImage中ramdisk的节点偏移

    void        *fit_hdr_fdt;   /* FDT blob FIT image header */ // FIT-uImage中FDT的镜像头
    const char  *fit_uname_fdt; /* FDT blob subimage node unit name */ // FIT-uImage中FDT的节点名
    int     fit_noffset_fdt;/* FDT blob subimage node offset */ // FIT-uImage中FDT的节点偏移
#endif

    image_info_t    os;     /* os image info */ // 操作系统信息的结构体
    ulong       ep;     /* entry point of OS */ // 操作系统的入口地址

    ulong       rd_start, rd_end;/* ramdisk start/end */ // ramdisk在内存上的起始地址和结束地址

    char        *ft_addr;   /* flat dev tree address */ // fdt在内存上的地址
    ulong       ft_len;     /* length of flat device tree */ // fdt在内存上的长度

    ulong       initrd_start; // 
    ulong       initrd_end; // 
    ulong       cmdline_start; // 
    ulong       cmdline_end; // 
    bd_t        *kbd; // 

    int     verify;     /* getenv("verify")[0] != 'n' */ // 是否需要验证
    int     state; // 状态标识,用于标识对应的bootm需要做什么操作,具体看下面2.

#ifdef CONFIG_LMB
    struct lmb  lmb;        /* for memory mgmt */
#endif

} bootm_headers_t;

2、状态说明

  • BOOTM_STATE_START 
    #define BOOTM_STATE_START (0x00000001) 
    开始执行bootm的一些准备动作。
  • BOOTM_STATE_FINDOS 
    #define BOOTM_STATE_FINDOS (0x00000002) 
    查找操作系统镜像
  • BOOTM_STATE_FINDOTHER 
    #define BOOTM_STATE_FINDOTHER (0x00000004) 
    查找操作系统镜像外的其他镜像,比如FDT\ramdisk等等
  • BOOTM_STATE_LOADOS 
    #define BOOTM_STATE_LOADOS (0x00000008) 
    加载操作系统
  • BOOTM_STATE_RAMDISK 
    #define BOOTM_STATE_RAMDISK (0x00000010) 
    操作ramdisk
  • BOOTM_STATE_FDT 
    #define BOOTM_STATE_FDT (0x00000020) 
    操作FDT
  • BOOTM_STATE_OS_CMDLINE 
    #define BOOTM_STATE_OS_CMDLINE (0x00000040) 
    操作commandline
  • BOOTM_STATE_OS_BD_T 
    #define BOOTM_STATE_OS_BD_T (0x00000080)
  • BOOTM_STATE_OS_PREP 
    #define BOOTM_STATE_OS_PREP (0x00000100) 
    跳转到操作系统的前的准备动作
  • BOOTM_STATE_OS_FAKE_GO 
    #define BOOTM_STATE_OS_FAKE_GO (0x00000200) /* ‘Almost’ run the OS */ 
    伪跳转,一般都能直接跳转到kernel中去
  • BOOTM_STATE_OS_GO 
    #define BOOTM_STATE_OS_GO (0x00000400) 
    跳转到kernel中去

3、软件流程说明

do_bootm_states根据states来判断要执行的操作。

  • 主要流程简单说明如下: 
    • bootm的准备动作 
      BOOTM_STATE_START
    • 获取kernel信息 
      BOOTM_STATE_FINDOS
    • 获取ramdisk和fdt的信息 
      BOOTM_STATE_FINDOTHER
    • 加载kernel到对应的位置上(有可能已经就在这个位置上了) 
      BOOTM_STATE_LOADOS
    • 重定向ramdisk和fdt(不一定需要) 
      BOOTM_STATE_RAMDISK、BOOTM_STATE_FDT
    • 执行跳转前的准备动作 
      BOOTM_STATE_OS_PREP
    • 设置启动参数,跳转到kernel所在的地址上 
      BOOTM_STATE_OS_GO

在这些流程中,起传递作用的是bootm_headers_t images这个数据结构,有些流程是解析镜像,往这个结构体里写数据。 
而跳转的时候,则需要使用到这个结构体里面的数据。

  • 软件代码如下 
    common/bootm.c
int do_bootm_states(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[],
            int states, bootm_headers_t *images, int boot_progress)
{
    boot_os_fn *boot_fn;
    ulong iflag = 0;
    int ret = 0, need_boot_fn;

    images->state |= states;
       // 把states放到bootm_headers_t images内部

    /*
     * Work through the states and see how far we get. We stop on
     * any error.
     */
        // 判断states是否需要BOOTM_STATE_START动作,也就是bootm的准备动作,需要的话则调用bootm_start
    if (states & BOOTM_STATE_START)
        ret = bootm_start(cmdtp, flag, argc, argv);

        // 判断states是否需要BOOTM_STATE_FINDOS动作,也就是获取kernel信息,需要的话在调用bootm_find_os
    if (!ret && (states & BOOTM_STATE_FINDOS))
        ret = bootm_find_os(cmdtp, flag, argc, argv);

    ·   // 判断states是否需要BOOTM_STATE_FINDOTHER动作,也就是获取ramdisk和fdt等其他镜像的信息,需要的话则调用bootm_find_other
    if (!ret && (states & BOOTM_STATE_FINDOTHER)) {
        ret = bootm_find_other(cmdtp, flag, argc, argv);
        argc = 0;   /* consume the args */
    }

        /* 这里要重点注意,前面的步骤都是在解析uImage镜像并填充bootm_headers_t images */
        /* 也就是说解析uImage的部分在此之前 */
        /* 而后续则是使用bootm_headers_t images 里面的内容来进行后续动作*/



    /* Load the OS */
        // 判断states是否需要BOOTM_STATE_LOADOS动作,也就是加载操作系统的动作,需要的话则调用bootm_load_os
    if (!ret && (states & BOOTM_STATE_LOADOS)) {
        ulong load_end;

        iflag = bootm_disable_interrupts();
        ret = bootm_load_os(images, &load_end, 0);
        if (ret == 0)
            lmb_reserve(&images->lmb, images->os.load,
                    (load_end - images->os.load));
        else if (ret && ret != BOOTM_ERR_OVERLAP)
            goto err;
        else if (ret == BOOTM_ERR_OVERLAP)
            ret = 0;
#if defined(CONFIG_SILENT_CONSOLE) && !defined(CONFIG_SILENT_U_BOOT_ONLY)
        if (images->os.os == IH_OS_LINUX)
            fixup_silent_linux();
#endif
    }

        // 是否需要重定向ramdinsk,do_bootm流程的话是不需要的
    /* Relocate the ramdisk */
#ifdef CONFIG_SYS_BOOT_RAMDISK_HIGH
    if (!ret && (states & BOOTM_STATE_RAMDISK)) {
        ulong rd_len = images->rd_end - images->rd_start;

        ret = boot_ramdisk_high(&images->lmb, images->rd_start,
            rd_len, &images->initrd_start, &images->initrd_end);
        if (!ret) {
            setenv_hex("initrd_start", images->initrd_start);
            setenv_hex("initrd_end", images->initrd_end);
        }
    }
#endif

        // 是否需要重定向fdt,do_bootm流程的话是不需要的
#if IMAGE_ENABLE_OF_LIBFDT && defined(CONFIG_LMB)
    if (!ret && (states & BOOTM_STATE_FDT)) {
        boot_fdt_add_mem_rsv_regions(&images->lmb, images->ft_addr);
        ret = boot_relocate_fdt(&images->lmb, &images->ft_addr,
                    &images->ft_len);
    }
#endif

    /* From now on, we need the OS boot function */
    if (ret)
        return ret;

        // 获取对应操作系统的启动函数,存放到boot_fn中
    boot_fn = bootm_os_get_boot_func(images->os.os);
    need_boot_fn = states & (BOOTM_STATE_OS_CMDLINE |
            BOOTM_STATE_OS_BD_T | BOOTM_STATE_OS_PREP |
            BOOTM_STATE_OS_FAKE_GO | BOOTM_STATE_OS_GO);
    if (boot_fn == NULL && need_boot_fn) {
        if (iflag)
            enable_interrupts();
        printf("ERROR: booting os '%s' (%d) is not supported\n",
               genimg_get_os_name(images->os.os), images->os.os);
        bootstage_error(BOOTSTAGE_ID_CHECK_BOOT_OS);
        return 1;
    }

    /* Call various other states that are not generally used */
    if (!ret && (states & BOOTM_STATE_OS_CMDLINE))
        ret = boot_fn(BOOTM_STATE_OS_CMDLINE, argc, argv, images);
    if (!ret && (states & BOOTM_STATE_OS_BD_T))
        ret = boot_fn(BOOTM_STATE_OS_BD_T, argc, argv, images);

        // 跳转到操作系统前的准备动作,会直接调用启动函数,但是标识是BOOTM_STATE_OS_PREP
    if (!ret && (states & BOOTM_STATE_OS_PREP))
        ret = boot_fn(BOOTM_STATE_OS_PREP, argc, argv, images);


    /* Check for unsupported subcommand. */
    if (ret) {
        puts("subcommand not supported\n");
        return ret;
    }

        // BOOTM_STATE_OS_GO标识,跳转到操作系统中,并且不应该再返回了
    /* Now run the OS! We hope this doesn't return */
    if (!ret && (states & BOOTM_STATE_OS_GO))
        ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_GO,
                images, boot_fn);

    /* Deal with any fallout */
err:
    if (iflag)
        enable_interrupts();

    if (ret == BOOTM_ERR_UNIMPLEMENTED)
        bootstage_error(BOOTSTAGE_ID_DECOMP_UNIMPL);
    else if (ret == BOOTM_ERR_RESET)
        do_reset(cmdtp, flag, argc, argv);

    return ret;
}

主要用到如下依次几个函数来实现:

  • bootm_start
  • bootm_find_os
  • bootm_find_other
  • bootm_load_os
  • bootm_os_get_boot_func
  • boot_fn(BOOTM_STATE_OS_PREP, argc, argv, images);
  • boot_selected_os
  • boot_selected_os(argc, argv, BOOTM_STATE_OS_GO,images, boot_fn);

4、bootm_start & bootm_find_os & bootm_find_other

主要负责解析环境变量、参数、uImage,来填充bootm_headers_t images这个数据结构。 
最终目的是实现bootm_headers_t images中的这几个成员

typedef struct bootm_headers {
    image_info_t    os;     /* os image info */
    ulong       ep;     /* entry point of OS */

    ulong       rd_start, rd_end;/* ramdisk start/end */

    char        *ft_addr;   /* flat dev tree address */
    ulong       ft_len;     /* length of flat device tree */

    ulong       initrd_start;
    ulong       initrd_end;
    ulong       cmdline_start;
    ulong       cmdline_end;
    bd_t        *kbd;
    int     verify;     /* getenv("verify")[0] != 'n' */
#ifdef CONFIG_LMB
    struct lmb  lmb;        /* for memory mgmt */
#endif
}
  • bootm_start 
    实现verify和lmb
  • bootm_find_os 
    实现os和ep。 
    也就是说,不管是Legacy-uImage还是FIT-uImage,最终解析出来要得到的都是这两个成员。 
    放在《uboot启动kernel篇(三)——解析uImage的kernel镜像》中具体说明。
  • bootm_find_other 
    实现rd_start, rd_end,ft_addr和initrd_end。 
    也就是说,不管是Legacy-uImage还是FIT-uImage,最终解析出来要得到的都是这两个成员。 
    放在《uboot启动kernel篇(四)——解析uImage的fdt》和《uboot启动kernel篇(五)——解析uImage的ramdisk》中具体说明。

5、bootm_load_os

简单说明一下,在bootm_load_os中,会对kernel镜像进行load到对应的位置上,并且如果kernel镜像是被mkimage压缩过的,那么会先经过解压之后再进行load。(这里要注意,这里的压缩和Image压缩成zImage并不是同一个,而是uboot在Image或者zImage的基础上进行的压缩!!!)

static int bootm_load_os(bootm_headers_t *images, unsigned long *load_end,
             int boot_progress)
{
    image_info_t os = images->os;
    ulong load = os.load; // kernel要加载的地址
    ulong blob_start = os.start;
    ulong blob_end = os.end;
    ulong image_start = os.image_start; // kernel实际存在的位置
    ulong image_len = os.image_len; // kernel的长度
    bool no_overlap;
    void *load_buf, *image_buf;
    int err;

    load_buf = map_sysmem(load, 0);
    image_buf = map_sysmem(os.image_start, image_len);

// 调用bootm_decomp_image,对image_buf的镜像进行解压缩,并load到load_buf上
    err = bootm_decomp_image(os.comp, load, os.image_start, os.type,
                 load_buf, image_buf, image_len,
                 CONFIG_SYS_BOOTM_LEN, load_end);

结果上述步骤之后,kernel镜像就被load到对应位置上了。

6、bootm_os_get_boot_func

bootm_os_get_boot_func用于获取到对应操作系统的启动函数,被存储到boot_fn 中。 
如下:

int do_bootm_states(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[],
            int states, bootm_headers_t *images, int boot_progress)
{
...
    boot_fn = bootm_os_get_boot_func(images->os.os);
...
}

boot_os_fn *bootm_os_get_boot_func(int os)
{
    return boot_os[os];
// 根据操作系统类型获得到对应的操作函数
}

static boot_os_fn *boot_os[] = {
...
#ifdef CONFIG_BOOTM_LINUX
    [IH_OS_LINUX] = do_bootm_linux,
#endif
}

可以看出最终启动linux的核心函数是do_bootm_linux。 
另外几个函数最终也是调用到boot_fn,对应linux也就是do_bootm_linux,所以这里不在说明了。 
下面继续说明一下do_bootm_linux的流程

五、do_bootm_linux

arch/arm/lib/bootm.c

int do_bootm_linux(int flag, int argc, char * const argv[],
           bootm_headers_t *images)
{
    /* No need for those on ARM */
    if (flag & BOOTM_STATE_OS_BD_T || flag & BOOTM_STATE_OS_CMDLINE)
        return -1;

        // 当flag为BOOTM_STATE_OS_PREP,则说明只需要做准备动作boot_prep_linux
    if (flag & BOOTM_STATE_OS_PREP) {
        boot_prep_linux(images);
        return 0;
    }

        // 当flag为BOOTM_STATE_OS_GO ,则说明只需要做跳转动作 
        if (flag & (BOOTM_STATE_OS_GO | BOOTM_STATE_OS_FAKE_GO)) {
        boot_jump_linux(images, flag);
        return 0;
    }

    boot_prep_linux(images); // 以全局变量bootm_headers_t images为参数传递给boot_prep_linux
    boot_jump_linux(images, flag);// 以全局变量bootm_headers_t images为参数传递给boot_jump_linux
    return 0;
}

boot_prep_linux用于实现跳转到linux前的准备动作。 
而boot_jump_linux用于跳转到linux中。 
都是以全局变量bootm_headers_t images为参数,这样就可以直接获取到前面步骤中得到的kernel镜像、ramdisk以及fdt的信息了。

  • boot_prep_linux 
    首先要说明一下LMB的概念。LMB是指logical memory blocks,主要是用于表示内存的保留区域,主要有fdt的区域,ramdisk的区域等等。 
    boot_prep_linux主要的目的是修正LMB,并把LMB填入到fdt中。 
    实现如下:
static void boot_prep_linux(bootm_headers_t *images)
{
    char *commandline = getenv("bootargs");

    if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len) {
#ifdef CONFIG_OF_LIBFDT
        debug("using: FDT\n");
        if (image_setup_linux(images)) {
            printf("FDT creation failed! hanging...");
            hang();
        }
#endif
    }

这里没有深入学习image_setup_linux,等后续有需要的话再进行深入。

  • boot_jump_linux 
    以arm为例: 
    arch/arm/lib/bootm.c
static void boot_jump_linux(bootm_headers_t *images, int flag)
{
    unsigned long machid = gd->bd->bi_arch_number; // 从bd中获取machine-id,machine-id在uboot启动流程的文章中有说明过了
    char *s;
    void (*kernel_entry)(int zero, int arch, uint params); // kernel入口函数,也就是kernel的入口地址,对应kernel的_start地址。
    unsigned long r2;
    int fake = (flag & BOOTM_STATE_OS_FAKE_GO); // 伪跳转,并不真正地跳转到kernel中

    kernel_entry = (void (*)(int, int, uint))images->ep; 
         // 将kernel_entry设置为images中的ep(kernel的入口地址),后面直接执行kernel_entry也就跳转到了kernel中了
        // 这里要注意这种跳转的方法

    debug("## Transferring control to Linux (at address %08lx)" \
        "...\n", (ulong) kernel_entry);
    bootstage_mark(BOOTSTAGE_ID_RUN_OS);
    announce_and_cleanup(fake);

        // 把images->ft_addr(fdt的地址)放在r2寄存器中
    if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len)
        r2 = (unsigned long)images->ft_addr;
    else
        r2 = gd->bd->bi_boot_params;

    if (!fake) {
            kernel_entry(0, machid, r2);
        // 这里通过调用kernel_entry,就跳转到了images->ep中了,也就是跳转到kernel中了,具体则是kernel的_start符号的地址。
        // 参数0则传入到r0寄存器中,参数machid传入到r1寄存器中,把images->ft_addr(fdt的地址)放在r2寄存器中
        // 满足了kernel启动的硬件要求
    }
}

到这里,经过kernel_entry之后就跳转到kernel环境中了。有兴趣可以看一下kernel的启动流程。

================================================================================ 
kernel启动流程文章 
[kernel 启动流程] 前篇——vmlinux.lds分析 
[kernel 启动流程] (第一章)概述 
[kernel 启动流程] (第二章)第一阶段之——设置SVC、关闭中断 
[kernel 启动流程] (第三章)第一阶段之——proc info的获取 
[kernel 启动流程] (第四章)第一阶段之——dtb的验证 
[kernel 启动流程] (第五章)第一阶段之——临时内核页表的创建 
[kernel 启动流程] (第六章)第一阶段之——打开MMU 
[kernel 启动流程] (第七章)第一阶段之——跳转到start_kernel

<think>好的,我现在需要详细说明U-Boot及其启动流程。首先,我应该先了解U-Boot是什么,它的主要功能和用途。U-Boot,全称是Das U-Boot,是一个开源的引导加载程序,主要用于嵌入式系统。它支持多种架构,如ARM、MIPS、PowerPC等,常用于引导Linux内核。 接下来,我需要理解U-Boot的启动流程。用户可能想知道从设备上电到加载内核的整个过程。启动流程通常分为几个阶段,每个阶段的具体任务可能有所不同,但大致可以分为以下几个步骤: 1. **ROM Bootloader**:设备上电后,首先执行的是芯片内部ROM中的引导代码。这部分代码通常是固化在硬件中的,负责初始化最基本的硬件,如时钟、内存控制器,并加载下一阶段的引导程序(如SPL)。 2. **SPL (Secondary Program Loader)**:由于内部ROM空间有限,SPL作为第阶段引导加载程序,负责进一步初始化硬件(如DRAM),并加载完整的U-Boot到内存中。SPL通常是U-Boot的一部分,经过编译后生成的小型镜像。 3. **U-Boot主程序**:SPL加载完成后,跳转到U-Boot的主程序。U-Boot会进行更全面的硬件初始化,包括串口、网卡、存储设备等,然后读取环境变量,根据配置加载内核镜像、设备树文件(DTB)和根文件系统。 4. **加载内核**:U-Boot将内核镜像、设备树和可能的initramfs加载到内存中的指定地址,然后通过bootm或bootz命令启动内核,传递必要的参数。 在这个过程中,需要考虑到不同硬件平台的差异,比如启动介质(NOR Flash、eMMC、SD卡等)、内存布局、环境变量的存储位置(如EEPROM、Flash中的特定分区)等。 另外,U-Boot还支持交互式命令,用户可以在启动过程中打断自动流程,进入命令行界面,手动执行命令进行调试或配置。环境变量在U-Boot中非常重要,它们决定了启动参数和流程,比如bootcmd定义了自动执行的命令序列。 可能还需要解释U-Boot的编译和配置过程,比如通过make menuconfig配置目标板,选择所需的驱动和功能,然后编译生成u-boot.bin等文件。此外,不同开发板的配置文件位于configs目录下,如mx6ullevk_defconfig对应特定的开发板。 在启动流程中,设备树(Device Tree)的作用也很重要,它描述了硬件配置,使得内核无需硬编码硬件信息。U-Boot负责将设备树进制文件(DTB)加载到内存,并传递给内核。 还要注意U-Boot的启动阶段可能涉及多个文件,比如SPL、U-Boot proper,以及如何将它们烧写到存储设备的正确位置。例如,在SD卡启动时,SPL可能需要存放在特定的偏移量位置,以便ROM Bootloader能够找到并加载它。 可能存在的疑问点包括:SPL和U-Boot的关系,如何配置环境变量,如何处理不同的启动介质,U-Boot如何与内核交互,以及如何调试启动问题。需要确保这些点在回答中都有所覆盖,并且结构清晰,步骤明确。 最后,需要验证这些信息是否正确,比如确认U-Boot的启动阶段划分是否准确,SPL的作用是否描述正确,以及加载内核的具体命令流程是否符合实际。如果有不确定的地方,可能需要查阅U-Boot的官方文档或源码进行确认,确保回答的准确性。</think>--- ### **U-Boot详解与启动流程分析** --- #### **1. U-Boot概述** **定义**: U-Boot(Universal Boot Loader)是嵌入式系统领域使用最广泛的开源引导加载程序,支持**ARM、MIPS、PowerPC、RISC-V**等30+处理器架构,兼容**NOR Flash、eMMC、NAND、SD卡**等多种存储介质。 **核心功能**: - 硬件初始化(时钟/DDR/外设) - 多阶段引导加载 - 固件升级(USB DFU/TFTP) - 环境变量管理 - 操作系统映像加载(Linux/FreeRTOS/VxWorks) - 安全启动(Verified Boot) --- #### **2. 典型启动流程** ```mermaid sequenceDiagram participant ROM Code participant SPL participant U-Boot participant Kernel ROM Code->>SPL: 加载SPL到SRAM SPL->>U-Boot: 初始化DRAM,加载U-Boot到内存 U-Boot->>U-Boot: 外设初始化(网卡/USB/存储) U-Boot->>Kernel: 加载内核镜像、设备树、initramfs Kernel->>Kernel: 解压并启动操作系统 ``` --- #### **3. 关键阶段详解** **3.1 ROM Bootloader** - **硬件行为**:芯片上电后自动执行固化在ROM中的代码 - **主要任务**: - 初始化基础时钟(CPU/总线) - 检测启动介质(通过GPIO或OTP配置) - 加载SPL到内部SRAM(典型大小:64-256KB) **示例代码(i.MX6ULL)**: ```c /* arch/arm/mach-imx/imx6ull/lowlevel_init.S */ bl setup_pll // 配置PLL时钟 bl setup_dcdr // DDR控制器初始化 ldr r0, =SPL_LOAD_ADDR bl load_image // 从SD卡加载SPL ``` --- **3.2 SPL阶段** - **进制文件**:`u-boot-spl.bin` - **内存限制**:通常在SRAM中运行(无DRAM支持) - **核心任务**: 1. 初始化DRAM控制器 2. 设置内存映射表 3. 加载完整U-Boot到DRAM 4. 验证U-Boot镜像签名(安全启动场景) **关键配置选项**: ```makefile # configs/mx6ullevk_defconfig CONFIG_SPL=y CONFIG_SPL_DM=y CONFIG_SPL_LOAD_FIT=y ``` --- **3.3 U-Boot主程序** - **入口函数**:`lib/init/board_init.c`中的`board_init_f()` - **启动阶段划分**: ```c board_init_f() // 初始化全局数据结构(gd_t) ↓ relocate_code() // 重定位到DRAM高端地址 ↓ board_init_r() // 完整外设初始化 ``` **硬件初始化顺序**: 1. 串口调试输出(`DEBUG_PORT`) 2. 内存控制器配置 3. 存储设备初始化(MMC/SATA) 4. 网络接口(PHY初始化) 5. 环境变量加载(`env_relocate()`) --- **3.4 环境变量管理** - **存储位置**:Flash专用分区(如eMMC的ENV分区) - **关键变量**: ```bash bootcmd=mmc dev 0; ext4load mmc 0:1 0x80800000 zImage; \ ext4load mmc 0:1 0x83000000 imx6ull.dtb; \ bootz 0x80800000 - 0x83000000 bootargs=console=ttymxc0,115200 root=/dev/mmcblk0p2 ro ``` **操作命令**: ```bash # 查看环境变量 printenv # 修改并保存 setenv ipaddr 192.168.1.100 saveenv ``` --- #### **4. 内核加载过程** **典型bootm命令流程**: ```c do_bootm()bootm_start() // 解析镜像头信息 ↓ bootm_load_os() // 加载内核到指定地址 ↓ boot_fn = boot_os[os] // 选择启动方法(Linux/NetBSD等) ↓ do_bootm_linux() // 设置ATAGs/FDT,跳转到内核入口 ``` **ARM平台内存布局示例**: | 地址范围 | 用途 | |------------------|-----------------------| | 0x80000000 | 内核加载地址 | | 0x81000000 | 设备树地址 | | 0x82000000 | initramfs地址 | --- #### **5. 安全启动实现** **数字签名验证流程**: ```mermaid graph LR A[SPL] --> B{验证U-Boot签名} B -->|有效| C[加载U-Boot] B -->|无效| D[进入恢复模式] C --> E[U-Boot验证内核签名] E -->|有效| F[启动内核] E -->|无效| G[终止启动] ``` **关键配置**: ```makefile CONFIG_SPL_FIT_SIGNATURE=y # 启用FIT镜像签名 CONFIG_SPL_CRYPTO_SUPPORT=y # 加密算法支持 CONFIG_SPL_RSA_VERIFY=y # RSA验证 ``` --- #### **6. 调试与优化** **6.1 常用调试手段**: - 串口日志输出(`CONFIG_DEBUG_UART`) - LED指示灯控制(`led_set_state()`) - 内存检测(`mtest`命令) - 启动暂停(`CONFIG_AUTOBOOT_KEYED`) **6.2 性能优化技巧**: ```c /* 加速存储设备访问 */ #define CONFIG_SYS_MMC_ENV_DEV 0 #define CONFIG_SYS_MMC_MAX_BLK_COUNT 1024 // 增大块传输数量 /* 压缩镜像尺寸 */ #define CONFIG_LZMA // 使用LZMA压缩算法 ``` --- #### **7. 典型移植步骤** 1. **创建板级目录**: ```bash board/<vendor>/<board>/ ├── Kconfig ├── Makefile └── <board>.c ``` 2. **配置设备树**: ```dts /dts-v1/; / { model = "Custom Board"; memory@80000000 { reg = <0x80000000 0x20000000>; }; }; ``` 3. **实现关键驱动**: - 串口(`drivers/serial/serial_xxx.c`) - 存储设备(`drivers/mmc/mmc_xxx.c`) - GPIO控制 --- #### **8. 启动时间分析** **典型时间分布(ARM Cortex-A9@1GHz)**: | 阶段 | 耗时(ms) | |----------------|-----------| | ROM Code | 12 | | SPL执行 | 58 | | U-Boot初始化 | 320 | | 内核加载 | 92 | | **总计** | **482** | **优化方向**: - 启用SPL框架(减少初始化步骤) - 预计算CRC32(替代运行时计算) - 并行初始化外设 --- 通过这种层次化的启动架构,U-Boot实现了从硬件初始化到操作系统引导的完整控制流程,其模块化设计和可移植性使其成为嵌入式系统开发的核心组件。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值