uboot启动流程2 - 命令行处理及跳到Linux

uboot在执行完所有初始化程序之后,调用run_main_loop进入主循环,调用 main_loop()。通过主循环进入了命令行模式。

一、main_loop()函数详解,路径:common\main.c

void main_loop(void)
{
    const char *s;

    bootstage_mark_name(BOOTSTAGE_ID_MAIN_LOOP, "main_loop");        //打印启动进度

    if (IS_ENABLED(CONFIG_VERSION_VARIABLE))
        env_set("ver", version_string);    //设置换将变量 ver 的值为 version_string,也就是设置版本号环境变量

    cli_init();

    if (IS_ENABLED(CONFIG_USE_PREBOOT))
        run_preboot_environment_command();    //获取环境变量 perboot 的内容, perboot是一些预启动命令,一般不使用

    if (IS_ENABLED(CONFIG_UPDATE_TFTP))
        update_tftp(0UL, NULL, NULL);

    //此函数会读取环境变量 bootdelay 和 bootcmd 的内容,然后将 bootdelay 的值赋值给全局变量 stored_bootdelay,
    //返回值为环境变量 bootcmd 的值
    s = bootdelay_process();    
    if (cli_process_fdt(&s))
        cli_secure_boot_cmd(s);

    autoboot_command(s);    //此函数就是检查倒计时是否结束?倒计时结束之前有没有被打断?

    cli_loop();             //uboot 的命令行处理函数,我们在 uboot 中输入各种命令,进行各种操作就是有 cli_loop 来处理的
    panic("No CLI available");
}

二、cli_loop()函数详解,路径:common/cli.c
调用过程:

cli_loop()
	->parse_file_outer()
		->parse_stream_outer()
			->run_list()
				->run_list_real()
					->run_pipe_real()
						->cmd_process()        //最终通过函数 cmd_process 来处理命令
void cli_loop(void)
{
    bootstage_mark(BOOTSTAGE_ID_ENTER_CLI_LOOP);
    #ifdef CONFIG_HUSH_PARSER
    parse_file_outer();
    /* This point is never reached */
    for (;;);
#elif defined(CONFIG_CMDLINE)
        cli_simple_loop();
#else
        printf("## U-Boot command line is disabled. Please enable CONFIG_CMDLINE\n");
#endif /*CONFIG_HUSH_PARSER*/
}

三、 uboot命令的定义和处理
uboot获取bootcmd中的命令,命令中调用了 bootm,启动内核。
1)命令的定义
1、自定义命令

U_BOOT_CMD(
        bootm,  CONFIG_SYS_MAXARGS,     1,      do_bootm,                                                                                                                                                    
        "boot application image from memory", bootm_help_text
);

2、U_BOOT_CMD 宏定义在 include/command.h 中
功能:定义一个cmd_tbl_t类型的变量,并在程序编译时链接到指定的段中。

/*
 * Command Flags:
 */
#define CMD_FLAG_REPEAT         0x0001  /* repeat last command          */
#define CMD_FLAG_BOOTD          0x0002  /* command is from bootd        */
#define CMD_FLAG_ENV            0x0004  /* command is from the environment */
 
#ifdef CONFIG_AUTO_COMPLETE
# define _CMD_COMPLETE(x) x,
#else
# define _CMD_COMPLETE(x)
#endif
#ifdef CONFIG_SYS_LONGHELP
# define _CMD_HELP(x) x,
#else
# define _CMD_HELP(x)
#endif
 
#define U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd,          \
                                _usage, _help, _comp)                   \
                { #_name, _maxargs, _rep, _cmd, _usage,                 \
                        _CMD_HELP(_help) _CMD_COMPLETE(_comp) }
 
#define U_BOOT_CMD_MKENT(_name, _maxargs, _rep, _cmd, _usage, _help)    \
        U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd,          \
                                        _usage, _help, NULL)
 
#define U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, _comp) \
        ll_entry_declare(cmd_tbl_t, _name, cmd) =                       \                                                                                                                                    
                U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd,  \
                                                _usage, _help, _comp);
    
#define U_BOOT_CMD(_name, _maxargs, _rep, _cmd, _usage, _help)          \
        U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, NULL)

其中最关键的函数ll_entry_declare 数据结构体类型:cmd_tbl_t

