Linux内核4.14版本:ARM64的内核启动过程(三)——prepare_namespace挂载根文件系统

目录

bootags参数又是怎么保存到数组的呢?

__setup

do_early_param

obsolete_checksetup


内核版本:4.14

简单分析:怎么挂接的文件系统”root=/dev/mtdblock3”

init\do_mounts.c的prepare_namespace()。

/*
 * Prepare the namespace - decide what/where to mount, load ramdisks, etc.
 */
void __init prepare_namespace(void)
{
	int is_floppy;

	if (root_delay) {
		printk(KERN_INFO "Waiting %d sec before mounting root device...\n",
		       root_delay);
		ssleep(root_delay);
	}

	/*
	 * wait for the known devices to complete their probing
	 *
	 * Note: this is a potential source of long boot delays.
	 * For example, it is not atypical to wait 5 seconds here
	 * for the touchpad of a laptop to initialize.
	 */
	wait_for_device_probe();

	md_run_setup();

	if (saved_root_name[0]) {	//判断saved_root_name[0]数组是否为空								
		root_device_name = saved_root_name;
		if (!strncmp(root_device_name, "mtd", 3) ||   //比较root_device_name数组是否已mtd开或者ubi
		    !strncmp(root_device_name, "ubi", 3)) {
			mount_block_root(root_device_name, root_mountflags);	
			goto out;		//是mtd,则跳转到out,直接挂载
		}
		ROOT_DEV = name_to_dev_t(root_device_name);
		if (strncmp(root_device_name, "/dev/", 5) == 0)	//比较是不是已/dev/开头
			root_device_name += 5;				//是的话,+5找到mtd开头
	}

	if (initrd_load())
		goto out;

	/* wait for any asynchronous scanning to complete */
	if ((ROOT_DEV == 0) && root_wait) {
		printk(KERN_INFO "Waiting for root device %s...\n",
			saved_root_name);
		while (driver_probe_done() != 0 ||
			(ROOT_DEV = name_to_dev_t(saved_root_name)) == 0)
			msleep(5);
		async_synchronize_full();
	}

	is_floppy = MAJOR(ROOT_DEV) == FLOPPY_MAJOR;

	if (is_floppy && rd_doload && rd_load_disk(0))
		ROOT_DEV = Root_RAM0;

	mount_root();			//将实际文件系统挂载到rootfs的"/dev/root"目录
out:
	devtmpfs_mount("dev");
	sys_mount(".", "/", NULL, MS_MOVE, NULL);
	sys_chroot(".");
}

      从上面代码得出,saved_root_name数组通过名字可以得出,是用来保存root文件系统的名字” /dev/mtdblock3”。

bootags参数又是怎么保存到数组的呢?

     通过搜索”saved_root_name”,找到如下代码init\do_mounts.c。

static int __init root_dev_setup(char *line)
{
	strlcpy(saved_root_name, line, sizeof(saved_root_name));
	return 1;
}
__setup("root=", root_dev_setup);

      其中root_dev_setup()函数是用来将line数组中的数据统统复制到saved_root_name数组中。          __setup("root=", root_dev_setup);中有”root=”,猜测下,这个估计就是用来匹配命令行中以”root=”开头的字符串,然后再将” root=/dev/mtdblock3”中的”/dev/mtdblock3”放在saved_root_name数组中。

__setup

       接下来分析上面的__setup("root=", root_dev_setup)宏定义,\include\linux\init.h。

