Linux虚拟文件系统(1)

1 虚拟文件系统(VFS)

**虚拟文件系统(Virtual File System, VFS)**作为内核的子系统。,它为用户空间的应用程序提供了一个统一的文件系统接口。通过VFS,不同的文件系统可以共存于同一个操作系统中,并且应用程序不需要了解底层具体文件系统的实现细节即可访问这些文件系统。

"一切皆文件"是Linux的基本哲学之一,不仅是普通的文件,包括目录、字符设备、块设备、套接字等,都可以以文件的方式被对待。实现这一行为的基础,正是Linux的虚拟文件系统机制。

主要特点

  1. 抽象层:VFS作为Linux内核与实际文件系统之间的抽象层,允许不同类型的文件系统(如ext4, NTFS, NFS等)以统一的方式进行交互。

  2. 通用接口 :它提供了一套标准的操作接口,例如打开文件(open)、读取文件(read)、写入文件(write)、关闭文件(close)等,使得应用程序可以通过相同的API访问各种存储介质和文件系统类型。

  3. 支持多种文件系统 :无论是本地文件系统还是网络文件系统,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在内核的相关属性结构

cpp 复制代码
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)、读写超级块、同步数据到磁盘等,如下:

cpp 复制代码
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中!

  1. 一个文件占用一个 inode,每个inode有编号
  2. 注意,这里的文件不单单是普通文件,目录文件也就是文件夹其实也是一个文件
  3. inode 的数量与大小在格式化时就已经固定了,每个inode 大小均固定为128 bytes (新的ext4 与xfs 可设定到256 bytes)
  4. 文件系统能够建立的文件数量与inode 的数量有关,存在空间还够但inode不够的情况
  5. 系统读取文件时需要先找到inode,并分析inode 所记录的权限与使用者是否符合,若符合才能够开始实际读取 block 的内容
  6. inode 要记录的资料非常多,但偏偏又只有128bytes , 而inode 记录一个block 号码要花掉4byte ,假设我一个文件有400MB 且每个block 为4K 时, 那么至少也要十万条block 号码的记录!inode 哪有这么多空间来存储?为此我们的系统很聪明的将inode 记录block 号码的区域定义为12个直接,一个间接, 一个双间接与一个三间接记录区,见后续章节。

inode在内核的相关属性结构

cpp 复制代码
struct inode {
    /* 1. 列表指针:用于管理inode */
    struct list_head i_hash;        // 哈希链表,用于快速查找inode
    struct 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编号,唯一标识一个inode
    atomic_t i_count;               // 引用计数,表示当前有多少用户正在使用这个inode
    kdev_t i_dev;                   // 设备号,表示该inode所在的设备

    /* 4. 文件权限和其他属性 */
    umode_t i_mode;                 // 文件模式(权限)
    nlink_t i_nlink;                // 链接数,即有多少硬链接指向此inode
    uid_t i_uid;                    // 用户ID
    gid_t i_gid;                    // 组ID
    kdev_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;          // 写计数,表示当前有多少进程正在写入该inode
    unsigned 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 结构体的具体内容及其字段的详细注释:

cpp 复制代码
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 结构体的具体内容及其字段的详细注释:

cpp 复制代码
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/O
    ssize_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);
};
相关推荐
wangzhongyudie3 分钟前
SQL实战:06交叉日期打折问题求解
数据库·sql
选与握5 分钟前
ubuntu工控机固定设备usb串口号
linux·运维·ubuntu
舰长11537 分钟前
ubuntu 安装mq
linux·运维·ubuntu
不是吧这都有重名1 小时前
利用systemd启动部署在服务器上的web应用
运维·服务器·前端
2401_841003981 小时前
mysql高可用
数据库·mysql
2302_809798321 小时前
【JavaWeb】MySQL(准备篇)
数据库·mysql·datagrip
传知摩尔狮1 小时前
Linux 中断源码性能分析实战:从内核深处榨取每一丝性能
linux·运维·服务器
阿四啊1 小时前
【Redis实战篇】秒杀优化
数据库·redis·缓存
川石课堂软件测试1 小时前
涨薪技术|0到1学会性能测试第65课-SQL捕获阻塞事件
数据库·sql·功能测试·oracle·性能优化·单元测试·tomcat
春风化作秋雨1 小时前
Nginx应用场景详解与配置指南
运维·nginx