Linux内核rootfs的初始化过程

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

在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(".");



你可能感兴趣的:(Linux内核,驱动开发移植,Linux内核学习笔记)