栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 系统运维 > 运维 > Linux

【u-boot】bootz命令启动linux内核过程分析

Linux 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

【u-boot】bootz命令启动linux内核过程分析

u-boot版本备注
4.1.15*
一、在u-boot的命令行下,当输入bootz命令后,u-boot将会进行怎样的操作流程?

通过一个宏定义U_BOOT_CMD进行命令与命令执行函数进行关联,从而使得在u-boot命令行中,通过输入bootz命令来启动操作系统内核。


对于U_BOOT_CMD这个命令注册宏替换操作有点复杂,但是通过层层宏替换后,可以明确知道该宏替换完成后,本质是:定义一个cmd_tbl_s结构体类型的变量并初始化值,然后放在.u_boot_list段中。如下图所示:
定义在文件:./include/command.h中

二、 do_bootz()函数执行怎样的操作过程?

通过前面分析,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内核的转换过程。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/333776.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号