Linux虚拟文件系统概述

发布于:2025-02-11 ⋅ 阅读:(25) ⋅ 点赞:(0)

介绍

虚拟文件系统(也称为虚拟文件系统交换机)是内核中的软件层,为用户空间程序提供文件系统接口。它还在内核中提供了一个抽象,允许不同的文件系统实现共存。

VFS 系统调用 open(2)、stat(2)、read(2)、write(2)、chmod(2) 等都是从进程上下文中调用的。文件系统锁定在文档Locking中进行了描述。

目录条目缓存 (dcache)

VFS 实现了 open(2)、stat(2)、chmod(2) 和类似的系统调用。传递给它们的路径名参数被 VFS 用于搜索目录条目缓存(也称为 dentry 缓存或 dcache)。这提供了一种非常快速的查找机制,可以将路径名(文件名)转换为特定的 dentry。dentry 位于 RAM 中,永远不会保存到磁盘:它们的存在只是为了提高性能。

目录项缓存旨在查看整个文件空间。由于大多数计算机无法同时将所有目录项放入 RAM 中,因此缓存的某些部分会丢失。为了将路径名解析为目录项,VFS 可能不得不先创建目录项,然后加载 inode。这是通过查找 inode 来完成的。

Inode 对象

单个目录项通常有一个指向 inode 的指针。inode 是文件系统对象,例如常规文件、目录、FIFO 和其他对象。它们位于磁盘上(对于块设备文件系统)或内存中(对于伪文件系统)。磁盘上的 inode 会在需要时复制到内存中,对 inode 的更改会写回到磁盘。单个 inode 可以由多个目录项指向(例如,硬链接就是这样做的)。

要查找 inode,VFS 需要调用父目录 inode 的 lookup() 方法。此方法由 inode 所在的特定文件系统实现安装。一旦 VFS 拥有所需的 dentry(以及 inode),我们就可以执行所有无聊的操作,例如 open(2) 文件或 stat(2) 文件以查看 inode 数据。stat(2) 操作相当简单:一旦 VFS 拥有 dentry,它就会查看 inode 数据并将其中一部分传回用户空间。

文件对象

打开文件需要另一项操作:分配文件结构(这是文件描述符的内核端实现)。新分配的文件结构使用指向 dentry 的指针和一组文件操作成员函数进行初始化。这些是从 inode 数据中获取的。然后调用 open() 文件方法,以便特定的文件系统实现可以完成其工作。您可以看到这是 VFS 执行的另一个切换。文件结构被放入进程的文件描述符表中。

读取、写入和关闭文件(以及其他各种 VFS 操作)是通过使用用户空间文件描述符来获取适当的文件结构,然后调用所需的文件结构方法来执行所需的操作来完成的。只要文件处于打开状态,它就会保持目录项处于使用状态,这反过来意味着 VFS inode 仍在使用中。

注册并挂载文件系统

要注册和取消注册文件系统,请使用以下 API 函数:

#include <linux/fs.h>

extern int register_filesystem(struct file_system_type *);
extern int unregister_filesystem(struct file_system_type *);

传递的 struct file_system_type 描述了您的文件系统。当请求将文件系统挂载到命名空间中的目录时,VFS 将为特定文件系统调用适当的 mount() 方法。by->mount() 返回的树的新 vfsmount 将附加到挂载点,这样当路径名解析到达挂载点时,它将跳转到该 vfsmount 的根目录。

您可以在文件 /proc/filesystems 中看到所有注册到内核的文件系统。

结构体

这描述了文件系统。定义了以下成员:

 struct file_system_type {
        const char *name;
        int fs_flags;
        int (*init_fs_context)(struct fs_context *);
        const struct fs_parameter_spec *parameters;
        struct dentry *(*mount) (struct file_system_type *, int,
                const char *, void *);
        void (*kill_sb) (struct super_block *);
        struct module *owner;
        struct file_system_type * next;
        struct hlist_head fs_supers;

        struct lock_class_key s_lock_key;
        struct lock_class_key s_umount_key;
        struct lock_class_key s_vfs_rename_key;
        struct lock_class_key s_writers_key[SB_FREEZE_LEVELS];

        struct lock_class_key i_lock_key;
        struct lock_class_key i_mutex_key;
        struct lock_class_key invalidate_lock_key;
        struct lock_class_key i_mutex_dir_key;
};

超级块对象

超级块对象代表已挂载的文件系统。struct super_operations()这描述了 VFS 如何操作文件系统的超级块。定义了以下成员:

