linux文件系统初始化过程主要分为三个阶段:挂载rootfs,提供第一个挂载点’’/;加载initrd,扩展VFS树;执行init程序,完成linux系统的初始化。下面会详细介绍每个阶段的主要内容。
-
vfs_caches_init()负责挂载rootfs文件系统,并创建了第一个挂载点目录:’/’;
-
rest_init()负责加载initrd文件,扩展VFS树,创建基本的文件系统目录拓扑;
-
init程序负责挂载磁盘文件系统,并将文件系统的根目录从rootfs切换到磁盘文件系统;
参考:https://www.cnblogs.com/shangye/p/6260471.html
https://www.cnblogs.com/alantu2018/p/8447303.html
1. 注册挂载根文件系统(初始化,内容空的)
start_kernel
vfs_caches_init
mnt_init
sysfs_init 注册并挂载sysfs文件系统,sysfs先于rootfs挂载是为全面展示linux驱动模型做好准备,sysfs文件系统目前还没有挂载到rootfs的某个挂载点上,后续init程序会把sysfs挂载到rootfs的sys挂载点上
init_rootfs()注册rootfs文件系统
init_mount_tree() 挂载rootfs文件系统
vfs_kern_mount
mount_fs
type->mount其实是rootfs_mount
mount_nodev
fill_super 其实是ramfs_fill_super
inode = ramfs_get_inode(sb, NULL, S_IFDIR | fsi->mount_opts.mode, 0);
sb->s_root = d_make_root(inode);
static const struct qstr name = QSTR_INIT("/", 1);[1*]
__d_alloc(root_inode->i_sb, &name);
...
mnt->mnt.mnt_root = root;[2*]
mnt->mnt.mnt_sb = root->d_sb;[3*]
mnt->mnt_mountpoint = mnt->mnt.mnt_root;[4*]
mnt->mnt_parent = mnt;[5*]
root.mnt = mnt;
root.dentry = mnt->mnt_root;
mnt->mnt_flags |= MNT_LOCKED;
set_fs_pwd(current->fs, &root);
set_fs_root(current->fs, &root);
bdev_cache_init()
chrdev_init()字符设备相关的初始化
...
rest_init
kernel_thread(kernel_init, NULL, CLONE_FS);
在执行kernel_init之前,会建立roofs文件系统。
[1*]处设置了根目录的名字为“/”。
[2*]处设置了vfsmount中的root目录
[3*]处设置了vfsmount中的超级块
[4*]处设置了vfsmount中的文件挂载点,指向了自己
[5*]处设置了vfsmount中的父文件系统的vfsmount为自己
从上面可以看到注册并挂载sysfs文件系统,sysfs先于rootfs挂载是为全面展示linux驱动模型做好准备,sysfs文件系统目前还没有挂载到rootfs的某个挂载点上,后续init程序会把sysfs挂载到rootfs的sys挂载点上
在这里,将rootfs文件系统挂载。它的挂载点默认为”/”.最后切换进程的根目录和当前目录为”/”.这也就是根目录的由来。不过这里只是初始化。等挂载完具体的文件系统之后,一般都会将根目录切换到具体的文件系统。所以在系统启动之后,用mount命令是看不到rootfs的挂载信息的.
2. 虚拟文件系统的挂载(解压具体的文件系统到根文件系统里面)
参考:https://www.cnblogs.com/schips/p/13178870.html
https://blog.csdn.net/crazycoder8848/article/details/79177228
https://www.cnblogs.com/shangye/p/6260471.html
static int __init kernel_init(void * unused)
{
……
……
do_basic_setup();
if (!ramdisk_execute_command)
ramdisk_execute_command = "/init";
if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
ramdisk_execute_command = NULL;
prepare_namespace();
}
/*
\* Ok, we have completed the initial bootup, and
\* we're essentially up and running. Get rid of the
\* initmem segments and start the user-mode stuff..
*/
init_post();
return 0;
}
do_basic_setup()是一个很关键的函数,所有直接编译在kernel中的模块都是由它启动的。代码片段如下:
static void __init do_basic_setup(void)
{
/* drivers will send hotplug events */
init_workqueues();
usermodehelper_init();
driver_init();
init_irq_proc();
do_initcalls();
}
Do_initcalls()用来启动所有在__initcall_start和__initcall_end段的函数,而静态编译进内核的modules也会将其入口放置在这段区间里。
跟根文件系统相关的初始化函数都会由rootfs_initcall()所引用。注意到有以下初始化函数:
rootfs_initcall(populate_rootfs);
也就是说会在系统初始化的时候会调用populate_rootfs进行初始化。
总流程位:
kernel_init
do_basic_setup
do_initcalls
rootfs_initcall(populate_rootfs)
prepare_namespace 检查init是否存在,如果不在,注册其他的根文件系统
populate_rootfs函数的作用就是将编译的initramfs文件系统解压到rootfs的根目录中。
static int __init populate_rootfs(void)
{
char *err = unpack_to_rootfs(__initramfs_start, __initramfs_size);//将__initramfs_start处的文件系统解压出来,上面说过了,内核编译时候保证至少会有一个initramfs在此处
if (err)
panic("%s", err); /* Failed to decompress INTERNAL initramfs */
if (initrd_start) {
//如果配置grub时候指定了外部文件系统,grub会将外部文件数据加载到initrd_start
#ifdef CONFIG_BLK_DEV_RAM//如果配置内核支持initrd格式的文件系统
int fd;
printk(KERN_INFO "Trying to unpack rootfs image as initramfs...\n");
err = unpack_to_rootfs((char *)initrd_start,
initrd_end - initrd_start);//首先还是按照initramfs格式解压grub加载的文件系统
if (!err) {
free_initrd();
goto done;
} else {
clean_rootfs();
unpack_to_rootfs(__initramfs_start, __initramfs_size);//如果grub加载的文件系统不是initramfs格式,那么清除rootfs中的数据,重新解压__initramfs_start,因为目录可能被破坏
}
printk(KERN_INFO "rootfs image is not initramfs (%s)"
"; looks like an initrd\n", err);
fd = sys_open("/initrd.image",
O_WRONLY|O_CREAT, 0700);
if (fd >= 0) {
ssize_t written = xwrite(fd, (char *)initrd_start,
initrd_end - initrd_start);//将grub加载的文件系统写入到/initrd.image文件中
if (written != initrd_end - initrd_start)
pr_err("/initrd.image: incomplete write (%zd != %ld)\n",
written, initrd_end - initrd_start);
sys_close(fd);
free_initrd();
}
done:
#else
printk(KERN_INFO "Unpacking initramfs...\n");
err = unpack_to_rootfs((char *)initrd_start,
initrd_end - initrd_start);//如果配置内核不支持initrd格式文件系统,那么统一按照initramfs格式解压
if (err)
printk(KERN_EMERG "Initramfs unpacking failed: %s\n", err);
free_initrd();
#endif
/*
* Try loading default modules from initramfs. This gives
* us a chance to load before device_initcalls.
*/
load_default_modules();
}
return 0;
}
此时rootfs文件系统中的基本的目录结构已经被populate_rootfs处理好。
具体过程,就是解压压缩包,根据解压出的内容,在初始的根文件系统中创建目录、文件,然后将解压出的文件的内容部分write到创建的文件中。由于已经有根文件系统了,因此相关代码就是通过调用通用的文件操作方面的系统调用,来完成上述任务的。
prepare_namespace
是调用mount_root挂载/dev/root,将/dev/root挂载到/root上,并且将当前目前切换到/root上
3. 构建最小根文件系统所需要的东西
总结最小根文件系统所需东西
- 终端/dev/console 设置/dev/null (如果没有设置标准输入,输出,错误——>无底洞,输出看不到)
- 设置配置文化inittab(配置文件里指定的应用程序或默认配置)
- 需要库(我们想我们自己建立的.c文件里的各种fopen,fread都是c库)
- init本身,即busybox