| u-boot版本 | 备注 |
|---|---|
| 4.1.15 | * |
通过一个宏定义U_BOOT_CMD进行命令与命令执行函数进行关联,从而使得在u-boot命令行中,通过输入bootz命令来启动操作系统内核。
对于U_BOOT_CMD这个命令注册宏替换操作有点复杂,但是通过层层宏替换后,可以明确知道该宏替换完成后,本质是:定义一个cmd_tbl_s结构体类型的变量并初始化值,然后放在.u_boot_list段中。如下图所示:
定义在文件:./include/command.h中
通过前面分析,bootz实际的执行函数为do_bootz()。该函数定义在文件./cmd/bootm.c文件中,如下所示:
int do_bootz(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
int ret;
argc--; argv++;
if (bootz_start(cmdtp, flag, argc, argv, &images))
return 1;
bootm_disable_interrupts();
images.os.os = IH_OS_LINUX;
ret = do_bootm_states(cmdtp, flag, argc, argv,
BOOTM_STATE_OS_PREP | BOOTM_STATE_OS_FAKE_GO |
BOOTM_STATE_OS_GO,
&images, 1);
return ret;
}
三、bootz命令是如何启动操作系统内核的?
在u-boot中用于操作系统启动有一个重要的全局变量,那就是images,该变量类型声明在文件./include/image.h文件中,变量定义在./cmd/bootm.c文件中。如下图所示:
【 images变量的定义】
【images类型的声明】
typedef struct image_header {
__be32 ih_magic;
__be32 ih_hcrc;
__be32 ih_time;
__be32 ih_size;
__be32 ih_load;
__be32 ih_ep;
__be32 ih_dcrc;
uint8_t ih_os;
uint8_t ih_arch;
uint8_t ih_type;
uint8_t ih_comp;
uint8_t ih_name[IH_NMLEN];
} image_header_t;
typedef struct image_info {
ulong start, end;
ulong image_start, image_len;
ulong load;
uint8_t comp, type, os;
uint8_t arch;
} image_info_t;
typedef struct bootm_headers {
image_header_t *legacy_hdr_os;
image_header_t legacy_hdr_os_copy;
ulong legacy_hdr_valid;
#if defined(CONFIG_FIT)
const char *fit_uname_cfg;
void *fit_hdr_os;
const char *fit_uname_os;
int fit_noffset_os;
void *fit_hdr_rd;
const char *fit_uname_rd;
int fit_noffset_rd;
void *fit_hdr_fdt;
const char *fit_uname_fdt;
int fit_noffset_fdt;
void *fit_hdr_setup;
const char *fit_uname_setup;
int fit_noffset_setup;
#endif
#ifndef USE_HOSTCC
image_info_t os;
ulong ep;
ulong rd_start, rd_end;
char *ft_addr;
ulong ft_len;
ulong initrd_start;
ulong initrd_end;
ulong cmdline_start;
ulong cmdline_end;
bd_t *kbd;
#endif
int verify;
#define BOOTM_STATE_START (0x00000001)
#define BOOTM_STATE_FINDOS (0x00000002)
#define BOOTM_STATE_FINDOTHER (0x00000004)
#define BOOTM_STATE_LOADOS (0x00000008)
#define BOOTM_STATE_RAMDISK (0x00000010)
#define BOOTM_STATE_FDT (0x00000020)
#define BOOTM_STATE_OS_CMDLINE (0x00000040)
#define BOOTM_STATE_OS_BD_T (0x00000080)
#define BOOTM_STATE_OS_PREP (0x00000100)
#define BOOTM_STATE_OS_FAKE_GO (0x00000200)
#define BOOTM_STATE_OS_GO (0x00000400)
int state;
#ifdef CONFIG_LMB
struct lmb lmb;
#endif
} bootm_headers_t;
//声明一个在外部定义的bootm_headers_t类型的变量images
extern bootm_headers_t images;
bootz命令实则调用do_bootz函数进行操作,以下为do_bootz函数的定义:
int do_bootz(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
int ret;
argc--; argv++;
//1、bootz命令执行开始阶段的处理
if (bootz_start(cmdtp, flag, argc, argv, &images))
return 1;
//2、在BOOTM_STATE_LOADOS 状态下需要禁用中断
bootm_disable_interrupts();
//3、设置系统镜像为Linux
images.os.os = IH_OS_LINUX;
//4、调用do_bootm_states来执行不同的boot阶段,其中有:BOOTM_STATE_OS_PREP 、 BOOTM_STATE_OS_FAKE_GO 和 BOOTM_STATE_OS_GO三个阶段
ret = do_bootm_states(cmdtp, flag, argc, argv,
BOOTM_STATE_OS_PREP | BOOTM_STATE_OS_FAKE_GO |
BOOTM_STATE_OS_GO,
&images, 1);
return ret;
}
在do_bootz命令执行操作函数中,将执行4个连续操作:
(1)bootz命令执行开始阶段的处理。 【bootz_start】
(2)在BOOTM_STATE_LOADOS 状态下禁用中断。
(3)设置系统镜像为Linux。
(4)调用do_bootm_states来执行不同的boot阶段,其中有:BOOTM_STATE_OS_PREP 、 BOOTM_STATE_OS_FAKE_GO 和 BOOTM_STATE_OS_GO三个阶段。【do_bootm_states】
【bootz_start函数定义】
static int bootz_start(cmd_tbl_t *cmdtp, int flag, int argc,
char * const argv[], bootm_headers_t *images)
{
int ret;
ulong zi_start, zi_end;
//1、执行bootm的BOOTM_STATE_START状态下的操作
ret = do_bootm_states(cmdtp, flag, argc, argv, BOOTM_STATE_START,
images, 1);
//2、设置Linux内核zImage的入口点
if (!argc) {
images->ep = load_addr;
debug("* kernel: default image load address = 0x%08lxn",
load_addr);
} else {
images->ep = simple_strtoul(argv[0], NULL, 16);
debug("* kernel: cmdline image address = 0x%08lxn",
images->ep);
}
//3、【启动函数】bootz_setup函数是u-boot为特定平台预留的启动函数,不同的平台下需要自己实现
ret = bootz_setup(images->ep, &zi_start, &zi_end);
if (ret != 0)
return 1;
//4、预留逻辑内存Blocks
lmb_reserve(&images->lmb, images->ep, zi_end - zi_start);
//5、查找和定位不同的ramdisk和设备树(dtb)文件
if (bootm_find_images(flag, argc, argv))
return 1;
#ifdef CONFIG_SECURE_BOOT
extern uint32_t authenticate_image(
uint32_t ddr_start, uint32_t image_size);
if (authenticate_image(images->ep, zi_end - zi_start) == 0) {
printf("Authenticate zImage Fail, Please checkn");
return 1;
}
#endif
return 0;
}
boot_start()函数需要进行五个连续操作:
(1)执行bootm的BOOTM_STATE_START状态下的操作。
(2)设置Linux内核zImage的入口点。
(3)【启动参数设置函数】bootz_setup函数是u-boot为特定平台预留的启动参数设置函数,不同的平台下需要不同实现。
(4)预留逻辑内存Blocks。
(5)查找和定位不同的ramdisk和设备树(dtb)文件。
boot_setup函数定义
struct zimage_header {
uint32_t code[9];
uint32_t zi_magic;
uint32_t zi_start;
uint32_t zi_end;
};
#define LINUX_ARM_ZIMAGE_MAGIC 0x016f2818 //为ARM Linux系统魔术数
int bootz_setup(ulong image, ulong *start, ulong *end)
{
struct zimage_header *zi;
zi = (struct zimage_header *)map_sysmem(image, 0);
if (zi->zi_magic != LINUX_ARM_ZIMAGE_MAGIC) {
puts("Bad Linux ARM zImage magic!n");
return 1;
}
*start = zi->zi_start;
*end = zi->zi_end;
printf("Kernel image @ %#08lx [ %#08lx - %#08lx ]n", image, *start,
*end);
return 0;
}
【重磅角色】do_bootm_states函数启动Linux内核过程。
在bootz_startup函数和do_bootz函数中都使用到了do_bootm_states函数,说明该函数非常重要。该函数的操作本质是:根据u-boot的不同状态来执行不同的操作。其中u-boot中定义了以下一些阶段:
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;
if (states & BOOTM_STATE_START)
ret = bootm_start(cmdtp, flag, argc, argv);
if (!ret && (states & BOOTM_STATE_FINDOS))
ret = bootm_find_os(cmdtp, flag, argc, argv);
if (!ret && (states & BOOTM_STATE_FINDOTHER)) {
ret = bootm_find_other(cmdtp, flag, argc, argv);
argc = 0;
}
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
}
#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
#if defined(CONFIG_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
if (ret)
return ret;
//查找操作系统启动函数,该函数非常重要,在linux下,通过该函数查找到的启动函数操作实则是boot_fn = do_bootm_linux
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 supportedn",
genimg_get_os_name(images->os.os), images->os.os);
bootstage_error(BOOTSTAGE_ID_CHECK_BOOT_OS);
return 1;
}
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))
ret = boot_fn(BOOTM_STATE_OS_PREP, argc, argv, images);
#ifdef CONFIG_TRACE
if (!ret && (states & BOOTM_STATE_OS_FAKE_GO)) {
char *cmd_list = getenv("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
if (ret) {
puts("subcommand not supportedn");
return ret;
}
if (!ret && (states & BOOTM_STATE_OS_GO))
ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_GO,
images, boot_fn); //启动已经选择了的操作系统
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_state函数主要执行三个连续步骤:
(1)根据u-boot状态执行不同状态下的操作。
(2)查找对应操作系统的启动函数。【bootm_os_get_boot_func】
(3)启动已经选择好的操作系统。【boot_selected_os】
bootm_os_get_boot_fun函数定义
boot_os_fn *bootm_os_get_boot_func(int os)
{
#ifdef CONFIG_NEEDS_MANUAL_RELOC
static bool relocated;
if (!relocated) {
int i;
for (i = 0; i < ARRAY_SIZE(boot_os); i++)
if (boot_os[i] != NULL)
boot_os[i] += gd->reloc_off;
relocated = true;
}
#endif
return boot_os[os];
}
bootm_os_get_boot_fun函数实则返回的是对应操作系统启动函数的函数指针,通过宏定义来选择操作系统。
static boot_os_fn *boot_os[] = {
[IH_OS_U_BOOT] = do_bootm_standalone,
#ifdef CONFIG_BOOTM_LINUX
[IH_OS_LINUX] = do_bootm_linux,
#endif
#ifdef CONFIG_BOOTM_NETBSD
[IH_OS_NETBSD] = do_bootm_netbsd,
#endif
#ifdef CONFIG_LYNXKDI
[IH_OS_LYNXOS] = do_bootm_lynxkdi,
#endif
#ifdef CONFIG_BOOTM_RTEMS
[IH_OS_RTEMS] = do_bootm_rtems,
#endif
#if defined(CONFIG_BOOTM_OSE)
[IH_OS_OSE] = do_bootm_ose,
#endif
#if defined(CONFIG_BOOTM_PLAN9)
[IH_OS_PLAN9] = do_bootm_plan9,
#endif
#if defined(CONFIG_BOOTM_VXWORKS) &&
(defined(CONFIG_PPC) || defined(CONFIG_ARM))
[IH_OS_VXWORKS] = do_bootm_vxworks,
#endif
#if defined(CONFIG_CMD_ELF)
[IH_OS_QNX] = do_bootm_qnxelf,
#endif
#ifdef CONFIG_INTEGRITY
[IH_OS_INTEGRITY] = do_bootm_integrity,
#endif
#ifdef CONFIG_BOOTM_OPENRTOS
[IH_OS_OPENRTOS] = do_bootm_openrtos,
#endif
};
综上,如果定义宏CONFIG_BOOTM_LINUX,那么将会返回得到操作系统的启动函数:do_bootm_linux()。
接下来,将分析该函数。
do_bootm_linux的定义
该函数在不同的系统架构下都有定义,这里以ARM进行分析,函数定义如下:
int do_bootm_linux(int flag, int argc, char * const argv[],
bootm_headers_t *images)
{
if (flag & BOOTM_STATE_OS_BD_T || flag & BOOTM_STATE_OS_CMDLINE)
return -1;
if (flag & BOOTM_STATE_OS_PREP) {
boot_prep_linux(images);
return 0;
}
if (flag & (BOOTM_STATE_OS_GO | BOOTM_STATE_OS_FAKE_GO)) {
boot_jump_linux(images, flag);
return 0;
}
boot_prep_linux(images);
boot_jump_linux(images, flag);
return 0;
}
do_bootm_linux主要涉及到两个连续操作:
(1)操作系统启动前的准备操作。【boot_prep_linux】
(2)启动跳转到linux。【boot_jump_linux】
boot_prep_linux的定义
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: FDTn");
if (image_setup_linux(images)) {
printf("FDT creation failed! hanging...");
hang();
}
#endif
} else if (BOOTM_ENABLE_TAGS) {
debug("using: ATAGSn");
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) {
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 - hangingn");
hang();
}
}
boot_jump_linux()函数的定义:
static void boot_jump_linux(bootm_headers_t *images, int flag)
{
#ifdef CONFIG_ARM64
void (*kernel_entry)(void *fdt_addr, void *res0, void *res1,
void *res2);
int fake = (flag & BOOTM_STATE_OS_FAKE_GO);
kernel_entry = (void (*)(void *fdt_addr, void *res0, void *res1,
void *res2))images->ep;
debug("## Transferring control to Linux (at address %lx)...n",
(ulong) kernel_entry);
bootstage_mark(BOOTSTAGE_ID_RUN_OS);
announce_and_cleanup(fake);
if (!fake) {
do_nonsec_virt_switch();
kernel_entry(images->ft_addr, NULL, NULL, NULL);
}
#else
//1、变量 machid 保存机器 ID,如果不使用设备树的话这个机器 ID 会被传递给 Linux
内核, Linux 内核会在自己的机器 ID 列表里面查找是否存在与 uboot 传递进来的 machid 匹配的
项目,如果存在就说 Linux 内核支持这个机器,那么 Linux 就会启动!如果使用设备树的话这
个 machid 就无效了,设备树存有一个“兼容性”这个属性,Linux 内核会比较“兼容性”属性
的值(字符串)来查看是否支持这个机器。
unsigned long machid = gd->bd->bi_arch_number;
char *s;
//2、函数 kernel_entry【本质是函数指针】,看名字“内核_进入”,说明此函数是进入 Linux 内核的,也就是最终的大 boos!!此函数有三个参数:zero,arch,params,第一个参数 zero 同样为 0;第二个参数为机器 ID;第三个参数 ATAGS 或者设备树(DTB)首地址,ATAGS 是传统的方法,用于传递一些命令行信息的,如果使用设备树的话就要传递设备树(DTB)。
void (*kernel_entry)(int zero, int arch, uint params);
unsigned long r2;
int fake = (flag & BOOTM_STATE_OS_FAKE_GO);
//3、获取 kernel_entry 函数,函数 kernel_entry 并不是 由uboot 定义,而是在 Linux 内
核中定义的, Linux 内核镜像文件的第一行代码就是函数 kernel_entry,而 images->ep 保存着 Linux
内核镜像的起始地址,而起始地址保存的正是 Linux 内核第一行代码!
kernel_entry = (void (*)(int, int, uint))images->ep;
s = getenv("machid");
if (s) {
if (strict_strtoul(s, 16, &machid) < 0) {
debug("strict_strtoul failed!n");
return;
}
printf("Using machid 0x%lx from environmentn", machid);
}
debug("## Transferring control to Linux (at address %08lx)"
"...n", (ulong) kernel_entry);
bootstage_mark(BOOTSTAGE_ID_RUN_OS);
//4、调用函数 announce_and_cleanup 来打印一些信息并做一些清理工作
announce_and_cleanup(fake);
if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len)
r2 = (unsigned long)images->ft_addr;
else
r2 = gd->bd->bi_boot_params;
if (!fake) {
#ifdef CONFIG_ARMV7_NONSEC
if (armv7_boot_nonsec()) {
armv7_init_nonsec();
secure_ram_addr(_do_nonsec_entry)(kernel_entry,
0, machid, r2);
} else
#endif
//5、调用 kernel_entry 函数进入 Linux 内核,此行将一去不复返,至此,进入Linux世界!!!
kernel_entry(0, machid, r2);
}
#endif
}}
#endif
}
四、总结
在u-boot引导启动linux内核的过程中,实则起着最关键作用的操作函数是do_bootm_linux函数,在该函数中完成了u-boot到linux内核的转换过程。



