本文以overlay的挂载过程为例,学习文件系统挂载流程。
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 */ };
挂载一个overlayfs,执行:
# mount -t overlay overlay -olowerdir=/tmp/lower,upperdir=upper,workdir=worker merged
overlayfs定义内核要求的结构体和函数等,等待内核调用。
首先需要注册一个文件系统,定义一个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
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定义了,创建一个文件系统需要的接口。下面我们从内核框架代码来看,这些函数是如何调用的。
在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
系统调用以及相关的处理函数,直到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);
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
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