#define ll_entry_declare(_type, _name, _list)                           \                                                                                                                                    
        _type _u_boot_list_2_##_list##_2_##_name __aligned(4)           \
                        __attribute__((unused,                          \
                        section(".u_boot_list_2_"#_list"_2_"#_name)))

文件:include/command.h

struct cmd_tbl_s {
        char            *name;          /* Command Name                 */
        int             maxargs;        /* maximum number of arguments  */
        int             repeatable;     /* autorepeat allowed?          */
                                        /* Implementation function      */
        int             (*cmd)(struct cmd_tbl_s *, int, int, char * const []);
        char            *usage;         /* Usage message        (short) */
#ifdef  CONFIG_SYS_LONGHELP
        char            *help;          /* Help  message        (long)  */
#endif
#ifdef CONFIG_AUTO_COMPLETE
        /* do auto completion on the arguments */
        int             (*complete)(int argc, char * const argv[], char last_char, int maxv, char *cmdv[]);
#endif
};
 
typedef struct cmd_tbl_s        cmd_tbl_t;   

展开后的变量定义如下:

ll_entry_declare(cmd_tbl_t, _name, cmd)
 以bootm为例
 _type=cmd_tbl_t
 _name=bootm
 _list=cmd
 这里最终会转化为如下数据结构
 
 cmd_tbl_t _u_boot_list_2_cmd_2_bootm=
 {
 _name=bootm,
 _maxargs=CONFIG_SYS_MAXARGS,
 _rep=1,
 _cmd=do_bootm,
 _usage="boot application image from memory",
 _help=bootm_help_text,
 _comp=NULL}

u-boot.lds 链接脚本中有一个名为“.u_boot_list”的段,所有.u_boot_list 开头的段都存放到.u_boot.list 中

总结一下:uboot 中使用 U_BOOT_CMD 来定义一个命令,最终的目的就是为了定义一个cmd_tbl_t 类型的变量,并初始化这个变量的各个成员。 uboot 中的每个命令都存储在.u_boot_list段中,每个命令都有一个名为 do_xxx(xxx 为具体的命令名)的函数,这个 do_xxx 函数就是具体的命令处理函数。

2)命令的处理
前面提到 cmd_process 函数用来处理命令,路径:common\command.c

enum command_ret_t cmd_process(int flag, int argc, char *const argv[],
                   int *repeatable, ulong *ticks)
{
    enum command_ret_t rc = CMD_RET_SUCCESS;
    struct cmd_tbl *cmdtp;

    /* Look up command in command table */
    cmdtp = find_cmd(argv[0]);                    //1.找到指定的命令
    if (cmdtp == NULL) {
        printf("Unknown command '%s' - try 'help'\n", argv[0]);
        return 1;
    }

    /* found - check max args */
    if (argc > cmdtp->maxargs)
        rc = CMD_RET_USAGE;

#if defined(CONFIG_CMD_BOOTD)
    /* avoid "bootd" recursion */
    else if (cmdtp->cmd == do_bootd) {
        if (flag & CMD_FLAG_BOOTD) {
            puts("'bootd' recursion detected\n");
            rc = CMD_RET_FAILURE;
        } else {
            flag |= CMD_FLAG_BOOTD;
        }
    }
#endif

    /* If OK so far, then do the command */
    if (!rc) {
        int newrep;

        if (ticks)
            *ticks = get_timer(0);
        rc = cmd_call(cmdtp, flag, argc, argv, &newrep);    //2.执行具体的命令
        if (ticks)
            *ticks = get_timer(*ticks);
        *repeatable &= newrep;
    }
    if (rc == CMD_RET_USAGE)
        rc = cmd_usage(cmdtp);
    return rc;
}

find_cmd()函数用来找到命令,路径:common\command.c

struct cmd_tbl *find_cmd(const char *cmd)
{
    struct cmd_tbl *start = ll_entry_start(struct cmd_tbl, cmd);//得到数组 cmd_tbl_t 命令表的起始地址
    const int len = ll_entry_count(struct cmd_tbl, cmd);        //得到命令表的长度
    return find_cmd_tbl(cmd, start, len);    //在命令表中找到所需的命令(参数 cmd 与命令表中每个成员的 name 字段都对比)
}

cmd_call()函数用来执行命令,路径:common\command.c

static int cmd_call(struct cmd_tbl *cmdtp, int flag, int argc,
            char *const argv[], int *repeatable)
{
    int result;

    result = cmdtp->cmd_rep(cmdtp, flag, argc, argv, repeatable);
    if (result)
        debug("Command failed, result=%d\n", result);
    return result;
}

四、bootm 启动 Linux 内核过程
1、bootm命令最终调用 do_bootm()函数,路径:cmd\bootm.c

int do_bootm(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
{
#ifdef CONFIG_NEEDS_MANUAL_RELOC
    static int relocated = 0;

    if (!relocated) {
        int i;

        /* relocate names of sub-command table 重定位子命令表 */
        for (i = 0; i < ARRAY_SIZE(cmd_bootm_sub); i++)
            cmd_bootm_sub[i].name += gd->reloc_off;

        relocated = 1;
    }
#endif

    /* 判断是否有子命令,这里我们不管 */
    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 0x20008000 0x21000000 0x22000000'时
/* argc=3, argv[0]=0x20008000 , argv[1]=0x21000000, argv[2]=0x22000000 */
    return do_bootm_states(cmdtp, flag, argc, argv, BOOTM_STATE_START |
        BOOTM_STATE_FINDOS | BOOTM_STATE_FINDOTHER |
        BOOTM_STATE_LOADOS |
#ifdef CONFIG_SYS_BOOT_RAMDISK_HIGH
        BOOTM_STATE_RAMDISK |
#endif
#if defined(CONFIG_PPC) || defined(CONFIG_MIPS)
        BOOTM_STATE_OS_CMDLINE |
#endif
        BOOTM_STATE_OS_PREP | BOOTM_STATE_OS_FAKE_GO |
        BOOTM_STATE_OS_GO, &images, 1);  
}

bootm的核心是do_bootm_states,以全局变量bootm_headers_t images作为do_bootm_states的参数。

2、do_bootm_states,路径:common\bootm.c

int do_bootm_states(struct cmd_tbl *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;

    /*
     * Work through the states and see how far we get. We stop on
     * any error.
     */
    if (states & BOOTM_STATE_START)
        ret = bootm_start(cmdtp, flag, argc, argv);    //初始化image全局变量,设置bootm的内存

    if (!ret && (states & BOOTM_STATE_FINDOS))
        ret = bootm_find_os(cmdtp, flag, argc, argv);    //确定image的内存地址

    if (!ret && (states & BOOTM_STATE_FINDOTHER))
        ret = bootm_find_other(cmdtp, flag, argc, argv);    //确定ramdisk、fdt(设备树)的内存地址

    /* Load the OS */
    if (!ret && (states & BOOTM_STATE_LOADOS)) {
        iflag = bootm_disable_interrupts();
        ret = bootm_load_os(images, 0);        //加载内核image
        if (ret && ret != BOOTM_ERR_OVERLAP)
            goto err;
        else if (ret == BOOTM_ERR_OVERLAP)
            ret = 0;
    }

    /* 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,    //重定位ramdisk
            rd_len, &images->initrd_start, &images->initrd_end);
        if (!ret) {
            env_set_hex("initrd_start", images->initrd_start);
            env_set_hex("initrd_end", images->initrd_end);
        }
    }
#endif
#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);//为设备数中的memory reserve预留内存,重定位fdt
        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;
//通过函数 bootm_os_get_boot_func 来查找系统启动函数,参数 images->os.os 就是系统类型,根据这个系统类型来选择对应的启动函数
    boot_fn = bootm_os_get_boot_func(images->os.os);    //函数返回值就是找到的系统启动函数为 do_bootm_linux
    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);
    if (!ret && (states & BOOTM_STATE_OS_PREP)) {
#if defined(CONFIG_SILENT_CONSOLE) && !defined(CONFIG_SILENT_U_BOOT_ONLY)
        if (images->os.os == IH_OS_LINUX)
            fixup_silent_linux();
#endif
        ret = boot_fn(BOOTM_STATE_OS_PREP, argc, argv, images);    //设置dtb或者bootargs
    }

#ifdef CONFIG_TRACE        //使能 TRACE功能 才会进此分支
    /* Pretend to run the OS, then run a user command */
    if (!ret && (states & BOOTM_STATE_OS_FAKE_GO)) {
        char *cmd_list = env_get("fakegocmd");

        ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_FAKE_GO,    
                images, boot_fn);
        if (!ret && cmd_list)
            ret = run_command_list(cmd_list, -1, flag);
    }
#endif

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

    /* 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,    //禁止中断,关闭cache,启动 Linux 内核
                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;
}

do_bootm_states函数流程

do_bootm_states
    >bootm_start(cmdtp, flag, argc, argv)//填充image中的verify和lmb
    >bootm_find_os(cmdtp, flag, argc, argv)//填充image中的os和ep,不管是Legacy-uImage还是FIT-uImage,最终解析出来要得到的都是这两个成员
    >bootm_find_other(cmdtp, flag, argc, argv)//实现rd_start, rd_end,ft_addr和initrd_end,不管是Legacy-uImage还是FIT-uImage,最终解析出来要得到的都是这两个成员
 
    >ret = bootm_load_os(images, &load_end, 0);
 
    >bootm_os_get_boot_func()//用于获取到对应操作系统的启动函数,被存储到boot_fn 中
    >boot_fn(BOOTM_STATE_OS_PREP, argc, argv, images);//调用函数 do_bootm_linux->boot_prep_linux 处理环境变量bootargs,传递给 Linux kernel 的参数
 
    >boot_selected_os(argc, argv, BOOTM_STATE_OS_GO,images, boot_fn);//启动 Linux 内核

下面对着几个函数的功能做一个简要的说明:
(1)bootm_start & bootm_find_os & bootm_find_other
主要负责解析环境变量、参数、uImage,来填充bootm_headers_t images这个数据结构。

typedef struct bootm_headers {
    image_info_t os; /* os image info ,            image 镜像信息*/
    ulong ep; /* entry point of OS ,                image入口地址 */
 
    ulong rd_start, rd_end;/* ramdisk start/end ,    ramdisk 开始和结束位置 */
 
    char *ft_addr; /* flat dev tree address ,        设备树地址 */
    ulong ft_len; /* length of flat device tree ,    设备树长度 */
    
    ulong initrd_start;                            //initrd 开始位置
    ulong initrd_end;                            //initrd 结束位置
    ulong cmdline_start;                        //cmdline 开始位置
    ulong cmdline_end;                        //cmdline 结束位置
    bd_t *kbd;
    int verify; /* getenv("verify")[0] != 'n' */
    #ifdef CONFIG_LMB
    struct lmb lmb; /* for memory mgmt ,       内存管理相关,不深入研究 */
    #endif
}

(2)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到对应位置上了。
(3)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。
(4)boot_selected_os及其它
另外几个函数最终也是调用到boot_fn,对应linux也就是do_bootm_linux,所以这里不在说明了。

3、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前的准备动作,如传递 “bootargs” 给linux;boot_jump_linux用于跳转到linux中。都是以全局变量bootm_headers_t images为参数,这样就可以直接获取到前面步骤中得到的kernel镜像、ramdisk以及fdt的信息了。

1)boot_prep_linux函数
首先要说明一下LMB的概念。LMB是指logical memory blocks,主要是用于表示内存的保留区域,主要有fdt的区域,ramdisk的区域等等。
boot_prep_linux主要的目的是修正LMB,并把LMB填入到fdt中,传递"bootargs" 给linux。
实现如下:

static void boot_prep_linux(bootm_headers_t *images)
{
    char *commandline = getenv("bootargs");    //从环境变量中获取 bootargs 的值
 
    if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len) {        //设备树分支,就不需要struct tag 了
#ifdef CONFIG_OF_LIBFDT
    debug("using: FDT\n");
    if (image_setup_linux(images)) {
        printf("FDT creation failed! hanging...");
        hang();
    }
#endif
    } else if (BOOTM_ENABLE_TAGS) {                        //传统分支,通过 struct tag 数据结构向内核传递参数
        debug("using: ATAGS\n");
        setup_start_tag(gd->bd);
        if (BOOTM_ENABLE_SERIAL_TAG)
            setup_serial_tag(&params);
        if (BOOTM_ENABLE_CMDLINE_TAG)
            setup_commandline_tag(gd->bd, commandline);
        if (BOOTM_ENABLE_REVISION_TAG)
            setup_revision_tag(&params);
        if (BOOTM_ENABLE_MEMORY_TAGS)
            setup_memory_tags(gd->bd);
        if (BOOTM_ENABLE_INITRD_TAG) {
            /*
             * In boot_ramdisk_high(), it may relocate ramdisk to
             * a specified location. And set images->initrd_start &
             * images->initrd_end to relocated ramdisk's start/end
             * addresses. So use them instead of images->rd_start &
             * images->rd_end when possible.
             */
            if (images->initrd_start && images->initrd_end) {
                setup_initrd_tag(gd->bd, images->initrd_start,
                         images->initrd_end);
            } else if (images->rd_start && images->rd_end) {
                setup_initrd_tag(gd->bd, images->rd_start,
                         images->rd_end);
            }
        }
        setup_board_tags(&params);
        setup_end_tag(gd->bd);
    } else {
        printf("FDT and ATAGS support not compiled in - hanging\n");
        hang();
    }

    board_prep_linux(images);
}

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

2)boot_jump_linux函数,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,
    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);    //打印一些信息并做一些清理工作,如打印 "Starting kernel ..."
 
    // 把images->ft_addr(fdt的地址)放在r2寄存器中
    if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len)
        r2 = (unsigned long)images->ft_addr;    //使用设备树时,r2存放设备树的起始地址
    else
        r2 = gd->bd->bi_boot_params;            //不使用设备树时,r2存放uboot传递给linux的bootargs
 
    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。

你可能感兴趣的:(linux)