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(¶ms);
if (BOOTM_ENABLE_CMDLINE_TAG)
setup_commandline_tag(gd->bd, commandline);
if (BOOTM_ENABLE_REVISION_TAG)
setup_revision_tag(¶ms);
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(¶ms);
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。