struct super_operations {
        struct inode *(*alloc_inode)(struct super_block *sb);
        void (*destroy_inode)(struct inode *);
        void (*free_inode)(struct inode *);

        void (*dirty_inode) (struct inode *, int flags);
        int (*write_inode) (struct inode *, struct writeback_control *wbc);
        int (*drop_inode) (struct inode *);
        void (*evict_inode) (struct inode *);
        void (*put_super) (struct super_block *);
        int (*sync_fs)(struct super_block *sb, int wait);
        int (*freeze_super) (struct super_block *sb,
                                enum freeze_holder who);
        int (*freeze_fs) (struct super_block *);
        int (*thaw_super) (struct super_block *sb,
                                enum freeze_wholder who);
        int (*unfreeze_fs) (struct super_block *);
        int (*statfs) (struct dentry *, struct kstatfs *);
        int (*remount_fs) (struct super_block *, int *, char *);
        void (*umount_begin) (struct super_block *);

        int (*show_options)(struct seq_file *, struct dentry *);
        int (*show_devname)(struct seq_file *, struct dentry *);
        int (*show_path)(struct seq_file *, struct dentry *);
        int (*show_stats)(struct seq_file *, struct dentry *);

        ssize_t (*quota_read)(struct super_block *, int, char *, size_t, loff_t);
        ssize_t (*quota_write)(struct super_block *, int, const char *, size_t, loff_t);
        struct dquot **(*get_dquots)(struct inode *);

        long (*nr_cached_objects)(struct super_block *,
                                struct shrink_control *);
        long (*free_cached_objects)(struct super_block *,
                                struct shrink_control *);
};

除非另有说明,否则所有方法的调用均无需持有任何锁。这意味着大多数方法都可以安全阻塞。所有方法都只能从进程上下文调用(即,不是从中断处理程序或下半部分调用)。

Inode 对象

inode 对象代表文件系统内的对象。

struct inode_operations()这描述了 VFS 如何操作文件系统中的 inode。从内核 2.6.22 开始,定义了以下成员:

struct inode_operations {
        int (*create) (struct mnt_idmap *, struct inode *,struct dentry *, umode_t, bool);
        struct dentry * (*lookup) (struct inode *,struct dentry *, unsigned int);
        int (*link) (struct dentry *,struct inode *,struct dentry *);
        int (*unlink) (struct inode *,struct dentry *);
        int (*symlink) (struct mnt_idmap *, struct inode *,struct dentry *,const char *);
        int (*mkdir) (struct mnt_idmap *, struct inode *,struct dentry *,umode_t);
        int (*rmdir) (struct inode *,struct dentry *);
        int (*mknod) (struct mnt_idmap *, struct inode *,struct dentry *,umode_t,dev_t);
        int (*rename) (struct mnt_idmap *, struct inode *, struct dentry *,
                       struct inode *, struct dentry *, unsigned int);
        int (*readlink) (struct dentry *, char __user *,int);
        const char *(*get_link) (struct dentry *, struct inode *,
                                 struct delayed_call *);
        int (*permission) (struct mnt_idmap *, struct inode *, int);
        struct posix_acl * (*get_inode_acl)(struct inode *, int, bool);
        int (*setattr) (struct mnt_idmap *, struct dentry *, struct iattr *);
        int (*getattr) (struct mnt_idmap *, const struct path *, struct kstat *, u32, unsigned int);
        ssize_t (*listxattr) (struct dentry *, char *, size_t);
        void (*update_time)(struct inode *, struct timespec *, int);
        int (*atomic_open)(struct inode *, struct dentry *, struct file *,
                           unsigned open_flag, umode_t create_mode);
        int (*tmpfile) (struct mnt_idmap *, struct inode *, struct file *, umode_t);
        struct posix_acl * (*get_acl)(struct mnt_idmap *, struct dentry *, int);
        int (*set_acl)(struct mnt_idmap *, struct dentry *, struct posix_acl *, int);
        int (*fileattr_set)(struct mnt_idmap *idmap,
                            struct dentry *dentry, struct fileattr *fa);
        int (*fileattr_get)(struct dentry *dentry, struct fileattr *fa);
        struct offset_ctx *(*get_offset_ctx)(struct inode *inode);
};

再次强调,除非另有说明,所有方法的调用都不需要持有任何锁。

地址空间对象

地址空间对象用于对页面缓存中的页面进行分组和管理。它可用于跟踪文件中的页面(或其他内容),还可跟踪文件各部分到进程地址空间的映射。

