ext4挂载
- 在linux 5.x的内核中,实际文件系统的挂载采用新的挂载API,引入了
struct fs_context
用于内部文件系统挂载的信息。 - 应用端发起mount命令,进入mount系统调用,执行do_mount的函数
代码语言:javascript
复制// vfs层保留该小节需要的核心字段
struct super_block {
// 文件系统类型
struct file_system_type *s_type;
// 文件系统super_block的操作函数
const struct super_operations *s_op;
// 根目录
struct dentry *s_root;
// 每个实际文件系统的超级块,比如ext4,这里就会存储ext4的super block
void *s_fs_info; /* Filesystem private info */
} __randomize_layout;
// 实际的ext4文件系统,super_block中的s_type中存储了ext4_fs_type的信息
static struct file_system_type ext4_fs_type = {
.owner = THIS_MODULE,
.name = "ext4",
// vfs层对应的mout的时间函数
.mount = ext4_mount,
.kill_sb = kill_block_super,
.fs_flags = FS_REQUIRES_DEV,
};
// ext4作为kernel module形式注入kernel,在ext4_init_fs注册文件系统
static int __init ext4_init_fs(void)
{
register_filesystem(&ext4_fs_type);
}
// 应用端发出mount命令挂载的系统调用
COMPAT_SYSCALL_DEFINE5(mount, const char __user *, dev_name,
const char __user *, dir_name,
const char __user *, type, compat_ulong_t, flags,
const void __user *, data)
{
// 实际是调用内核的do_mount函数来完成mount操作
retval = do_mount(kernel_dev, dir_name, kernel_type, flags, options);
}
long do_mount(const char *dev_name, const char __user *dir_name,
const char *type_page, unsigned long flags, void *data_page)
{
do_new_mount(&path, type_page, sb_flags, mnt_flags,
dev_name, data_page);
}
static int do_new_mount(struct path *path, const char *fstype, int sb_flags,
int mnt_flags, const char *name, void *data)
{
// 根据文件系统类型名称获取文件系统的内核module.这里是ext4会根据ext4来获取ext4 文件系统的kernel module
type = get_fs_type(fstype);
// 为此次文件系统挂载创建文件系统挂载上下文
fc = fs_context_for_mount(type, sb_flags);
// 在这里实际调用文件的mount函数赋值,比如这是ext4_mount.
err = vfs_get_tree(fc);
// 内核开始进行挂载
err = do_new_mount_fc(fc, path, mnt_flags);
}
// 初始化一个fs_context,同时给fs_context中的struct dentry *root,这个root包含了dentry和super_block.其中root是通过 legacy_get_tree中调用具体文件系统的mount函数,这里是调用ext4_mount函数,初始化root dentry
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);
}
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)
{
// ext4中ext4_fs_type并没有的函数指针为空
init_fs_context = fc->fs_type->init_fs_context;
if (!init_fs_context)
// 这里默认init_fs_context
init_fs_context = legacy_init_fs_context;
// legacy_init_fs_context调用,fs_context->ops = &legacy_fs_context_ops;这里legacy_fs_context_ops定义了函数指针表。这里谈到的是vfs层的mount系统调用和实际的文件系统的mount有有关的就是legacy_fs_context_ops.get_tree函数
ret = init_fs_context(fc);
}
// 新内核引入的fs_context_operations,vfs层的mount是是如何和真实的文件系统mount函数挂钩,这里就是调用fs_context_operations.get_tree实现
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,
//这里是调用真是文件系统的ext4_mount函数
.get_tree = legacy_get_tree,
.reconfigure = legacy_reconfigure,
};
// 在这里调用真实文件系统的ext4的mount函数入口
int vfs_get_tree(struct fs_context *fc)
{
// get_tree指向legacy_get_tree函数
error = fc->ops->get_tree(fc);
}
// do_mount函数会调用do_new_mount,而do_new_mount函数会调用do_new_mount_fc来针对每个挂载的文件系统创建struct mount结构,然后再这个函数里面调用do_add_mount完成ext4文件系统的挂载
static int do_new_mount_fc(struct fs_context *fc, struct path *mountpoint,
unsigned int mnt_flags)
{
struct vfsmount *mnt;
struct super_block *sb = fc->root->d_sb;
mnt = vfs_create_mount(fc);
mnt_warn_timestamp_expiry(mountpoint, mnt);
error = do_add_mount(real_mount(mnt), mountpoint, mnt_flags);
if (error < 0)
mntput(mnt);
return error;
}
// 这里是挂载实际文件系统的mount的函数调用
static int legacy_get_tree(struct fs_context *fc)
{
struct legacy_fs_context *ctx = fc->fs_private;
struct super_block *sb;
struct dentry *root;
root = fc->fs_type->mount(fc->fs_type, fc->sb_flags,
fc->source, ctx->legacy_data);
if (IS_ERR(root))
return PTR_ERR(root);
sb = root->d_sb;
BUG_ON(!sb);
fc->root = root;
return 0;
}
ext4中super_block初始化
- os提供vfs层来建立用户态文件操作和真实磁盘文件系统之间关系。vfs层的super_block是通过工厂模式设计模式来整合真实文件系统的super_block.
- ext4的super_block,初始化是在用户态发起mount时候进行初始化。vfs中文件创建、删除、写入都会在vfs层的inode操作,最终会进入实际文件系统的inode操作
代码语言:javascript
复制// 实际文件系统的super_block的操作函数
static const struct super_operations ext4_sops = {
// ext4文件系统inode申请
.alloc_inode = ext4_alloc_inode,
// ext4文件系统inode释放
.free_inode = ext4_free_in_core_inode,
.destroy_inode = ext4_destroy_inode,
// inode的写入操作函数
.write_inode = ext4_write_inode,
.dirty_inode = ext4_dirty_inode,
.drop_inode = ext4_drop_inode,
.evict_inode = ext4_evict_inode,
.put_super = ext4_put_super,
.sync_fs = ext4_sync_fs,
.freeze_fs = ext4_freeze,
.unfreeze_fs = ext4_unfreeze,
.statfs = ext4_statfs,
.remount_fs = ext4_remount,
.show_options = ext4_show_options,
#ifdef CONFIG_QUOTA
.quota_read = ext4_quota_read,
.quota_write = ext4_quota_write,
.get_dquots = ext4_get_dquots,
#endif
.bdev_try_to_free_page = bdev_try_to_free_page,
};
// ext4 super_block的初始化函数
static int ext4_fill_super(struct super_block *sb, void *data, int silent)
{
sb->s_op = &ext4_sops;
}
// 挂载时候进行ext4_mount函数
static struct dentry *ext4_mount(struct file_system_type *fs_type, int flags,
const char *dev_name, void *data)
{
return mount_bdev(fs_type, flags, dev_name, data, ext4_fill_super);
}
open文件流程间接
- vfs层包含了是实际文件系统的内存影像
- 用户进程调用open函数,传入文件名称、打开文件的flags、文件的权限等信息,进入内核态的do_sys_open函数
- 进入do_sys_open函数,首先是执行get_unused_fd_flags从当前的进程中申请未被使用的文件描述符
- 其次是构建一个struct file结构,该结构是每个进程打开的fd关联的文件。调用了do_filp_open函数,该函数根据文件名称和文件打开的flags。
- 如果正常执行 fd_install 函数把fd和当前进程的打开的struct file数组关联起来,把索引为fd 的struct file数组和struct file进行关联
- 最后释放了内核态的filename,这个也是从用户态参数初始化而来
代码语言:javascript
复制// 系统调用的入口,传入文件名称、文件打开模式、文件mode,进入内核态
SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)
{
if (force_o_largefile())
flags |= O_LARGEFILE;
return do_sys_open(AT_FDCWD, filename, flags, mode);
}
long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode)
{
struct open_flags op;
int fd = build_open_flags(flags, mode, &op);
struct filename *tmp;
if (fd)
return fd;
tmp = getname(filename);
if (IS_ERR(tmp))
return PTR_ERR(tmp);
// 从当前进程文件描述符中申请一个fd,这个fd的本质就是该进程的struct file数组的下标
fd = get_unused_fd_flags(flags);
if (fd >= 0) {
// 根据文件路径和flags构建一个struct file实例
struct file *f = do_filp_open(dfd, tmp, &op);
// 如果返回是无效的指针
if (IS_ERR(f)) {
// 把申请的fd放回去
put_unused_fd(fd);
fd = PTR_ERR(f);
} else {
fsnotify_open(f);
// 把对应的fd和struct file数组进行关联
fd_install(fd, f);
}
}
putname(tmp);
return fd;
}
// do_filp_open解析文件路径,返回进程的打开的struct file
open文件执行函数说明
- getname: 拷贝用户态传过来的文件路径
- get_unused_fd_flags:从当前进程中获取未被使用的文件描述符
- do_filp_open:解析路径并返回进程打开的文件struct file
- path_openat:路径查找函数
- path_init:设置路径的查找的开始位置
- link_path_walk:目录逐层查找
- do_last:查找dcache,通过所在目录的目录项对象的inode operation为相关文件创建新inode
- lookup_fast:从dcache中找到dentry
- lookup_open:通过所在目录的目录项对象的inode operation为相关文件创建新inode