Linux虚拟文件系统(1)
1 虚拟文件系统(VFS)
虚拟文件系统(Virtual File System, VFS)作为内核的子系统。,它为用户空间的应用程序提供了一个统一的文件系统接口。通过VFS,不同的文件系统可以共存于同一个操作系统中,并且应用程序不需要了解底层具体文件系统的实现细节即可访问这些文件系统。
"一切皆文件"是Linux的基本哲学之一,不仅是普通的文件,包括目录、字符设备、块设备、套接字等,都可以以文件的方式被对待。实现这一行为的基础,正是Linux的虚拟文件系统机制。
主要特点
-
抽象层:VFS作为Linux内核与实际文件系统之间的抽象层,允许不同类型的文件系统(如ext4, NTFS, NFS等)以统一的方式进行交互。
-
通用接口:它提供了一套标准的操作接口,例如打开文件(
open
)、读取文件(read
)、写入文件(write
)、关闭文件(close
)等,使得应用程序可以通过相同的API访问各种存储介质和文件系统类型。 -
支持多种文件系统:无论是本地文件系统还是网络文件系统,VFS都能处理。这包括但不限于磁盘上的文件系统(如ext4, xfs),内存文件系统(如tmpfs),以及网络文件系统(如NFS)。
2 虚拟文件系统的4个主要对象
2.1 超级块
超级块(super_block):每个文件系统都有一个超级块,包含了该文件系统的元数据信息,比如文件系统的大小、空闲块的数量等.
可以通过以下命令查看当前系统的挂载情况,从而确定有多少个超级块实例:
超级块(super_block)一般大小为1024bytes,记录的信息主要有:
- block 与inode 的总量
- 未使用与已使用的inode / block 数量
- 一个valid bit 数值,若此文件系统已被挂载,则valid bit 为0 ,若未被挂载,则valid bit 为1
- block 与inode 的大小 (block 为1, 2, 4K,inode 为128bytes 或256bytes);
- 其他各种文件系统相关信息:filesystem 的挂载时间、最近一次写入资料的时间、最近一次检验磁碟(fsck) 的时
superblock在内核的相关属性结构
struct super_block {/* 1. 链接到全局超级块链表 */struct list_head s_list; // 所有超级块链接成的全局链表/* 2. 设备标识与块大小 */kdev_t s_dev; // 设备号(如 /dev/sda1)unsigned long s_blocksize; // 文件系统块大小(字节单位)unsigned char s_blocksize_bits; // 块大小以 log2 表示(例如 1024=10)/* 3. 状态与标志 */unsigned char s_lock; // 超级块是否被锁住unsigned char s_dirt; // 是否为“脏”(是否需要写回磁盘)/* 4. 文件系统类型与操作函数 */struct file_system_type *s_type; // 指向文件系统的类型(如 ext4_fs_type)const struct super_operations *s_op; // 超级块操作函数(如 alloc_inode、write_super)const struct dquot_operations *dq_op; // 配额操作函数/* 5. 挂载标志与魔数 */unsigned long s_flags; // 挂载标志(如 MS_RDONLY, MS_NOATIME)unsigned long s_magic; // 文件系统魔数(如 EXT4_SUPER_MAGIC = 0xEF53)/* 6. 根目录项 */struct dentry *s_root; // 根目录对应的 dentry(如 / 或 /home)/* 7. 等待队列 */wait_queue_head_t s_wait; // 等待该超级块某些资源可用的进程队列/* 8. 脏 inode 和打开文件列表 */struct list_head s_dirty; // 所有脏 inode 的链表(等待同步到磁盘)struct list_head s_files; // 当前所有打开文件的链表/* 9. 关联的块设备 */struct block_device *s_bdev; // 指向底层块设备结构(如 /dev/sda1)/* 10. 挂载点链表 */struct list_head s_mounts; // 挂载到该超级块的所有 vfsmount 实例链表/* 11. 配额配置选项 */struct quota_mount_options s_dquot; // 磁盘配额相关挂载选项/* 12. 文件系统私有信息(联合体) */union {struct minix_sb_info minix_sb;struct ext2_sb_info ext2_sb;struct hpfs_sb_info hpfs_sb;struct ntfs_sb_info ntfs_sb;struct msdos_sb_info msdos_sb;struct isofs_sb_info isofs_sb;struct nfs_sb_info nfs_sb;struct sysv_sb_info sysv_sb;struct affs_sb_info affs_sb;struct ufs_sb_info ufs_sb;struct efs_sb_info efs_sb;struct shmem_sb_info shmem_sb;struct romfs_sb_info romfs_sb;struct smb_sb_info smbfs_sb;struct hfs_sb_info hfs_sb;struct adfs_sb_info adfs_sb;struct qnx4_sb_info qnx4_sb;struct bfs_sb_info bfs_sb;struct udf_sb_info udf_sb;struct ncp_sb_info ncpfs_sb;struct usbdev_sb_info usbdevfs_sb;void *generic_sbp;} u;/* 13. VFS 内部使用的同步机制 */struct semaphore s_vfs_rename_sem; // rename 操作时的互斥信号量/* 14. NFS 相关路径处理 */struct semaphore s_nfsd_free_path_sem; // NFS 导出路径的同步信号量
};
struct super_operations
是 Linux 内核中定义的一组函数指针,用于操作超级块(super_block
),它提供了对文件系统级别的各种操作接口。这些操作包括创建和销毁索引节点(inode)、读写超级块、同步数据到磁盘等,如下:
struct super_operations {// 创建一个新的索引节点(inode)。在需要创建新文件或目录时调用。struct inode *(*alloc_inode)(struct super_block *sb);// 销毁一个索引节点(inode)。当索引节点不再需要时调用。void (*destroy_inode)(struct inode *);// 从磁盘读取超级块信息并更新超级块结构体。通常在挂载文件系统时调用。void (*read_inode) (struct inode *);// 将脏的超级块信息写回到磁盘。在同步操作中使用。void (*write_inode) (struct inode *, int);// 清理超级块上的脏数据,并将其写回磁盘。在卸载文件系统时调用。void (*put_super) (struct super_block *);// 同步超级块中的所有脏数据到磁盘。用于确保数据一致性。void (*write_super) (struct super_block *);// 检查超级块是否已经被修改(脏)。返回非零值表示已修改。int (*sync_fs)(struct super_block *sb, int wait);// 更新超级块的状态。通常在改变挂载选项后调用。void (*write_super_lockfs) (struct super_block *);// 解除之前由 write_super_lockfs 加上的锁。恢复正常的文件系统操作。void (*unlockfs) (struct super_block *);// 统计文件系统的统计信息,如空闲块数、总块数等。int (*statfs) (struct dentry *, struct kstatfs *);// 重新设置文件系统的大小。对于支持动态调整大小的文件系统有用。int (*remount_fs) (struct super_block *, int *, char *);// 在清除inode时调用,允许文件系统执行一些清理工作。void (*clear_inode) (struct inode *);// 在删除inode时调用,允许文件系统执行一些额外的工作。void (*delete_inode) (struct inode *);// 显示文件系统的特定信息。主要用于调试目的。void (*show_options)(struct seq_file *, struct dentry *);// 检查文件系统是否可以被挂载。可以在挂载前验证文件系统的状态。int (*show_devname)(struct seq_file *, struct dentry *);// 显示文件系统的类型名称。int (*show_path)(struct seq_file *, struct dentry *);// 显示文件系统的UUID或其他唯一标识符。int (*show_stats)(struct seq_file *, struct dentry *);// 其他扩展功能,如加密支持等。int (*bdev_try_to_free_page)(struct super_block*, struct page*, gfp_t);
};
2.2 索引节点
- 索引节点是VFS中的核心概念,它包含内核在操作文件或目录时需要的全部信息。
- 一个索引节点代表文件系统中的一个文件(这里的文件不仅是指我们平时所认为的普通的文件,还包括目录,特殊设备文件等等)。
- 索引节点和超级块一样是实际存储在磁盘上的,当被应用程序访问到时才会在内存中创建。
文件或目录的inode编号,如下:
stat 命令提供了更详细的文件或文件系统状态信息,包括inode编号、权限、大小等。
检查文件系统的inode使用情况
1.主要记录文件的属性以及该文件实际数据是放置在哪些block中,它记录的信息至少有这些:
- 大小、真正内容的block号码(一个或多个)
- 访问模式(read/write/excute)
- 拥有者与群组(owner/group)
- 各种时间:建立或状态改变的时间、最近一次的读取时间、最近修改的时间
没有文件名!文件名在目录的block中!
- 一个文件占用一个 inode,每个inode有编号
- 注意,这里的文件不单单是普通文件,目录文件也就是文件夹其实也是一个文件
- inode 的数量与大小在格式化时就已经固定了,每个inode 大小均固定为128 bytes (新的ext4 与xfs 可设定到256 bytes)
- 文件系统能够建立的文件数量与inode 的数量有关,存在空间还够但inode不够的情况
- 系统读取文件时需要先找到inode,并分析inode 所记录的权限与使用者是否符合,若符合才能够开始实际读取 block 的内容
- inode 要记录的资料非常多,但偏偏又只有128bytes , 而inode 记录一个block 号码要花掉4byte ,假设我一个文件有400MB 且每个block 为4K 时, 那么至少也要十万条block 号码的记录!inode 哪有这么多空间来存储?为此我们的系统很聪明的将inode 记录block 号码的区域定义为12个直接,一个间接, 一个双间接与一个三间接记录区,见后续章节。
inode在内核的相关属性结构
struct inode {/* 1. 列表指针:用于管理inode */struct list_head i_hash; // 哈希链表,用于快速查找inodestruct list_head i_list; // inode状态链表(如脏inode链表)struct list_head i_dentry; // 指向与该inode关联的所有dentry/* 2. 脏缓冲区列表 */struct list_head i_dirty_buffers; // 脏缓冲区链表,用于回写操作/* 3. 索引节点编号和引用计数 */unsigned long i_ino; // inode编号,唯一标识一个inodeatomic_t i_count; // 引用计数,表示当前有多少用户正在使用这个inodekdev_t i_dev; // 设备号,表示该inode所在的设备/* 4. 文件权限和其他属性 */umode_t i_mode; // 文件模式(权限)nlink_t i_nlink; // 链接数,即有多少硬链接指向此inodeuid_t i_uid; // 用户IDgid_t i_gid; // 组IDkdev_t i_rdev; // 如果是特殊文件(字符设备或块设备),则存储其设备号loff_t i_size; // 文件大小(字节数)/* 5. 时间戳 */time_t i_atime; // 最后访问时间time_t i_mtime; // 最后修改时间time_t i_ctime; // 状态改变时间(如权限、所有者等)/* 6. 块大小和块数 */unsigned long i_blksize; // 逻辑块大小(通常为文件系统的块大小)unsigned long i_blocks; // 文件占用的实际块数/* 7. 版本控制和同步 */unsigned long i_version; // 版本号,用于检测文件变化struct semaphore i_sem; // 信号量,用于保护对inode的并发访问struct semaphore i_zombie; // 处理僵尸inode时使用的信号量/* 8. 操作函数指针 */struct inode_operations *i_op; // inode操作函数集struct file_operations *i_fop; // 文件操作函数集(默认文件操作)/* 9. 超级块和等待队列 */struct super_block *i_sb; // 指向该inode所属的超级块wait_queue_head_t i_wait; // 等待队列头,用于进程等待inode可用/* 10. 文件锁和其他信息 */struct file_lock *i_flock; // 文件锁信息struct address_space *i_mapping;// 映射到地址空间的信息struct address_space i_data; // 地址空间数据(如页缓存)struct dquot *i_dquot[MAXQUOTAS]; // 磁盘配额信息/* 11. 特殊文件信息 */struct pipe_inode_info *i_pipe; // 如果是管道文件,则存储相关信息struct block_device *i_bdev; // 如果是块设备,则存储相关信息/* 12. 目录通知 */unsigned long i_dnotify_mask; // 目录通知事件掩码struct dnotify_struct *i_dnotify; // 目录通知结构/* 13. 状态标志 */unsigned long i_state; // inode的状态标志(如脏、锁定等)unsigned int i_flags; // inode标志(如不可变、追加等)unsigned char i_sock; // 是否为socket/* 14. 写计数和其他标志 */atomic_t i_writecount; // 写计数,表示当前有多少进程正在写入该inodeunsigned int i_attr_flags; // 扩展属性标志__u32 i_generation; // 生成号,用于NFS等需要版本控制的场景/* 15. 文件系统特定信息 */union {struct minix_inode_info minix_i;struct ext2_inode_info ext2_i;struct hpfs_inode_info hpfs_i;struct ntfs_inode_info ntfs_i;struct msdos_inode_info msdos_i;struct umsdos_inode_info umsdos_i;struct iso_inode_info isofs_i;struct nfs_inode_info nfs_i;struct sysv_inode_info sysv_i;struct affs_inode_info affs_i;struct ufs_inode_info ufs_i;struct efs_inode_info efs_i;struct romfs_inode_info romfs_i;struct shmem_inode_info shmem_i;struct coda_inode_info coda_i;struct smb_inode_info smbfs_i;struct hfs_inode_info hfs_i;struct adfs_inode_info adfs_i;struct qnx4_inode_info qnx4_i;struct bfs_inode_info bfs_i;struct udf_inode_info udf_i;struct ncp_inode_info ncpfs_i;struct proc_inode_info proc_i;struct socket socket_i;struct usbdev_inode_info usbdev_i;void *generic_ip;} u; // 文件系统特定的数据结构
};
struct inode_operations
用于操作索引节点(inode)。每个文件系统都需要实现这些函数以支持基本的文件操作。
下面是对 struct inode_operations
结构体的具体内容及其字段的详细注释:
struct inode_operations {// 创建一个新的索引节点(如创建新文件或目录)struct dentry * (*create) (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 inode *,struct dentry *,const char *);// 创建一个指向已有索引节点的新名称(重命名)int (*rename) (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_work *);// 文件权限检查int (*permission) (struct user_namespace *, struct inode *, int);// 释放inode资源void (*free_inode) (struct inode *);// 在删除inode前调用,允许执行清理工作void (*destroy_inode)(struct inode *);// 读取目录项int (*read_inode) (struct inode *);// 写入inode信息到磁盘int (*write_inode) (struct inode *, struct writeback_control *wbc);// 截断文件大小int (*truncate) (struct inode *);// 设置文件权限和所有者int (*setattr) (struct dentry *, struct iattr *);// 获取文件属性int (*getattr) (const struct path *, struct kstat *, u32, unsigned int);// 设置扩展属性int (*setxattr) (struct dentry *, const char *,const void *, size_t, int);// 获取扩展属性ssize_t (*getxattr) (struct dentry *, const char *, void *, size_t);// 列出扩展属性ssize_t (*listxattr) (struct dentry *, char *, size_t);// 移除扩展属性int (*removexattr) (struct dentry *, const char *);// 更新时间戳int (*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 inode *, struct dentry *, umode_t);
};
struct file_operations 用于处理对文件的各种操作请求。这些操作包括打开、读取、写入、关闭文件等。每个文件系统或设备驱动程序都需要实现这个结构体的部分或全部成员函数,以便与虚拟文件系统(VFS)层进行交互。
以下是 struct file_operations
结构体的具体内容及其字段的详细注释:
struct file_operations {// 打开文件时调用struct module *owner; // 通常设置为 THIS_MODULE,表示该操作集属于哪个模块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 (*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);// 异步I/O支持int (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);int (*aio_write)(struct kiocb *, const struct iovec *, unsigned long, loff_t);// 检查文件访问权限int (*check_flags)(int);// 文件截断int (*ftruncate) (struct inode *, loff_t);// 锁定文件long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);long (*compat_ioctl) (struct file *, unsigned int, unsigned long);// 发送信号到进程int (*fasync) (int, struct file *, int);// 锁定文件int (*lock) (struct file *, int, struct file_lock *);// 轮询函数,用于等待某些事件发生(如可读、可写)unsigned int (*poll) (struct file *, struct poll_table_struct *);// 使用mmap将文件映射到内存时调用int (*mmap_capabilities) (struct file *);// 用于直接I/Ossize_t (*direct_IO)(struct kiocb *, struct iov_iter *, loff_t offset);// 用于扩展属性int (*setlease)(struct file *, long, struct file_lock **, void **priv);long (*fallocate)(struct file *file, int mode, loff_t offset, loff_t len);
};