首先看下mnt_init方法:
// fs/namespace.c
void __init mnt_init(void)
{
...
init_rootfs();
init_mount_tree();
}
看下其init_rootfs方法:
// init/do_mounts.c
static struct file_system_type rootfs_fs_type = {
.name = "rootfs",
.mount = rootfs_mount,
.kill_sb = kill_litter_super,
};
int __init init_rootfs(void)
{
int err = register_filesystem(&rootfs_fs_type);
...
return err;
}
该方法主要是用来注册rootfs文件系统。
再看下init_mount_tree方法:
// fs/namespace.c
static void __init init_mount_tree(void)
{
struct vfsmount *mnt;
...
struct path root;
struct file_system_type *type;
type = get_fs_type("rootfs");
...
mnt = vfs_kern_mount(type, 0, "rootfs", NULL);
...
root.mnt = mnt;
root.dentry = mnt->mnt_root;
...
set_fs_pwd(current->fs, &root);
set_fs_root(current->fs, &root);
}
该方法首先拿到上面注册的rootfs文件系统,再调用vfs_kern_mount方法挂载该系统,然后将挂载结果mnt赋值给类型为struct path的变量root,同时将root.dentry赋值为mnt->mnt_root,即挂载的rootfs文件系统的根目录。
最后,设置当前进程的当前目录和根目录都为root。
看下vfs_kern_mount方法:
// fs/namespace.c
struct vfsmount *vfs_kern_mount(struct file_system_type *type,
int flags, const char *name,
void *data)
{
struct fs_context *fc;
struct vfsmount *mnt;
...
fc = fs_context_for_mount(type, flags);
...
if (!ret)
mnt = fc_mount(fc);
...
return mnt;
}
EXPORT_SYMBOL_GPL(vfs_kern_mount);
先看下其fs_context_for_mount方法:
// fs/fs_context.c
struct fs_context *fs_context_for_mount(struct file_system_type *fs_type,
unsigned int sb_flags)
{
return alloc_fs_context(fs_type, NULL, sb_flags, 0,
FS_CONTEXT_FOR_MOUNT);
}
EXPORT_SYMBOL(fs_context_for_mount);
继续看alloc_fs_context方法:
// fs/fs_context.c
static struct fs_context *alloc_fs_context(struct file_system_type *fs_type,
struct dentry *reference,
unsigned int sb_flags,
unsigned int sb_flags_mask,
enum fs_context_purpose purpose)
{
int (*init_fs_context)(struct fs_context *);
struct fs_context *fc;
...
fc = kzalloc(sizeof(struct fs_context), GFP_KERNEL);
...
fc->fs_type = get_filesystem(fs_type);
...
init_fs_context = fc->fs_type->init_fs_context;
if (!init_fs_context)
init_fs_context = legacy_init_fs_context;
ret = init_fs_context(fc);
...
return fc;
...
}
由上可知,fc->fs_type指向的rootfs文件系统类型里并没有init_fs_context方法,所以该方法就被赋值为legacy_init_fs_context,之后又调用了该方法。
// fs/fs_context.c
const struct fs_context_operations legacy_fs_context_ops = {
.free = legacy_fs_context_free,
.dup = legacy_fs_context_dup,
.parse_param = legacy_parse_param,
.parse_monolithic = legacy_parse_monolithic,
.get_tree = legacy_get_tree,
.reconfigure = legacy_reconfigure,
};
/*
* Initialise a legacy context for a filesystem that doesn't support
* fs_context.
*/
static int legacy_init_fs_context(struct fs_context *fc)
{
fc->fs_private = kzalloc(sizeof(struct legacy_fs_context), GFP_KERNEL);
...
fc->ops = &legacy_fs_context_ops;
return 0;
}
该方法的主要作用就是将fc->ops字段设置为legacy_fs_context_ops。
继续看下vfs_kern_mount的fc_mount方法:
// fs/namespace.c
struct vfsmount *fc_mount(struct fs_context *fc)
{
int err = vfs_get_tree(fc);
if (!err) {
...
return vfs_create_mount(fc);
}
...
}
EXPORT_SYMBOL(fc_mount);
该方法先调用了vfs_get_tree方法,看下:
// fs/super.c
int vfs_get_tree(struct fs_context *fc)
{
...
error = fc->ops->get_tree(fc);
...
return 0;
}
EXPORT_SYMBOL(vfs_get_tree);
由上可知,fc->ops指向的值为legacy_fs_context_ops,所以,fc->ops->get_tree对应的方法为legacy_get_tree。
// fs/fs_context.c
static int legacy_get_tree(struct fs_context *fc)
{
...
struct dentry *root;
root = fc->fs_type->mount(fc->fs_type, fc->sb_flags,
fc->source, ctx->legacy_data);
...
fc->root = root;
return 0;
}
该方法又调用了fc->fs_type->mount指向的方法,并将返回值赋值给fc->root,这样,fc->root就指向了该文件系统的跟目录。
由上可知,fc->fs_type->mount指向的方法为rootfs_mount。
// init/do_mounts.c
static struct dentry *rootfs_mount(struct file_system_type *fs_type,
int flags, const char *dev_name, void *data)
{
...
void *fill = ramfs_fill_super;
...
return mount_nodev(fs_type, flags, data, fill);
}
该方法中,fill变量被设置为ramfs_fill_super方法,然后该方法又调用了mount_nodev方法。
// fs/super.c
struct dentry *mount_nodev(struct file_system_type *fs_type,
int flags, void *data,
int (*fill_super)(struct super_block *, void *, int))
{
int error;
struct super_block *s = sget(fs_type, NULL, set_anon_super, flags, NULL);
...
error = fill_super(s, data, flags & SB_SILENT ? 1 : 0);
...
return dget(s->s_root);
}
EXPORT_SYMBOL(mount_nodev);
该方法先创建一个struct super_block实例,再调用fill_super指向的方法为该实例赋值,最后返回s->s_root指向的值,即:该文件系统的根目录。
由上可知,fill_super指向的方法为ramfs_fill_super。
// fs/ramfs/inode.c
int ramfs_fill_super(struct super_block *sb, void *data, int silent)
{
...
inode = ramfs_get_inode(sb, NULL, S_IFDIR | fsi->mount_opts.mode, 0);
sb->s_root = d_make_root(inode);
...
return 0;
}
该方法先调用ramfs_get_inode方法创建并初始化一个inode,再调用d_make_root方法生成一个dentry,并将inode的值赋值给dentry的d_inode字段。
生成的dentry实例最后被赋值给了sb->s_root,这样sb->s_root就指向了该文件系统的根目录。
返回上面的fc_mount方法,通过调用vfs_get_tree方法,fc->root的值就指向了rootfs文件系统的根目录。
再看下fc_mount中的的vfs_create_mount方法:
// fs/namespace.c
struct vfsmount *vfs_create_mount(struct fs_context *fc)
{
struct mount *mnt;
...
mnt = alloc_vfsmnt(fc->source ?: "none");
...
mnt->mnt.mnt_sb = fc->root->d_sb;
mnt->mnt.mnt_root = dget(fc->root);
mnt->mnt_mountpoint = mnt->mnt.mnt_root;
mnt->mnt_parent = mnt;
...
return &mnt->mnt;
}
EXPORT_SYMBOL(vfs_create_mount);
由该方法可以看到,mnt->mnt.mnt_root被设置为fc->root,即rootfs文件系统的根目录。
fc_mount方法调用完毕之后退回到vfs_kern_mount方法,vfs_kern_mount方法调用完毕之后退回到init_mount_tree,而在上文也讲到了,init_mount_tree方法里会把root.dentry的值设置为mnt->mnt_root,即rootfs文件系统的根目录,再之后将root的值赋值给当前进程的当前目录和根目录字段。
这样,rootfs文件系统的整个挂载过程就结束了,最终的结果就是,当前进程的根目录就是rootfs文件系统的根目录。
那rootfs文件系统的根目录就是我们想要找的根目录吗?
当然不是,我们要找的根目录应该在硬盘上啊。
那硬盘上的文件系统的根目录是在哪里挂载的呢?硬盘上的文件系统和rootfs文件系统又是什么关系呢?
限于篇幅原因,我们下篇文章再讲。
完。
本文分享自 Linux内核及JVM底层相关技术研究 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!