地址空间可以提供许多不同但相关的服务。其中包括传达内存压力、按地址查找页面以及跟踪标记为“脏”或“写回”的页面。

第一个方法可以独立于其他方法使用。VM 可以尝试写入脏页以清理它们,或者释放干净页以重新使用它们。为此,它可以在脏页上调用 ->writepage 方法,在设置了 private 标志的干净页上调用 ->release_folio。没有 PagePrivate 且没有外部引用的干净页将被释放,而不会通知 address_space。

为了实现此功能,需要使用 lru_cache_add 将页面放置在 LRU 上,并且每次使用该页面时都需要调用 mark_page_active。

页面通常由 ->index 保存在基数树索引中。此树维护有关每个页面的 PG_Dirty 和 PG_Writeback 状态的信息,以便可以快速找到具有这些标志之一的页面。

Dirty 标记主要由 mpage_writepages(默认的 ->writepages 方法)使用。它使用标记来查找要调用 ->writepage 的脏页。如果不使用 mpage_writepages(即地址提供其自己的 ->writepages),则几乎不使用 PAGECACHE_TAG_DIRTY 标记。write_inode_now 和 sync_inode 确实使用它(通过 __sync_single_inode)来检查 ->writepages 是否已成功写出整个 address_space。

Filemapwait 和 sync_page* 函数通过 filemap_fdatawait_range 使用 Writeback 标签来等待所有写回完成。

address_space 处理程序可能会将额外信息附加到页面,通常使用“struct page”中的“private”字段。如果附加了此类信息,则应设置 PG_Private 标志。这将导致各种 VM 例程对 address_space 处理程序进行额外调用以处理该数据。

地址空间充当存储和应用程序之间的中介。数据一次一整页地读入地址空间,并通过复制页面或内存映射页面提供给应用程序。数据由应用程序写入地址空间,然后通常以整页的形式写回存储,但 address_space 对写入大小的控制更精细。

读取过程本质上只需要 ‘read_folio’。写入过程更复杂,使用 write_begin/write_end 或 dirty_folio 将数据写入 address_space,并使用 writepage 和 writepages 将数据写回到存储。

在 address_space 中添加或删除页面受到 inode 的 i_mutex 保护。

当数据写入页面时,应设置 PG_Dirty 标志。它通常保持设置状态,直到 writepage 要求写入。这应该清除 PG_Dirty 并设置 PG_Writeback。在清除 PG_Dirty 后,实际上可以在任何时候写入。一旦确定它是安全的,就会清除 PG_Writeback。

写回使用 writeback_control 结构来指导操作。这为 writepage 和 writepages 操作提供了一些有关写回请求的性质和原因以及执行该请求的约束的信息。它还用于将有关 writepage 或 writepages 请求结果的信息返回给调用者。

处理写回期间的错误

大多数执行缓冲 I/O 的应用程序都会定期调用文件同步调用(fsync、fdatasync、msync 或 sync_file_range),以确保写入的数据已到达后备存储。如果在写回期间出现错误,则它们希望在发出文件同步请求时报告该错误。在一个请求上报告错误后,对同一文件描述符的后续请求应返回 0,除非自上次文件同步以来发生了进一步的写回错误。

理想情况下,内核只会报告已写入但随后无法写回的文件描述错误。但是,通用页面缓存基础结构不会跟踪弄脏每个页面的文件描述,因此无法确定哪些文件描述符应该返回错误。

相反,内核中的通用写回错误跟踪基础结构会将错误报告给 fsync,报告发生错误时打开的所有文件描述上的错误。在有多个写入者的情况下,所有写入者都会在后续的 fsync 中收到错误,即使通过该特定文件描述符完成的所有写入都成功(或者即使该文件描述符上根本没有写入)。

希望使用此基础结构的文件系统应调用mapping_set_error来记录发生错误时address_space中的错误。然后,在file->fsync操作中从页面缓存写回数据后,它们应调用file_check_and_advance_wb_err来确保的错误光标已前进到由后备设备发出的错误流中的正确位置。

struct address_space_operations

这描述了 VFS 如何操纵文件到文件系统中的页面缓存的映射。定义了以下成员:

