linux内核最后如何挂载根文件系统
一、前世今生
在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_name
是mtd
或者ubi
类型的根设备,则运行mount_block_root()
挂载文件系统。
(2)【方式二】:调用initrd_load()
进行早期根文件系统的挂载,如果是mount_initrd
为true的情况下,将执行根文件系统挂载操作。在linux内核中包含两种挂载早期根文件系统
的机制,初始化RAM磁盘(initrd)是一种老式的机制。而initramfs
是新的用于挂载早期根文件系统的机制。对于initrd
和initramfs
机制的目的:用于执行早期的用户空间程序;在挂载正真(最后的)根文件系统之前加载一些必须的设备驱动程序。
(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)。哈哈!