Linux内核有没有rootfs,Linux内核rootfs的初始化过程[通俗易懂]

2022-11-10 15:31:59 浏览数 (1)

大家好,又见面了,我是你们的朋友全栈君。

由于在下水平相当有限,不当之处,还望大家批评指正^_^

在Linux shell中执行mount命令,通常可以看到某个做了文件系统的磁盘分区或flash分区或内存文件系统做为所谓的根文件系统被mount到了挂载点/处。

实际上内核中最初始的根文件系统,并不是来自内核外部,他是由内核自己构建出来的。

为了说明这个过程,我们先说说mount的过程。

系统调用sys_mount是在fs/namespace.c中实现的,主要工作则由do_mount完成。

一个常规的mount操作大致包含两个动作:

(1)将一个文件系统加载到内核中

注意,这里仅仅是加载。

该动作是由vfs_kern_mount完成的。

每一个文件系统被加载到内核后,内核中都会产生如下几个结构:

一个struct mount结构

一个struct super_block结构

一个struct dentry结构,他是此文件系统的根目录的目录顶,名称为/。注意仅仅是此文件系统的根目录。

struct mount结构中有指针分别指向super_block结构和此文件系统的根目录顶结构。

super_block结构和此文件系统的根目录顶结构也有指针相互指向对方。

另外,struct mount结构中的mnt_devname记录了所mount的块设备文件。

(2)将上一步加载的文件系统,挂接到以/为起点的目录树中的某个位置处。

此工作由do_add_mount完成。

其实就是将此mount结构串接到某个已有的文件系统内的某个目录项上(类型需要为目录)。

实质性的操作函数如下: void mnt_set_mountpoint(struct mount *mnt,

struct mountpoint *mp,

struct mount *child_mnt)

{

mp->m_count ;

mnt_add_count(mnt, 1);

/* essentially, that’s mntget */

child_mnt->mnt_mountpoint = dget(mp->m_dentry);

child_mnt->mnt_parent = mnt;

child_mnt->mnt_mp = mp;

}

这里child_mnt为被挂载的文件系统的mount结构。

mp->m_dentry是挂载点路径中最后一个目录对应的目录项。

mnt为mp->m_dentry所在文件系统的mount结构。

另外,mp->m_dentry->d_flags还会被置上 DCACHE_MOUNTED标记,

这个动作完成后,外界就可以访问到这个文件系统了。

这个过程感觉挺复杂,在下对其代码实现理解得也很有限^_^

不过,可以通过open系统调用的实现,看到内核遍历路径的过程中,是如何转向被挂载的文件系统内部的。

下面列出了sys_open的函数调用链(从上到下),

其中最后的函数__lookup_mnt展示了由挂载点目录项查找被挂载的文件系统对应的struct mount结构的过程。

sys_open

do_sys_open

do_filp_open

path_openat

path_openat

link_path_walk

walk_component

lookup_fast

__follow_mount_rcu

__lookup_mnt

前面说了,一个普通的mount包含上述两个步骤。

然而,内核中最初始的根文件系统,由于其特殊性(没有地方可以挂接),所以只执行了上述两步中的第一步。

这里先贴一下相关函数吧^_^

fs/namespace.c

mnt_init

init_mount_tree

核心是init_mount_tree,其代码如下。

static void __init init_mount_tree(void)

{

struct vfsmount *mnt;

struct mnt_namespace *ns;

struct path root;

struct file_system_type *type;

type = get_fs_type(“rootfs”);

if (!type)

panic(“Can’t find rootfs type”);

mnt = vfs_kern_mount(type, 0, “rootfs”, NULL);

put_filesystem(type);

if (IS_ERR(mnt))

panic(“Can’t create rootfs”);

ns = create_mnt_ns(mnt);

if (IS_ERR(ns))

panic(“Can’t allocate initial namespace”);

init_task.nsproxy->mnt_ns = ns;

get_mnt_ns(ns);

root.mnt = mnt;

root.dentry = mnt->mnt_root;

set_fs_pwd(current->fs, &root);

set_fs_root(current->fs, &root);

}

可见内核通过如下方式调用vfs_kern_mount加载了一个文件系统到内核中。

vfs_kern_mount(type, 0, “rootfs”, NULL);

特殊之处:一般的mount,设备文件为/dev/path/to/block_dev_file,而这里的设备文件为rootfs。

所以,相应的mnt_devname就是rootfs了。注意,只有这个最早的rootfs对应的块设备文件为rootfs.

文件系统类型type(即名叫rootfs文件系统类型)的实现在哪里呢?

答案是在fs/ramfs/inode.c中。如下即是。

static struct file_system_type rootfs_fs_type = {

.name= “rootfs”,

.mount = rootfs_mount,

.kill_sb = kill_litter_super,

};