address_space_operations {
        int (*writepage)(struct page *page, struct writeback_control *wbc);
        int (*read_folio)(struct file *, struct folio *);
        int (*writepages)(struct address_space *, struct writeback_control *);
        bool (*dirty_folio)(struct address_space *, struct folio *);
        void (*readahead)(struct readahead_control *);
        int (*write_begin)(struct file *, struct address_space *mapping,
                           loff_t pos, unsigned len,
                        struct page **pagep, void **fsdata);
        int (*write_end)(struct file *, struct address_space *mapping,
                         loff_t pos, unsigned len, unsigned copied,
                         struct folio *folio, void *fsdata);
        sector_t (*bmap)(struct address_space *, sector_t);
        void (*invalidate_folio) (struct folio *, size_t start, size_t len);
        bool (*release_folio)(struct folio *, gfp_t);
        void (*free_folio)(struct folio *);
        ssize_t (*direct_IO)(struct kiocb *, struct iov_iter *iter);
        int (*migrate_folio)(struct mapping *, struct folio *dst,
                        struct folio *src, enum migrate_mode);
        int (*launder_folio) (struct folio *);

        bool (*is_partially_uptodate) (struct folio *, size_t from,
                                       size_t count);
        void (*is_dirty_writeback)(struct folio *, bool *, bool *);
        int (*error_remove_folio)(struct mapping *mapping, struct folio *);
        int (*swap_activate)(struct swap_info_struct *sis, struct file *f, sector_t *span)
        int (*swap_deactivate)(struct file *);
        int (*swap_rw)(struct kiocb *iocb, struct iov_iter *iter);
};

文件对象

文件对象表示由进程打开的文件。在 POSIX 术语中,这也称为“打开文件描述”。

struct file_operations(文件操作这描述了 VFS 如何操作打开的文件。从内核 4.18 开始,定义了以下成员:

struct file_operations {
        struct module *owner;
        loff_t (*llseek) (struct file *, loff_t, int);
        ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
        ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
        ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
        ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
        int (*iopoll)(struct kiocb *kiocb, bool spin);
        int (*iterate_shared) (struct file *, struct dir_context *);
        __poll_t (*poll) (struct file *, struct poll_table_struct *);
        long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
        long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
        int (*mmap) (struct file *, struct vm_area_struct *);
        int (*open) (struct inode *, struct file *);
        int (*flush) (struct file *, fl_owner_t id);
        int (*release) (struct inode *, struct file *);
        int (*fsync) (struct file *, loff_t, loff_t, int datasync);
        int (*fasync) (int, struct file *, int);
        int (*lock) (struct file *, int, struct file_lock *);
        unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
        int (*check_flags)(int);
        int (*flock) (struct file *, int, struct file_lock *);
        ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
        ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
        int (*setlease)(struct file *, long, struct file_lock **, void **);
        long (*fallocate)(struct file *file, int mode, loff_t offset,
                          loff_t len);
        void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
        unsigned (*mmap_capabilities)(struct file *);
#endif
        ssize_t (*copy_file_range)(struct file *, loff_t, struct file *, loff_t, size_t, unsigned int);
        loff_t (*remap_file_range)(struct file *file_in, loff_t pos_in,
                                   struct file *file_out, loff_t pos_out,
                                   loff_t len, unsigned int remap_flags);
        int (*fadvise)(struct file *, loff_t, loff_t, int);
};

请注意,文件操作由 inode 所在的特定文件系统实现。打开设备节点(字符或块特殊)时,大多数文件系统将调用 VFS 中的特殊支持例程,这些例程将定位所需的设备驱动程序信息。这些支持例程将文件系统文件操作替换为设备驱动程序的操作,然后继续调用文件的新 open() 方法。这就是在文件系统中打开设备文件最终调用设备驱动程序 open() 方法的方式。

挂载选项

解析选项

在挂载和重新挂载时,文件系统会传递一个字符串,其中包含以逗号分隔的挂载选项列表。这些选项可以采用以下任一形式:

选项 选项=值

<linux/parser.h> 头文件定义了一个帮助解析这些选项的 API。有很多关于如何在现有文件系统中使用它的示例。

显示选项

如果文件系统接受挂载选项,则必须定义 show_options() 来显示所有当前活动的选项。规则如下:

  • 必须显示非默认值或其值与默认值不同的选项

  • 可以显示默认启用或具有默认值的选项

仅在挂载助手和内核之间内部使用的选项(例如文件描述符),或仅在挂载期间有效的选项(例如控制日志创建的选项)不受上述规则的约束。

上述规则的根本原因是为了确保能够根据 /proc/mounts 中的信息准确复制挂载(例如,卸载并再次挂载)。


网站公告

今日签到

点亮在社区的每一天
去签到