VFS虚拟文件系统
虚拟文件系统为用户空间程序提供通用的文件和文件系统相关的接口,通过虚拟文件系统,程序可以通过标准的 linux 系统调用对不同的文件系统,甚至不同介质上的文件系统进行读写操作。
1. 结构体成员
1.1 超级块对象和操作
超级块代表了一个已挂载的具体文件系统的全局元数据,各种文件系统都必须实现超级块对象,用于存储特定文件系统的信息。
structsuper_block{structlist_head s_list;/* 把当前超级块挂载到全局的超级块链表 */dev_t s_dev;/* 设备标识符 */unsignedchar s_blocksize_bits;/* 以位为单位的块大小 */unsignedlong s_blocksize;/* 已字节为单位的块大小 */loff_t s_maxbytes;/* 文件大小上限 */structfile_system_type*s_type;/* 文件系统类型 */conststructsuper_operations*s_op;/* 超级快方法 */conststructdquot_operations*dq_op;/* 磁盘限额方法 */conststructquotactl_ops*s_qcop;/* 限额控制方法 */conststructexport_operations*s_export_op;/* 导出方法 */unsignedlong s_flags;/* 挂载标志 */unsignedlong s_iflags;/* 内部状态标志 */unsignedlong s_magic;/* 文件系统的幻数 */structdentry*s_root;/* 目录挂载点 */structrw_semaphore s_umount;/* 卸载信号量 */int s_count;/* 超级快引用计数 */atomic_t s_active;/* 活动引用计数 */structhlist_bl_head s_roots;/* 目录挂载点 */structlist_head s_mounts;/* 引用该超级块的所有挂载点的链表头 */structblock_device*s_bdev;/* 指向超级块对应的块设备 */structbacking_dev_info*s_bdi;structmtd_info*s_mtd;/* 指向超级块对应的MTD设备 */structhlist_node s_instances;/* 用于组织同一文件系统类型的所有超级块实例 */unsignedint s_quota_types;/* 支持哪些配额类型 */structquota_info s_dquot;/* quota运行管理状态 */structsb_writers s_writers;void*s_fs_info;/* 文件系统特殊信息 */ u32 s_time_gran;/* 时间戳颗粒度 */time64_t s_time_min;time64_t s_time_max;#ifdefCONFIG_FSNOTIFY __u32 s_fsnotify_mask;structfsnotify_mark_connector __rcu *s_fsnotify_marks;#endifchar s_id[32];/* Informational name */uuid_t s_uuid;/* UUID */unsignedint s_max_links;fmode_t s_mode;structmutex s_vfs_rename_mutex;constchar*s_subtype;conststructdentry_operations*s_d_op;int cleancache_poolid;structshrinker s_shrink;atomic_long_t s_remove_count;atomic_long_t s_fsnotify_connectors;int s_readonly_remount;errseq_t s_wb_err;structworkqueue_struct*s_dio_done_wq;structhlist_head s_pins;structuser_namespace*s_user_ns;structlist_lru s_dentry_lru;structlist_lru s_inode_lru;structrcu_head rcu;structwork_struct destroy_work;structmutex s_sync_lock;int s_stack_depth;spinlock_t s_inode_list_lock ____cacheline_aligned_in_smp;structlist_head s_inodes;spinlock_t s_inode_wblist_lock;structlist_head s_inodes_wb;} __randomize_layout;超级块对象中最重要的一个域是s_op,它是超级块的操作函数表。
structsuper_operations{/* 为该文件系统分配inode对象 */structinode*(*alloc_inode)(structsuper_block*sb);/* 销毁inode前的清理回调 */void(*destroy_inode)(structinode*);/* 真正释放inode内存 */void(*free_inode)(structinode*);/* 当inode被标记为脏时 */void(*dirty_inode)(structinode*,int flags);/* 将inode写回存储介质 */int(*write_inode)(structinode*,structwriteback_control*wbc);/* 决定inode是否应立即遗弃 */int(*drop_inode)(structinode*);/* inode被回收时调用 */void(*evict_inode)(structinode*);/* 卸载超级块时调用 */void(*put_super)(structsuper_block*);/* 同步整个文件系统 */int(*sync_fs)(structsuper_block*sb,int wait);/* 超级块冻结的上层处理 */int(*freeze_super)(structsuper_block*);/* 文件系统内部真正执行冻结 */int(*freeze_fs)(structsuper_block*);/* 超级块解冻的上层处理 */int(*thaw_super)(structsuper_block*);/* 文件系统内部真正执行解冻 */int(*unfreeze_fs)(structsuper_block*);int(*statfs)(structdentry*,structkstatfs*);int(*remount_fs)(structsuper_block*,int*,char*);void(*umount_begin)(structsuper_block*);int(*show_options)(structseq_file*,structdentry*);int(*show_devname)(structseq_file*,structdentry*);int(*show_path)(structseq_file*,structdentry*);int(*show_stats)(structseq_file*,structdentry*);#ifdefCONFIG_QUOTAssize_t(*quota_read)(structsuper_block*,int,char*,size_t,loff_t);ssize_t(*quota_write)(structsuper_block*,int,constchar*,size_t,loff_t);structdquot**(*get_dquots)(structinode*);#endiflong(*nr_cached_objects)(structsuper_block*,structshrink_control*);long(*free_cached_objects)(structsuper_block*,structshrink_control*);};当文件系统被 mount 时,内核会从磁盘读取元数据,并根据这些信息在内存中创建一个struct super_block对象。但并非所有文件系统都有元数据(比如proc或sysfs,它们的超级块信息是动态生成的)。
1.2 索引节点对象和操作
索引节点包含了文件的元数据,即除了文件名和实际内容之外的所有信息。文件名存储在目录项中,而不是 inode 中,意味同一个 inode 可以对应不同的文件名(硬链接原理)。
structinode{umode_t i_mode;/* 文件类型和权限位 */unsignedshort i_opflags;/* 操作标志位 */kuid_t i_uid;/* 使用者的ID */kgid_t i_gid;/* 使用组的ID */unsignedint i_flags;/* 属性标志位 */#ifdefCONFIG_FS_POSIX_ACLstructposix_acl*i_acl;structposix_acl*i_default_acl;#endifconststructinode_operations*i_op;/* inode操作表 */structsuper_block*i_sb;/* 所属超级块 */structaddress_space*i_mapping;#ifdefCONFIG_SECURITYvoid*i_security;#endifunsignedlong i_ino;/* inode号 */union{/* 硬链接计数 */constunsignedint i_nlink;unsignedint __i_nlink;};dev_t i_rdev;/* 设备号 */loff_t i_size;/* 文件大小 */structtimespec64 i_atime;/* 最近访问时间 */structtimespec64 i_mtime;/* 最近修改时间 */structtimespec64 i_ctime;/* 状态变化时间 */spinlock_t i_lock;/* i_blocks, i_bytes, maybe i_size */unsignedshort i_bytes; u8 i_blkbits; u8 i_write_hint;blkcnt_t i_blocks;#ifdef__NEED_I_SIZE_ORDEREDseqcount_t i_size_seqcount;#endifunsignedlong i_state;/* 当前内部状态位 */structrw_semaphore i_rwsem;unsignedlong dirtied_when;/* jiffies of first dirtying */unsignedlong dirtied_time_when;structhlist_node i_hash;structlist_head i_io_list;/* backing dev IO list */#ifdefCONFIG_CGROUP_WRITEBACKstructbdi_writeback*i_wb;/* the associated cgroup wb */int i_wb_frn_winner; u16 i_wb_frn_avg_time; u16 i_wb_frn_history;#endifstructlist_head i_lru;/* inode LRU list */structlist_head i_sb_list;structlist_head i_wb_list;/* backing dev writeback list */union{structhlist_head i_dentry;structrcu_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;/* 写打开计数 */#ifdefined(CONFIG_IMA)||defined(CONFIG_FILE_LOCKING)atomic_t i_readcount;/* struct files open RO */#endifunion{conststructfile_operations*i_fop;/* 文件操作表 */void(*free_inode)(structinode*);};structfile_lock_context*i_flctx;structaddress_space i_data;structlist_head i_devices;union{structpipe_inode_info*i_pipe;structcdev*i_cdev;char*i_link;unsigned i_dir_seq;}; __u32 i_generation;#ifdefCONFIG_FSNOTIFY __u32 i_fsnotify_mask;/* all events this inode cares about */structfsnotify_mark_connector __rcu *i_fsnotify_marks;#endif#ifdefCONFIG_FS_ENCRYPTIONstructfscrypt_info*i_crypt_info;#endif#ifdefCONFIG_FS_VERITYstructfsverity_info*i_verity_info;#endifvoid*i_private;/* fs or device private pointer */} __randomize_layout;struct inode_operations描述了 vfs 操作索引节点的所有方法,这些方法又文件系统实现。
structinode_operations{/* 在目录inode下查找名字对应的子dentry */structdentry*(*lookup)(structinode*,structdentry*,unsignedint);/* 获取符号链接目标 */constchar*(*get_link)(structdentry*,structinode*,structdelayed_call*);/* 检查当前进程对inode的访问权限 */int(*permission)(structuser_namespace*,structinode*,int);structposix_acl*(*get_acl)(structinode*,int, bool);int(*readlink)(structdentry*,char __user *,int);/* 在目录下创建普通文件 */int(*create)(structuser_namespace*,structinode*,structdentry*,umode_t, bool);/* 创建硬链接 */int(*link)(structdentry*,structinode*,structdentry*);/* 删除目录项对应的文件链接 */int(*unlink)(structinode*,structdentry*);/* 创建符号链接 */int(*symlink)(structuser_namespace*,structinode*,structdentry*,constchar*);/* 创建目录 */int(*mkdir)(structuser_namespace*,structinode*,structdentry*,umode_t);/* 删除目录 */int(*rmdir)(structinode*,structdentry*);/* 创建设备节点、fifo、socket等特殊文件 */int(*mknod)(structuser_namespace*,structinode*,structdentry*,umode_t,dev_t);/* 重命名或移动目录项 */int(*rename)(structuser_namespace*,structinode*,structdentry*,structinode*,structdentry*,unsignedint);int(*setattr)(structuser_namespace*,structdentry*,structiattr*);int(*getattr)(structuser_namespace*,conststructpath*,structkstat*, u32,unsignedint);ssize_t(*listxattr)(structdentry*,char*,size_t);int(*fiemap)(structinode*,structfiemap_extent_info*, u64 start, u64 len);int(*update_time)(structinode*,structtimespec64*,int);int(*atomic_open)(structinode*,structdentry*,structfile*,unsigned open_flag,umode_t create_mode);int(*tmpfile)(structuser_namespace*,structinode*,structdentry*,umode_t);int(*set_acl)(structuser_namespace*,structinode*,structposix_acl*,int);int(*fileattr_set)(structuser_namespace*mnt_userns,structdentry*dentry,structfileattr*fa);int(*fileattr_get)(structdentry*dentry,structfileattr*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 项连接相关的索引节点。
- 最近被使用的双向链表,该链表包含未被使用和负状态的目录项对象,并且该链表总是在头部插入目录项,所以链头节点的数据总是比链尾的数据要新。当内核要删除节点回收内存时,会从链尾删除,因为它们在最近内再次被使用的可能性最小。
- 散链表和相应的散列函数用来快速的给定路径解析未相关目录对象。
structdentry{unsignedint d_flags;/* 状态标志位 */seqcount_spinlock_t d_seq;/* per dentry seqlock */structhlist_bl_node d_hash;/* lookup hash list */structdentry*d_parent;/* 父目录dentry,组织目录树层级关系 */structqstr d_name;/* 目录项名字 */structinode*d_inode;/* 目录项对应的inode */unsignedchar d_iname[DNAME_INLINE_LEN];/* 小名字内联缓冲区 */structlockref d_lockref;/* per-dentry lock and refcount */conststructdentry_operations*d_op;/* 目录项操作函数集 */structsuper_block*d_sb;/* The root of the dentry tree */unsignedlong d_time;/* used by d_revalidate */void*d_fsdata;/* fs-specific data */union{structlist_head d_lru;/* LRU list */wait_queue_head_t*d_wait;/* in-lookup ones only */};structlist_head d_child;/* 作为子目录项时,挂载到父目录子项链表 */structlist_head d_subdirs;/* 子目录项链表 */union{structhlist_node d_alias;/* inode alias list */structhlist_bl_node d_in_lookup_hash;/* only for in-lookup ones */structrcu_head d_rcu;} d_u;} __randomize_layout;struct dentry_operations说明了 vfs 操作目录项的所有方法。
structdentry_operations{/* 重新验证debtry是否有效 */int(*d_revalidate)(structdentry*,unsignedint);int(*d_weak_revalidate)(structdentry*,unsignedint);int(*d_hash)(conststructdentry*,structqstr*);/* 比较名字是否相等 */int(*d_compare)(conststructdentry*,unsignedint,constchar*,conststructqstr*);/* 决定是否删除dentry */int(*d_delete)(conststructdentry*);/* dentry初始化时调用 */int(*d_init)(structdentry*);/* dentry最终释放前调用 */void(*d_release)(structdentry*);/* dentry被释放裁剪时调用 */void(*d_prune)(structdentry*);/* 指定如何释放与dentry关联的inode */void(*d_iput)(structdentry*,structinode*);/* 生成dentry的显示名字 */char*(*d_dname)(structdentry*,char*,int);/* 访问该dentry时自动挂载 */structvfsmount*(*d_automount)(structpath*);int(*d_manage)(conststructpath*, bool);structdentry*(*d_real)(structdentry*,conststructinode*);} ____cacheline_aligned;1.4 文件对象和操作
文件对象表示进程已打开的文件。
structfile{union{/* 延迟销毁链表 */structllist_node fu_llist;structrcu_head fu_rcuhead;} f_u;structpath f_path;/* 当前文件对应的路径对象 */structinode*f_inode;/* 当前文件对应的inode */conststructfile_operations*f_op;/* 文件操作函数集 */spinlock_t f_lock;enumrw_hint f_write_hint;atomic_long_t f_count;/* 引用计数 */unsignedint f_flags;/* 打开标志 */fmode_t f_mode;/* 文件模式标志 */structmutex f_pos_lock;loff_t f_pos;/* 文件偏移量 */structfown_struct f_owner;conststructcred*f_cred;structfile_ra_state f_ra; u64 f_version;#ifdefCONFIG_SECURITYvoid*f_security;#endifvoid*private_data;#ifdefCONFIG_EPOLLstructhlist_head*f_ep;#endif/* #ifdef CONFIG_EPOLL */structaddress_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 */structfile_handle{ __u32 handle_bytes;int handle_type;unsignedchar f_handle[];};struct file_operations代表文件对象的操作,具体的文件系统可以为每一种操作做专门的实现,也可以使用通用的操作。
structfile_operations{/* 指向所属的内核模块 */structmodule*owner;/* 调整文件偏移 */loff_t(*llseek)(structfile*,loff_t,int);/* 从指定偏移处读取数据 */ssize_t(*read)(structfile*,char __user *,size_t,loff_t*);/* 写数据到指定偏移处 */ssize_t(*write)(structfile*,constchar __user *,size_t,loff_t*);ssize_t(*read_iter)(structkiocb*,structiov_iter*);ssize_t(*write_iter)(structkiocb*,structiov_iter*);int(*iopoll)(structkiocb*kiocb, bool spin);/* 遍历目录项 */int(*iterate)(structfile*,structdir_context*);int(*iterate_shared)(structfile*,structdir_context*);__poll_t(*poll)(structfile*,structpoll_table_struct*);long(*unlocked_ioctl)(structfile*,unsignedint,unsignedlong);long(*compat_ioctl)(structfile*,unsignedint,unsignedlong);int(*mmap)(structfile*,structvm_area_struct*);unsignedlong mmap_supported_flags;int(*open)(structinode*,structfile*);int(*flush)(structfile*,fl_owner_t id);int(*release)(structinode*,structfile*);int(*fsync)(structfile*,loff_t,loff_t,int datasync);int(*fasync)(int,structfile*,int);int(*lock)(structfile*,int,structfile_lock*);ssize_t(*sendpage)(structfile*,structpage*,int,size_t,loff_t*,int);unsignedlong(*get_unmapped_area)(structfile*,unsignedlong,unsignedlong,unsignedlong,unsignedlong);int(*check_flags)(int);int(*flock)(structfile*,int,structfile_lock*);ssize_t(*splice_write)(structpipe_inode_info*,structfile*,loff_t*,size_t,unsignedint);ssize_t(*splice_read)(structfile*,loff_t*,structpipe_inode_info*,size_t,unsignedint);int(*setlease)(structfile*,long,structfile_lock**,void**);long(*fallocate)(structfile*file,int mode,loff_t offset,loff_t len);void(*show_fdinfo)(structseq_file*m,structfile*f);#ifndefCONFIG_MMUunsigned(*mmap_capabilities)(structfile*);#endifssize_t(*copy_file_range)(structfile*,loff_t,structfile*,loff_t,size_t,unsignedint);loff_t(*remap_file_range)(structfile*file_in,loff_t pos_in,structfile*file_out,loff_t pos_out,loff_t len,unsignedint remap_flags);int(*fadvise)(structfile*,loff_t,loff_t,int);} __randomize_layout;