第13章 虚拟文件系统
虚拟文件系统(VFS)作为内核子系统,为用户空间程序提供了文件和文件系统相关的接口。系统中所有的文件系统不但依赖VFS共存,而且依靠VFS系统协同工作。通过VFS,程序可以利用标准Unix系统调用对不同的文件系统,甚至不同介质上的文件系统进行读写操作:
image.png
一、通用文件系统接口
VFS使得用户可以直接通过open、read、write等系统调用而无需考虑具体文件系统和实际物理介质。
二、文件系统抽象层
为了支持多文件系统,VFS提供了一个通用文件系统模型,该模型囊括了任何文件系统的常用功能集和行为,VFS定义了所有文件系统都支持的、基本的、概念上的接口和数据结构。实际文件系统通过编程提供VFS所期望的抽象接口和数据结构,这样,内核就可以毫不费力地和任何文件系统协同工作,并且这样提供给用户空间的接口,也可以和任何文件系统无缝地连接在一起,完成实际工作。
image.png
三、Unix文件系统
Unix使用了四种和文件系统相关的传统抽象概念:
- 文件
- 目录项:/home/wolfman/butter中,/、home、wolfman、butter都是目录项
- 索引节点
- 安装点
Unix系统将文件的相关信息和文件本身区分开,文件相关信息(也就是元数据)被存储在一个单独的数据结构中,称为索引节点(inode)。文件系统的控制信息存储在超级块中,超级块是一种包含文件系统信息的数据结构。
四、VFS对象及其数据结构
VFS中有四个主要对象类型:
- 超级块对象:代表一个具体的已安装文件系统,包含super_operations操作对象
- 索引节点对象:代表一个具体文件,包含inode_operations操作对象
- 目录项对象:代表一个目录项,是路径组成部分,包含dentry_operations操作对象
- 文件对象:代表由进程打开的文件,包含file_operations操作对象
操作对象作为一个结构体指针来实现,此结构体中包含指向操作其父对象的函数指针。
五、超级块对象
各种文件系统都必须实现超级块对象,该对象用于存储特定文件系统的信息,通常对应于存在磁盘特定扇区的文件系统超级块或文件系统控制块。对于非基于磁盘的文件系统,会在使用现场创建超级块并将其保存到内存中。
在文件系统安装时,文件系统会调用alloc_super()函数从磁盘读取文件系统超级块,并将其信息填充到内存中的超级块对象中。
超级块操作函数表:
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 *);
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
int (*bdev_try_to_free_page)(struct super_block*, struct page*, gfp_t);
long (*nr_cached_objects)(struct super_block *,
struct shrink_control *);
long (*free_cached_objects)(struct super_block *,
struct shrink_control *);
};
六、索引节点对象
索引节点对象包含了内核在操作文件或目录时需要的全部信息。索引节点对象必须在内存中创建,以便文件系统使用。一个索引节点代表文件系统中的一个文件(索引节点仅当文件被访问时才在内存中创建),可以是设备或管道等特殊文件。
索引节点操作:
struct inode_operations {
struct dentry * (*lookup) (struct inode *,struct dentry *, unsigned int);
const char * (*get_link) (struct dentry *, struct inode *, struct delayed_call *);
int (*permission) (struct inode *, int);
struct posix_acl * (*get_acl)(struct inode *, int);
int (*readlink) (struct dentry *, char __user *,int);
int (*create) (struct inode *,struct dentry *, umode_t, bool);
int (*link) (struct dentry *,struct inode *,struct dentry *);
int (*unlink) (struct inode *,struct dentry *);
int (*symlink) (struct inode *,struct dentry *,const char *);
int (*mkdir) (struct inode *,struct dentry *,umode_t);
int (*rmdir) (struct inode *,struct dentry *);
int (*mknod) (struct inode *,struct dentry *,umode_t,dev_t);
int (*rename) (struct inode *, struct dentry *,
struct inode *, struct dentry *, unsigned int);
int (*setattr) (struct dentry *, struct iattr *);
int (*getattr) (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 inode *, struct dentry *, umode_t);
int (*set_acl)(struct inode *, struct posix_acl *, int);
} ____cacheline_aligned;
七、目录项对象
为了查找操作方便,VFS引入了目录项概念。每个dentry代表路径中的一个特定部分。目录项对象没有对应的磁盘数据结构,VFS根据字符串形式的路径名现场创建。
目录项对象有三种有效状态:被使用、未被使用和负状态。
内核将目录项对象缓存在目录项缓存(dcache)中,dcache中包含三部分:
- 被使用的目录项链表
- 最近被使用的双向链表
- 散列表和相应的散列函数用来快速地将给定路径解析为相关目录项对象
目录项操作:
struct dentry_operations {
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 *);
int (*d_delete)(const struct dentry *);
int (*d_init)(struct dentry *);
void (*d_release)(struct dentry *);
void (*d_prune)(struct dentry *);
void (*d_iput)(struct dentry *, struct inode *);
char *(*d_dname)(struct dentry *, char *, int);
struct vfsmount *(*d_automount)(struct path *);
int (*d_manage)(const struct path *, bool);
struct dentry *(*d_real)(struct dentry *, const struct inode *);
} ____cacheline_aligned;
八、文件对象
文件对象表示进程已打开的文件,因为多个进程可以同时打开和操作同一个文件,所以同个文件也可能存在多个对应的文件对象,但对应的索引节点和目录项对象是唯一的。文件对象没有对应的磁盘数据。
文件对象操作:
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;