ILD

filesystem mount process
作者:Yuan Jianpeng 邮箱:yuanjp89@163.com
发布时间:2025-8-10 站点:Inside Linux Development

本文以overlay的挂载过程为例,学习文件系统挂载流程。


1 filesystem context

2019年,内核引入了filesystem context,挂载过程走这个新的流程。定义了一个新的fs_context结构体,所有的数据在这个结构体中维护。见参考链接1。内核的文档见参考链接2。


头文件

include/linux/fs_context.h

struct fs_context {
        const struct fs_context_operations *ops;
        struct mutex            uapi_mutex;     /* Userspace access mutex */
        struct file_system_type *fs_type;
        void                    *fs_private;    /* The filesystem's context */
        void                    *sget_key;
        struct dentry           *root;          /* The root and superblock */
        struct user_namespace   *user_ns;       /* The user namespace for this mount */
        struct net              *net_ns;        /* The network namespace for this mount */
        const struct cred       *cred;          /* The mounter's credentials */ 
        struct p_log            log;            /* Logging buffer */
        const char              *source;        /* The source name (eg. dev path) */
        void                    *security;      /* LSM options */
        void                    *s_fs_info;     /* Proposed s_fs_info */
        unsigned int            sb_flags;       /* Proposed superblock flags (SB_*) */
        unsigned int            sb_flags_mask;  /* Superblock flags that were changed */
        unsigned int            s_iflags;       /* OR'd with sb->s_iflags */
        enum fs_context_purpose purpose:8;
        enum fs_context_phase   phase:8;        /* The phase the context is in */
        bool                    need_free:1;    /* Need to call ops->free() */
        bool                    global:1;       /* Goes into &init_user_ns */
        bool                    oldapi:1;       /* Coming from mount(2) */
        bool                    exclusive:1;    /* create new superblock, reject existing one */
};


2 挂载overlayfs

挂载一个overlayfs,执行:

# mount -t overlay overlay -olowerdir=/tmp/lower,upperdir=upper,workdir=worker merged



3 overlayfs代码

overlayfs定义内核要求的结构体和函数等,等待内核调用。

3.1 fs/overlayfs/super.c

首先需要注册一个文件系统,定义一个struct file_system_type结构体变量ovl_fs_type,然后调用register_filesystem()注册文件系统。


struct file_system_type ovl_fs_type = {
        .owner                  = THIS_MODULE,
        .name                   = "overlay",
        .init_fs_context        = ovl_init_fs_context,
        .parameters             = ovl_parameter_spec,
        .fs_flags               = FS_USERNS_MOUNT,
        .kill_sb                = kill_anon_super,
};
MODULE_ALIAS_FS("overlay");


static int __init ovl_init(void)
{
    register_filesystem(&ovl_fs_type);
}


这个结构体,定义了初始化fs_context的接口init_fs_context:ovl_init_fs_context,以及一个参数spec:ovl_parameter_spec

这两个函数和数组,都定义在fs/overlayfs/params.c


3.2 fs/overlayfs/params.c

ovl_init_fs_context,它的任务,是分配两个结构体,然后初始化fc的ops,


int ovl_init_fs_context(struct fs_context *fc)
{
    struct ovl_fs_context *ctx = kzalloc(sizeof(*ctx), GFP_KERNEL_ACCOUNT);
    struct ovl_fs *ofs = kzalloc(sizeof(struct ovl_fs), GFP_KERNEL);
            
        fc->s_fs_info           = ofs;
        fc->fs_private          = ctx;
        fc->ops                 = &ovl_context_ops;
}



最重要的是:

static const struct fs_context_operations ovl_context_ops = {
        .parse_monolithic = ovl_parse_monolithic,
        .parse_param = ovl_parse_param,
        .get_tree    = ovl_get_tree,
        .reconfigure = ovl_reconfigure,
        .free        = ovl_free,
};


fs_context_operations定义了,创建一个文件系统需要的接口。下面我们从内核框架代码来看,这些函数是如何调用的。


4 内核框架代码

在ovl_init_fs_context,加上dump_stack(),挂载一个overlayfs,查看调用栈如下:

[   15.740816]  ovl_init_fs_context+0x20/0x124 [overlay]
[   15.740853]  alloc_fs_context+0xd0/0x148
[   15.740867]  fs_context_for_mount+0x1c/0x24
[   15.740878]  path_mount+0x530/0x6a0
[   15.740893]  do_mount+0x64/0x78
[   15.740905]  __arm64_sys_mount+0x144/0x15c
[   15.740917]  do_el0_svc+0xa8/0xd0
[   15.740928]  el0_svc+0x18/0x44
[   15.740940]  el0t_64_sync_handler+0x80/0x124
[   15.740951]  el0t_64_sync+0x14c/0x150