而此文件正是ramfs文件系统的实现文件,其结构定义如下。

static struct file_system_type ramfs_fs_type = {

.name= “ramfs”,

.mount = ramfs_mount,

.kill_sb = ramfs_kill_sb,

.fs_flags = FS_USERNS_MOUNT,

};

可见初始的rootfs文件系统类型,基本上就是ramfs,超级块的填充、文件系统的操作,与ramfs都是共用同一份代码。

而rootfs包装一个自己的mount函数rootfs_mount,只是为了传个MS_NOUSER标记而已。

对于设备名rootfs,rootfs_mount压根就没用到,而实际上也不存在这个设备。

那么这里的vfs_kern_mount完成后,达成了什么效果呢。

同前面介绍的一样,会产生一个mount结构,一个super_block结构,一个文件系统内的根目录对应的名称为/的目录项。

那么这个文件系统往哪挂呢?没地方挂 ^_^

接下来可以看到,内核的主要操作如下:

ns = create_mnt_ns(mnt);init_task.nsproxy->mnt_ns = ns;get_mnt_ns(ns);root.mnt = mnt;root.dentry = mnt->mnt_root;set_fs_pwd(current->fs, &root);set_fs_root(current->fs, &root);

创建一个mnt_namespace(mount命名空间),将rootfs的mount结构做为其root。

然后将init_task的mount命名空间指定为此命名空间。

然后,将此初始rootfs的根目录设置为当前任务的pwd及root。

好了,至此目录树的起点/算是有了。但是目前rootfs里面还没有内容呢。

接下来start_kernel的流程会顺着rest_init -) kernel_init -) kernel_init_freeable往下走。

那接下来顺着kernel_init_freeable往下看rootfs相关内容。

先是走到do_pre_smp_initcalls,从而调用到了由rootfs_initcall(populate_rootfs);定义的初始化函数populate_rootfs。

如果系统配置了使用initramfs,那么后面populate_rootfs会将内存中的根文件系统压缩包解压到rootfs中。

压缩包起始于__initramfs_start地址处,大小为__initramfs_size。

注意,这只是向初始的rootfs中增加内容,并没有更换rootfs。

具体过程,就是解压压缩包,根据解压出的内容,在初始的根文件系统中创建目录、文件,然后将解压出的文件的内容部分write到创建的文件中。由于已经有根文件系统了,因此相关代码就是通过调用通用的文件操作方面的系统调用,来完成上述任务的。

具体函数有do_name、do_copy、do_symlink。

对于压缩包压缩算法的识别,是由decompress_method函数完成的。

相关代码如下:

struct compress_format {

unsigned char magic[2];

const char *name;

decompress_fn decompressor;

};

static const struct compress_format compressed_formats[] __initconst = {

{ {037, 0213}, “gzip”, gunzip },

{ {037, 0236}, “gzip”, gunzip },

{ {0x42, 0x5a}, “bzip2”, bunzip2 },

{ {0x5d, 0x00}, “lzma”, unlzma },

{ {0xfd, 0x37}, “xz”, unxz },

{ {0x89, 0x4c}, “lzo”, unlzo },

{ {0, 0}, NULL, NULL }

};

decompress_fn __init decompress_method(const unsigned char *inbuf, int len,

const char **name)

{

const struct compress_format *cf;

if (len < 2)

return NULL; /* Need at least this much… */

for (cf = compressed_formats; cf->name; cf ) {

if (!memcmp(inbuf, cf->magic, 2))

break;

}

if (name)

*name = cf->name;

return cf->decompressor;

}

实际上,这里识别的前两个字节,应该就是相应算法的压缩文件的前两个字节。

例如xz文件的前两个字节内容如下(前两个字节正是上面的xz对应的magic数字):

[root@localhost ~]# hexdump -n 2 -C test.tar.xz

00000000 fd 37 |.7|

00000002

[root@localhost ~]#

initramfs环节完成了,继续顺着kernel_init_freeable往下看。

如果ramdisk_execute_command指向的init程序不可访问,

就进入prepare_namespace,但是这个过程涉及到内核命令行参数中与rootfs有关的内容。

例如,noinitrd、initrd=adrress,size、 root=/dev/ram、root=/dev/mmcblk0p1、root=/dev/nfs等。

这一步做完,初始的根文件系统基本上就被替换掉了。

最终的根文件系统可能是内存文件系统、可能是flash存储介质上的一块区域,也可能是nfs,就看用户的系统是如何定制的了。

如果是内存文件系统,这里应该会创建/dev/ram或/dev/root块设备文件节点,并将之mount为新的rootfs。

实际的过程为:

sys_mount(block_dev_file, “/root”, fs, flags, data);

sys_chdir(“/root”);

sys_mount(“.”, “/”, NULL, MS_MOVE, NULL);

sys_chroot(“.”);

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

0 人点赞