一、前世今生

kernel_init线程函数中会调用kernel_init_freeable()函数,在kernel_init_freeable函数中将调用prepare_namespace()函数挂载指定的根文件系统。

【漫漫长路,挂载开始!!!】


二、kernel_init线程入口

kernel_init()函数如下所示(/init/main.c):

static int __ref kernel_init(void *unused)
{
	int ret;

	kernel_init_freeable();
    
	async_synchronize_full();
	free_initmem();
	mark_rodata_ro();
	system_state = SYSTEM_RUNNING;
	numa_default_policy();

	flush_delayed_fput();

	if (ramdisk_execute_command) {
		ret = run_init_process(ramdisk_execute_command);
		if (!ret)
			return 0;
		pr_err("Failed to execute %s (error %d)\n",
		       ramdisk_execute_command, ret);
	}

	if (execute_command) {
		ret = run_init_process(execute_command);
		if (!ret)
			return 0;
		panic("Requested init %s failed (error %d).",
		      execute_command, ret);
	}
	if (!try_to_run_init_process("/sbin/init") ||
	    !try_to_run_init_process("/etc/init") ||
	    !try_to_run_init_process("/bin/init") ||
	    !try_to_run_init_process("/bin/sh"))
		return 0;

	panic("No working init found.  Try passing init= option to kernel. "
	      "See Linux Documentation/init.txt for guidance.");
}

上述代码第5行执行完成时,根文件系统就挂载成功了。具体执行函数由prepare_namespace()实现。下文将分析该函数。

第7~8行代码用于释放初始化函数调用时分配的内存,async_synchronize_full()函数用于同步所有的异步函数调用。free_initmem()函数用于释放初始化时所使用的内存空间。

三、重磅角色—prepare_namespace

prepare_namespace函数定义在(/init/do_mounts.c)文件中,如下所示:

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_device_probe();

	md_run_setup();

	if (saved_root_name[0]) {
		root_device_name = saved_root_name;
		if (!strncmp(root_device_name, "mtd", 3) ||
		    !strncmp(root_device_name, "ubi", 3)) {
			mount_block_root(root_device_name, root_mountflags);
			goto out;
		}
		ROOT_DEV = name_to_dev_t(root_device_name);
		if (strncmp(root_device_name, "/dev/", 5) == 0)
			root_device_name += 5;
	}

	if (initrd_load())
	{
		goto out;
	}
		
	/* 等待所有的异步扫描操作完成 */
	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(100);
		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();

out:
	devtmpfs_mount("dev");
	sys_mount(".", "/", NULL, MS_MOVE, NULL);
	sys_chroot(".");
}

上述代码本质是三种程序运行方式:

(1)【方式一】:如果root_device_namemtd或者ubi类型的根设备,则调用mount_block_root()函数挂载文件系统。

(2)【方式二】:调用initrd_load()进行早期根文件系统的挂载,如果mount_initrd为true的情况下,将执行根文件系统挂载操作。在linux内核中包含两种挂载早期根文件系统的机制:初始化RAM磁盘(initrd)是一种老式的机制。而initramfs是新的用于挂载早期根文件系统的机制。设计initrdinitramfs机制的目的:用于执行早期的用户空间程序;在挂载真正(最后的)根文件系统之前加载一些必须的设备驱动程序

(3)【方式三】:调用mount_root()函数进行文件系统挂载。该种方式是linux内核中比较常用的方式,在这种方式下,又包含三种文件系统挂载操作:1、nfs方式。2、Floppy方式。3、block方式。在平时开发中,常使用nfs进行网络挂载根文件系统,以便进行开发和调试。

以上三种方式,在实际linux启动过程中,linux内核将选择其中一种作为挂载根文件系统的方式。


下文将继续分析这三种方式:

【方式一】mount_block_root()函数将调用do_mount_root()进行文件系统挂载。如下图所示:
在这里插入图片描述

【方式二】initrd方式的文件系统挂载。

【方式三】

对于方式三,会调用mount_root()函数进行根文件系统的挂载,该函数定义如下(/init/do_mounts.c):

void __init mount_root(void)
{
#ifdef CONFIG_ROOT_NFS
	if (ROOT_DEV == Root_NFS) {
		if (mount_nfs_root())
			return;

		printk(KERN_ERR "VFS: Unable to mount root fs via NFS, trying floppy.\n");
		ROOT_DEV = Root_FD0;
	}
#endif
#ifdef CONFIG_BLK_DEV_FD
	if (MAJOR(ROOT_DEV) == FLOPPY_MAJOR) {
		/* rd_doload is 2 for a dual initrd/ramload setup */
		if (rd_doload==2) {
			if (rd_load_disk(1)) {
				ROOT_DEV = Root_RAM1;
				root_device_name = NULL;
			}
		} else
			change_floppy("root floppy");
	}
#endif
#ifdef CONFIG_BLOCK
	create_dev("/dev/root", ROOT_DEV);
	mount_block_root("/dev/root", root_mountflags);
#endif
}

从以上代码片段可知:在进行mount_root()操作时,同样存在三种方式:

(1)NFS方式

通过mount_nfs_root()函数完成。

(2)ramload方式

通过rd_load_disk()函数完成。

(3)BLOCK方式

通过create_dev()和mount_block_root()两个函数完成。

mount_nfs_root()mount_block_root()两个函数都调用一个核心功能函数:do_mount_root()

该函数定义如下:

static int __init do_mount_root(char *name, char *fs, int flags, void *data)
{
	struct super_block *s;
	int err = sys_mount(name, "/root", fs, flags, data);
	if (err)
		return err;

	sys_chdir("/root");
	s = current->fs->pwd.dentry->d_sb;
	ROOT_DEV = s->s_dev;
	printk(KERN_INFO
	       "VFS: Mounted root (%s filesystem)%s on device %u:%u.\n",
	       s->s_type->name,
	       s->s_flags & MS_RDONLY ?  " readonly" : "",
	       MAJOR(ROOT_DEV), MINOR(ROOT_DEV));
	return 0;
}

在上述代码中,调用了sys_mount()sys_chdir()系统调用函数。


又到linux的系统调用了,sys_mount系统调用可参考该篇文章:
https://blog.csdn.net/kai_ding/article/details/9050429


由于小生知识与精力有限,如若文章存在不妥的地方,欢迎批评,也可与小生一起讨论(iriczhao@163.com)。哈哈!

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