4.1 fs/namespace.c

系统调用以及相关的处理函数,直到fs context这一层,都是定义在fs/namespace.c里面的。

一个全新的挂载(非bind或者reconfigure?),调用流程如下,这里我们


SYSCALL_DEFINE5(mount, char __user *, dev_name, char __user *, dir_name,
                char __user *, type, unsigned long, flags, void __user *, data)


type就是-t overlay的overlay

dev_name,就是设备名,我们传的是overlay。对于非设备类型的挂载,这个参数可以随便填,只是个标记。

dir_name,就是挂载的目标路径,这里是merged。

flags,是解析出来的通用挂载选项。data是挂载参数页,以page的形式传递,是为了支持任何文件系统的特殊情况。


    -》do_mount()

        -》path_mount


path_mount,这里就会区分挂载类型,对于全新的挂载,会调用:

    -》do_new_mount()


do_new_mount()

这个函数处理所有的挂载流程

1 找到对应的文件系统

type = get_fs_type(fstype);


2 创建fs context

fc = fs_context_for_mount(type, sb_flags);


3 解析参数

dev_name,当成source参数解析。

vfs_parse_fs_string(fc, "source", name, strlen(name));

其它参数走:

parse_monolithic_mount_data(fc, data);


4 初始化文件系统

vfs_get_tree(fc);


5 挂载文件系统

do_new_mount_fc(fc, path, mnt_flags);


6 释放fs context

put_fs_context(fc);


4.2 fs/fs_context.c

fs_context_for_mount()

类似的有:fs_context_for_reconfigure()/fs_context_for_submount()/

它们都调用:


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)


它分配一个新的fs_context,然后初始化它,然后调用fstype的init_fs_context函数。


vfs_parse_fs_string()

    最终会调用到 fc->ops->parse_param


parse_monolithic_mount_data()

    调用 fc->ops->parse_monolithic

        一般会自定义一个选项切割函数,然后调用:

            vfs_parse_monolithic_sep(fc, data, ovl_next_opt);

                最终还是会调用到:vfs_parse_fs_string()


void put_fs_context(struct fs_context *fc)

在最后会调用这个函数来释放fs_context,同时也会释放root dentry

void put_fs_context(struct fs_context *fc)
{
        struct super_block *sb;

        if (fc->root) {
                sb = fc->root->d_sb;
                dput(fc->root);
                fc->root = NULL;
                deactivate_super(sb);
        }

        if (fc->need_free && fc->ops && fc->ops->free)
                fc->ops->free(fc);

        security_free_mnt_opts(&fc->security);
        put_net(fc->net_ns);
        put_user_ns(fc->user_ns);
        put_cred(fc->cred);
        put_fc_log(fc);
        put_filesystem(fc->fs_type);
        kfree(fc->source);
        kfree(fc);      
}

root和root->d_sb会额外拿一个引用计数,然后再释放掉,如果初始化失败。也会走这里,但是实际会释放。

没有异常的时候need_free是true,fc->ops->free()也会执行。

最后会释放fc


4.3 fs/super.c

int vfs_get_tree(struct fs_context *fc)

会调用:

fc->ops->get_tree(),初始化root dentry 和 super block。


卸载overlayfs

# umount merged



总结

fs_context是个短暂存活的对象,只是挂载文件系统的时候分配,挂载完成后释放。

文件系统,在他们模块初始化的时候,注册自己的文件系统,其中有一个init_fs_context接口,这个接口用来初始化fs context。

它需要指定fs context的ops。

ops里面有2个接口,一个是解析参数的接口,一个是get tree接口。

fs context,在get tree之后,就会挂载文件系统。

最后释放fs context。


fs context有2个字段,可以用来存储整个挂载过程的文件系统私有数据:

fc_private

s_fs_info


overlayfs,用fc_priavate存储,ovl_fs_context,在挂载后会释放,主要用来存储配置。

用s_fs_info存储ovl_fs,这个结构体是最后在get tree,会移动到sb的private,用来存储fs的私有数据。



参考:

VFS: Introduce filesystem context. https://lwn.net/Articles/780267

https://www.kernel.org/doc/html/latest/filesystems/mount_api.html


Copyright © linuxdev.cc 2017-2024. Some Rights Reserved.