虚拟文件系统为用户空间程序提供通用的文件和文件系统相关的接口,通过虚拟文件系统,程序可以通过标准的 linux 系统调用对不同的文件系统,甚至不同介质上的文件系统进行读写操作。

1. 结构体成员
1.1 超级块对象和操作
超级块代表了一个已挂载的具体文件系统的全局元数据,各种文件系统都必须实现超级块对象,用于存储特定文件系统的信息。
c
struct super_block {
struct list_head s_list; /* 把当前超级块挂载到全局的超级块链表 */
dev_t s_dev; /* 设备标识符 */
unsigned char s_blocksize_bits; /* 以位为单位的块大小 */
unsigned long s_blocksize; /* 已字节为单位的块大小 */
loff_t s_maxbytes; /* 文件大小上限 */
struct file_system_type *s_type; /* 文件系统类型 */
const struct super_operations *s_op; /* 超级快方法 */
const struct dquot_operations *dq_op; /* 磁盘限额方法 */
const struct quotactl_ops *s_qcop; /* 限额控制方法 */
const struct export_operations *s_export_op; /* 导出方法 */
unsigned long s_flags; /* 挂载标志 */
unsigned long s_iflags; /* 内部状态标志 */
unsigned long s_magic; /* 文件系统的幻数 */
struct dentry *s_root; /* 目录挂载点 */
struct rw_semaphore s_umount; /* 卸载信号量 */
int s_count; /* 超级快引用计数 */
atomic_t s_active; /* 活动引用计数 */
struct hlist_bl_head s_roots; /* 目录挂载点 */
struct list_head s_mounts; /* 引用该超级块的所有挂载点的链表头 */
struct block_device *s_bdev; /* 指向超级块对应的块设备 */
struct backing_dev_info *s_bdi;
struct mtd_info *s_mtd; /* 指向超级块对应的MTD设备 */
struct hlist_node s_instances; /* 用于组织同一文件系统类型的所有超级块实例 */
unsigned int s_quota_types; /* 支持哪些配额类型 */
struct quota_info s_dquot; /* quota运行管理状态 */
struct sb_writers s_writers;
void *s_fs_info; /* 文件系统特殊信息 */
u32 s_time_gran; /* 时间戳颗粒度 */
time64_t s_time_min;
time64_t s_time_max;
#ifdef CONFIG_FSNOTIFY
__u32 s_fsnotify_mask;
struct fsnotify_mark_connector __rcu *s_fsnotify_marks;
#endif
char s_id[32]; /* Informational name */
uuid_t s_uuid; /* UUID */
unsigned int s_max_links;
fmode_t s_mode;
struct mutex s_vfs_rename_mutex;
const char *s_subtype;
const struct dentry_operations *s_d_op;
int cleancache_poolid;
struct shrinker s_shrink;
atomic_long_t s_remove_count;
atomic_long_t s_fsnotify_connectors;
int s_readonly_remount;
errseq_t s_wb_err;
struct workqueue_struct *s_dio_done_wq;
struct hlist_head s_pins;
struct user_namespace *s_user_ns;
struct list_lru s_dentry_lru;
struct list_lru s_inode_lru;
struct rcu_head rcu;
struct work_struct destroy_work;
struct mutex s_sync_lock;
int s_stack_depth;
spinlock_t s_inode_list_lock ____cacheline_aligned_in_smp;
struct list_head s_inodes;
spinlock_t s_inode_wblist_lock;
struct list_head s_inodes_wb;
} __randomize_layout;
超级块对象中最重要的一个域是s_op,它是超级块的操作函数表。
c
struct super_operations {
/* 为该文件系统分配inode对象 */
struct inode *(*alloc_inode)(struct super_block *sb);
/* 销毁inode前的清理回调 */
void (*destroy_inode)(struct inode *);
/* 真正释放inode内存 */
void (*free_inode)(struct inode *);
/* 当inode被标记为脏时 */
void (*dirty_inode) (struct inode *, int flags);
/* 将inode写回存储介质 */
int (*write_inode) (struct inode *, struct writeback_control *wbc);
/* 决定inode是否应立即遗弃 */
int (*drop_inode) (struct inode *);
/* 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 *);
/* 文件系统内部真正执行冻结 */
int (*freeze_fs) (struct super_block *);
/* 超级块解冻的上层处理 */
int (*thaw_super) (struct super_block *);
/* 文件系统内部真正执行解冻 */
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 *);
#ifdef CONFIG_QUOTA
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 *);
#endif
long (*nr_cached_objects)(struct super_block *,
struct shrink_control *);
long (*free_cached_objects)(struct super_block *,
struct shrink_control *);
};
当文件系统被 mount 时,内核会从磁盘读取元数据,并根据这些信息在内存中创建一个struct super_block对象。但并非所有文件系统都有元数据(比如proc或sysfs,它们的超级块信息是动态生成的)。
1.2 索引节点对象和操作
索引节点包含了文件的元数据,即除了文件名和实际内容之外的所有信息。文件名存储在目录项中,而不是 inode 中,意味同一个 inode 可以对应不同的文件名(硬链接原理)。
c
struct inode {
umode_t i_mode; /* 文件类型和权限位 */
unsigned short i_opflags; /* 操作标志位 */
kuid_t i_uid; /* 使用者的ID */
kgid_t i_gid; /* 使用组的ID */
unsigned int i_flags; /* 属性标志位 */
#ifdef CONFIG_FS_POSIX_ACL
struct posix_acl *i_acl;
struct posix_acl *i_default_acl;
#endif
const struct inode_operations *i_op; /* inode操作表 */
struct super_block *i_sb; /* 所属超级块 */
struct address_space *i_mapping;
#ifdef CONFIG_SECURITY
void *i_security;
#endif
unsigned long i_ino; /* inode号 */
union { /* 硬链接计数 */
const unsigned int i_nlink;
unsigned int __i_nlink;
};
dev_t i_rdev; /* 设备号 */
loff_t i_size; /* 文件大小 */
struct timespec64 i_atime; /* 最近访问时间 */
struct timespec64 i_mtime; /* 最近修改时间 */
struct timespec64 i_ctime; /* 状态变化时间 */
spinlock_t i_lock; /* i_blocks, i_bytes, maybe i_size */
unsigned short i_bytes;
u8 i_blkbits;
u8 i_write_hint;
blkcnt_t i_blocks;
#ifdef __NEED_I_SIZE_ORDERED
seqcount_t i_size_seqcount;
#endif
unsigned long i_state; /* 当前内部状态位 */
struct rw_semaphore i_rwsem;
unsigned long dirtied_when; /* jiffies of first dirtying */
unsigned long dirtied_time_when;
struct hlist_node i_hash;
struct list_head i_io_list; /* backing dev IO list */
#ifdef CONFIG_CGROUP_WRITEBACK
struct bdi_writeback *i_wb; /* the associated cgroup wb */
int i_wb_frn_winner;
u16 i_wb_frn_avg_time;
u16 i_wb_frn_history;
#endif
struct list_head i_lru; /* inode LRU list */
struct list_head i_sb_list;
struct list_head i_wb_list; /* backing dev writeback list */
union {
struct hlist_head i_dentry;
struct rcu_head i_rcu;
};
atomic64_t i_version; /* inode版本号 */
atomic64_t i_sequence; /* see futex */
atomic_t i_count; /* 引用计数 */
atomic_t i_dio_count;
atomic_t i_writecount; /* 写打开计数 */
#if defined(CONFIG_IMA) || defined(CONFIG_FILE_LOCKING)
atomic_t i_readcount; /* struct files open RO */
#endif
union {
const struct file_operations *i_fop; /* 文件操作表 */
void (*free_inode)(struct inode *);
};
struct file_lock_context *i_flctx;
struct address_space i_data;
struct list_head i_devices;
union {
struct pipe_inode_info *i_pipe;
struct cdev *i_cdev;
char *i_link;
unsigned i_dir_seq;
};
__u32 i_generation;
#ifdef CONFIG_FSNOTIFY
__u32 i_fsnotify_mask; /* all events this inode cares about */
struct fsnotify_mark_connector __rcu *i_fsnotify_marks;
#endif
#ifdef CONFIG_FS_ENCRYPTION
struct fscrypt_info *i_crypt_info;
#endif
#ifdef CONFIG_FS_VERITY
struct fsverity_info *i_verity_info;
#endif
void *i_private; /* fs or device private pointer */
} __randomize_layout;
struct inode_operations描述了 vfs 操作索引节点的所有方法,这些方法又文件系统实现。
c
struct inode_operations {
/* 在目录inode下查找名字对应的子dentry */
struct dentry * (*lookup) (struct inode *,struct dentry *, unsigned int);
/* 获取符号链接目标 */
const char * (*get_link) (struct dentry *, struct inode *, struct delayed_call *);
/* 检查当前进程对inode的访问权限 */
int (*permission) (struct user_namespace *, struct inode *, int);
struct posix_acl * (*get_acl)(struct inode *, int, bool);
int (*readlink) (struct dentry *, char __user *,int);
/* 在目录下创建普通文件 */
int (*create) (struct user_namespace *, struct inode *,struct dentry *,
umode_t, bool);
/* 创建硬链接 */
int (*link) (struct dentry *,struct inode *,struct dentry *);
/* 删除目录项对应的文件链接 */
int (*unlink) (struct inode *,struct dentry *);
/* 创建符号链接 */
int (*symlink) (struct user_namespace *, struct inode *,struct dentry *,
const char *);
/* 创建目录 */
int (*mkdir) (struct user_namespace *, struct inode *,struct dentry *,
umode_t);
/* 删除目录 */
int (*rmdir) (struct inode *,struct dentry *);
/* 创建设备节点、fifo、socket等特殊文件 */
int (*mknod) (struct user_namespace *, struct inode *,struct dentry *,
umode_t,dev_t);
/* 重命名或移动目录项 */
int (*rename) (struct user_namespace *, struct inode *, struct dentry *,
struct inode *, struct dentry *, unsigned int);
int (*setattr) (struct user_namespace *, struct dentry *,
struct iattr *);
int (*getattr) (struct user_namespace *, const struct path *,
struct kstat *, u32, unsigned int);
ssize_t (*listxattr) (struct dentry *, char *, size_t);
int (*fiemap)(struct inode *, struct fiemap_extent_info *, u64 start,
u64 len);
int (*update_time)(struct inode *, struct timespec64 *, int);
int (*atomic_open)(struct inode *, struct dentry *,
struct file *, unsigned open_flag,
umode_t create_mode);
int (*tmpfile) (struct user_namespace *, struct inode *,
struct dentry *, umode_t);
int (*set_acl)(struct user_namespace *, struct inode *,
struct posix_acl *, int);
int (*fileattr_set)(struct user_namespace *mnt_userns,
struct dentry *dentry, struct fileattr *fa);
int (*fileattr_get)(struct dentry *dentry, struct fileattr *fa);
} ____cacheline_aligned;
1.3 目录项对象和操作
struct dentry目录项是连接文件名和索引节点的桥梁,inode 只有编号,并不包含文件名。为了让用户通过文件路径找到文件,内核引入了目录项。
目录项有3种状态:未被使用、被使用、负状态。一个未被使用的 dentry 对应一个有效的 inode,但是 vfs 未使用它(dentry->d_lockref.count == 0)。 一个被使用 dentry 对应一个有效的 inode ,并且存在一个或多个使用者。一个负状态的 dentry没有对应有效的 inode,但是 dentry 仍然保留着。
当一个进程尝试打开并读取一个不存在的文件,open()系统调用不断的返回ENOENT,直到内核构建了这个路径,遍历磁盘上的目录结构体并检查这个文件确实不存在为止。
构建路径:当执行open("/var/log/missing.txt")时,内核并不知道 missing.txt 是否存在,它必须执行以下构建步骤:
- 找到
/的 dentry 和 inode。 - 逐级查找
- 查找 var:在
/的 inode 指向的数据块中搜索 var ,如果找到了,就在内存中创建一个 var 的 dentry。 - 查找 log:进入 var 的 inode ,重复上述过程,在内存中创建 log 的 dentry 。
- 查找 missing.txt:进入 log 的 inode,搜索其目录项列表。
- 查找 var:在
- 内核遍历了 log 目录在磁盘上的所有记录,发现没有叫 missing.txt 的项。
如果没有负状态 dentry,下一次另外一个进程请求同一个不存在的文件时,内核又得重复上面的步骤。由于磁盘 I/O 的延迟远高于内存,保留该 dentry 能够加快查询速度。
vfs 遍历路径名中所有的元素并将它们逐个的解析层目录项对象是一件费时的工作,内核将目录项对象缓存在目录项缓存中,目录项缓存分为 3 部分:
- 被使用的目录项链表,该链表通过提供 inode 的 i_dentry 项连接相关的索引节点。
- 最近被使用的双向链表,该链表包含未被使用和负状态的目录项对象,并且该链表总是在头部插入目录项,所以链头节点的数据总是比链尾的数据要新。当内核要删除节点回收内存时,会从链尾删除,因为它们在最近内再次被使用的可能性最小。
- 散链表和相应的散列函数用来快速的给定路径解析未相关目录对象。
c
struct dentry {
unsigned int d_flags; /* 状态标志位 */
seqcount_spinlock_t d_seq; /* per dentry seqlock */
struct hlist_bl_node d_hash; /* lookup hash list */
struct dentry *d_parent; /* 父目录dentry,组织目录树层级关系 */
struct qstr d_name; /* 目录项名字 */
struct inode *d_inode; /* 目录项对应的inode */
unsigned char d_iname[DNAME_INLINE_LEN]; /* 小名字内联缓冲区 */
struct lockref d_lockref; /* per-dentry lock and refcount */
const struct dentry_operations *d_op; /* 目录项操作函数集 */
struct super_block *d_sb; /* The root of the dentry tree */
unsigned long d_time; /* used by d_revalidate */
void *d_fsdata; /* fs-specific data */
union {
struct list_head d_lru; /* LRU list */
wait_queue_head_t *d_wait; /* in-lookup ones only */
};
struct list_head d_child; /* 作为子目录项时,挂载到父目录子项链表 */
struct list_head d_subdirs; /* 子目录项链表 */
union {
struct hlist_node d_alias; /* inode alias list */
struct hlist_bl_node d_in_lookup_hash; /* only for in-lookup ones */
struct rcu_head d_rcu;
} d_u;
} __randomize_layout;
struct dentry_operations说明了 vfs 操作目录项的所有方法。
c
struct dentry_operations {
/* 重新验证debtry是否有效 */
int (*d_revalidate)(struct dentry *, unsigned int);
int (*d_weak_revalidate)(struct dentry *, unsigned int);
int (*d_hash)(const struct dentry *, struct qstr *);
/* 比较名字是否相等 */
int (*d_compare)(const struct dentry *,
unsigned int, const char *, const struct qstr *);
/* 决定是否删除dentry */
int (*d_delete)(const struct dentry *);
/* dentry初始化时调用 */
int (*d_init)(struct dentry *);
/* dentry最终释放前调用 */
void (*d_release)(struct dentry *);
/* dentry被释放裁剪时调用 */
void (*d_prune)(struct dentry *);
/* 指定如何释放与dentry关联的inode */
void (*d_iput)(struct dentry *, struct inode *);
/* 生成dentry的显示名字 */
char *(*d_dname)(struct dentry *, char *, int);
/* 访问该dentry时自动挂载 */
struct vfsmount *(*d_automount)(struct path *);
int (*d_manage)(const struct path *, bool);
struct dentry *(*d_real)(struct dentry *, const struct inode *);
} ____cacheline_aligned;
1.4 文件对象和操作
文件对象表示进程已打开的文件。
c
struct file {
union { /* 延迟销毁链表 */
struct llist_node fu_llist;
struct rcu_head fu_rcuhead;
} f_u;
struct path f_path; /* 当前文件对应的路径对象 */
struct inode *f_inode; /* 当前文件对应的inode */
const struct file_operations *f_op; /* 文件操作函数集 */
spinlock_t f_lock;
enum rw_hint f_write_hint;
atomic_long_t f_count; /* 引用计数 */
unsigned int f_flags; /* 打开标志 */
fmode_t f_mode; /* 文件模式标志 */
struct mutex f_pos_lock;
loff_t f_pos; /* 文件偏移量 */
struct fown_struct f_owner;
const struct cred *f_cred;
struct file_ra_state f_ra;
u64 f_version;
#ifdef CONFIG_SECURITY
void *f_security;
#endif
void *private_data;
#ifdef CONFIG_EPOLL
struct hlist_head *f_ep;
#endif /* #ifdef CONFIG_EPOLL */
struct address_space *f_mapping;
errseq_t f_wb_err;
errseq_t f_sb_err; /* for syncfs */
} __randomize_layout
__attribute__((aligned(4))); /* lest something weird decides that 2 is OK */
struct file_handle {
__u32 handle_bytes;
int handle_type;
unsigned char f_handle[];
};
struct file_operations代表文件对象的操作,具体的文件系统可以为每一种操作做专门的实现,也可以使用通用的操作。
c
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) (struct file *, struct dir_context *);
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 *);
unsigned long mmap_supported_flags;
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 *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
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);
} __randomize_layout;