#define __setup_param(str, unique_id, fn, early)          \    //定义__setup_param(str, unique_id, fn, early)
/*定义字符串数组__setup_str_##unique_id[]=str; \表示还在define中     */
static char __setup_str_##unique_id[] __initdata = str;    \      //相当于: __setup_str_ root_dev_setup[]="root="   
/*定义结构体obs_kernel_param型__setup_##unique_id*/
static struct  obs_kernel_param  __setup_##unique_id\             
               __attribute_used__                                   \
                   __attribute__((__section__(".init.setup")))        \    //设置.init.setup段
                   __attribute__((aligned((sizeof(long)))))    \
                   = { __setup_str_##unique_id, fn, early }   //将"root=",root_dev_setup,0 放在 .init.setup段中

 

#define __setup(str, fn)                   \        //定义__setup(str, fn)使用__setup_param(str, fn, fn, 0)
          __setup_param(str, fn, fn, 0)

      最终__setup("root=", root_dev_setup)宏= { __setup_str_ root_dev_setup[], root_dev_setup , 0 };
      在.init.setup段中存放了3个成员,第一个成员是字符串数组等于”root=”,第二个成员是一个函数,第三个成员early=0;
      其中.init.setup段在vmlinux.lds中使用(.init.setup段用于存放特殊的内容,比如命令行参数),在文件include\asm-generic\vmlinux.lds.h中。

#define INIT_SETUP(initsetup_align)					\
		. = ALIGN(initsetup_align);				\
		VMLINUX_SYMBOL(__setup_start) = .;			\
		KEEP(*(.init.setup))					\
		VMLINUX_SYMBOL(__setup_end) = .;

      接下来分析宏__setup("root=", root_dev_setup);又是怎么被调用的。由于通过宏”__setup("root=", root_dev_setup);”最终被存在了.init.setup段里,所以首先搜索”__setup_start”,发现在init/main.c中do_early_param函数和obsolete_checksetup函数都使用了它。

do_early_param

      先来分析do_early_param函数,首先我们看看它被谁调用。搜索do_early_param,发现它被parse_early_param()函数调用,如下图:

/* Check for early params. */
static int __init do_early_param(char *param, char *val,
				 const char *unused, void *arg)
{
	const struct obs_kernel_param *p;

	for (p = __setup_start; p < __setup_end; p++) {
		if ((p->early && parameq(param, p->str)) ||
		    (strcmp(param, "console") == 0 &&
		     strcmp(p->str, "earlycon") == 0)
		) {
			if (p->setup_func(val) != 0)
				pr_warn("Malformed early option '%s'\n", param);
		}
	}
	/* We accept everything at this stage. */
	return 0;
}

void __init parse_early_options(char *cmdline)
{
	parse_args("early options", cmdline, NULL, 0, 0, 0, NULL,
		   do_early_param);
}

/* Arch code calls this early on, or if not, just before other parsing. */
void __init parse_early_param(void)
{
	static int done __initdata;
	static char tmp_cmdline[COMMAND_LINE_SIZE] __initdata;

	if (done)
		return;

	/* All fall through to do_early_param. */
	strlcpy(tmp_cmdline, boot_command_line, COMMAND_LINE_SIZE);
	parse_early_options(tmp_cmdline);
	done = 1;
}

然后搜索parse_early_param(),发现它在start_kernel函数中使用,如下图:

asmlinkage __visible void __init start_kernel(void)
{
	.......

	pr_notice("Kernel command line: %s\n", boot_command_line);
	/* parameters may set static keys */
	jump_label_init();
	parse_early_param();
	after_dashes = parse_args("Booting kernel",
				  static_command_line, __start___param,
				  __stop___param - __start___param,
				  -1, -1, NULL, &unknown_bootoption);
	if (!IS_ERR_OR_NULL(after_dashes))
		parse_args("Setting init args", after_dashes, NULL, 0, -1, -1,
			   NULL, set_init_arg);

	.................
}

得出:在内核启动start_kernel()中会处理这个do_early_param函数.

接下来分析do_early_param源码

/* Check for early params. */
static int __init do_early_param(char *param, char *val,
				 const char *unused, void *arg)
{
	const struct obs_kernel_param *p;		//定义obs_kernel_param结构体指针*p

	for (p = __setup_start; p < __setup_end; p++) {		 //查找.init.setup段的内容
		if ((p->early && parameq(param, p->str)) ||
		    (strcmp(param, "console") == 0 &&
		     strcmp(p->str, "earlycon") == 0)
		) {
			if (p->setup_func(val) != 0)				//处理early非0的函数
				pr_warn("Malformed early option '%s'\n", param);
		}
	}
	/* We accept everything at this stage. */
	return 0;
}

上面obs_kernel_param的结构体定义如下,刚好对应了。

struct obs_kernel_param {
         const char *str;                //__setup_str_ root_dev_setup[]=”root=”
         int (*setup_func)(char *);        // root_dev_setup(char *line)
         int early;                     // early=0
};

      __setup("root=", root_dev_setup)宏= { __setup_str_ root_dev_setup[], root_dev_setup , 0 }中的3个成员。由于__setup("root=", root_dev_setup)的early=0,所以if (p->early && strcmp(param, p->str) == 0)永远不成立。所以在内核启动strat_kernel()函数中会通过do_early_param函数是处理early不为0的函数。

obsolete_checksetup

      然后分析obsolete_checksetup函数,首先我们看看它被谁调用。

start_kernel->unknown_bootoption->obsolete_checksetup

得出:在内核启动start_kernel()中会处理这个obsolete_checksetup函数.

static bool __init obsolete_checksetup(char *line)
{
	const struct obs_kernel_param *p;		//定义obs_kernel_param型结构体指针
	bool had_early_param = false;

	p = __setup_start;
	do {
		int n = strlen(p->str);
		if (parameqn(line, p->str, n)) {			//确定是否有内容
			if (p->early) {							//early非0,则不执行函数
				/* Already done in parse_early_param?
				 * (Needs exact match on param part).
				 * Keep iterating, as we can have early
				 * params and __setups of same names 8( */
				if (line[n] == '\0' || line[n] == '=')
					had_early_param = true;
			} else if (!p->setup_func) {			//  处理early为0的函数
				pr_warn("Parameter %s is obsolete, ignored\n",
					p->str);
				return true;	
			} else if (p->setup_func(line + n))		 //处理early为0的函数
				return true;
		}
		p++;
	} while (p < __setup_end);						 //从__setup_start到__setup_end查找

	return had_early_param;
}

通过上面代码分析得出:
       __setup("root=", root_dev_setup)宏= { __setup_str_ root_dev_setup[], root_dev_setup , 0 }中的第三个成员early=0, 会执行root_dev_setup()函数,然后将文件系统目录拷贝到全局变量saved_root_name[]数组中,使后面的函数来挂载文件系统.
      所以在内核启动strat_kernel()函数中会通过obsolete_checksetup函数处理early为0的函数。

猜你喜欢

转载自blog.csdn.net/yangguoyu8023/article/details/121451327