浅学文件系统3(虚拟文件系统)

25 阅读11分钟

虚拟文件系统 Virtual File System

image.png

Linux中的 虚拟文件系统 (Virtual File System),所有的文件系统(ext4, xfs, tmpfs.....)等等,都要实现它提供的接口(实现具象化了)。

虚拟文件系统是Linux内核中的一个软件层,对内实现文件系统的抽象,允许不同的文件系统共存对外向应用程序提供统一的文件系统接口

VFS 定义了通用的文件系统模型,包括:

  • 超级块(Superblock):存储文件系统的元数据(如块大小、文件系统类型、大小、区块数、索引节点数等)。
  • 索引节点(Inode):存储文件的元数据(如权限、大小、创建时间、磁盘位置等 文件的基本信息)。
  • 目录项(Dentry):表示目录中的文件或子目录,用于快速查找。
  • 文件对象(File Object):表示打开的文件,包含文件的读写位置等信息。

VFS中的几个关键数据结构

Linux内核复杂的虚拟文件系统有很多struct结构,以下列举了部分数据结构

索引节点 
struct inode { 
    long i_ino; // 索引节点号 
    umode_t i_mode; // 文件类型与访问权限 
    uid_t i_uid; // 所有者标识符 
    gid_t i_gid; // 组标识符 
    loff_t i_size; // 文件大小 
    timespec i_atime; // 上次访问文件的时间 
    timespec i_mtime; // 上次修改文件的时间 
    timespec i_ctime; // 文件创建时间 
    long i_blksize; // 磁盘块大小 
    long i_blocks; // 文件的块数 
    inode_operations *i_op; // 索引节点的操作 
    file_operations *i_fop; // 文件操作 
    ....... 
};
索引节点操作;具体实现,不同的文件系统,都有自己的实现方式 
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 inode *, struct dentry *, 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 timespec *, int); // 更新时间戳
    int (*atomic_open)(struct inode *, struct dentry *, struct file *, unsigned int, umode_t, int *); // 原子打开操作
    int (*tmpfile)(struct inode *, struct dentry *, umode_t); // 创建临时文件
    int (*set_acl)(struct inode *, struct posix_acl *, int); // 设置访问控制列表
    ....... 
};
//文件对象 
struct file { 
    path f_path; //文件路径 
    inode *f_inode; //文件对应的索引节点 
    long f_pos; //读写位置 
    file_operations f_op; //文件操作 
    .....
}
文件操作, 每种文件系统都会去实现这些方法 
struct file_operations { 
    struct module *owner; /* 实现本结构的内核模块(用于引用计数,通常为 THIS_MODULE) */ 
    /* ① 文件位置与读写 */ 
    loff_t (*llseek) (struct file *, loff_t, int); /* 调整文件指针:whence=SEEK_SET/SEEK_CUR/SEEK_END */ 
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); /* 传统同步读,返回已读字节数或负错误码 */ 
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); /* 传统同步写,返回已写字节数或负错误码 */ 
    
    /* ② 迭代器式读写(支持异步、向量 IO) */ 
    ssize_t (*read_iter) (struct kiocb *, struct iov_iter *); /* 新接口:read_iter / aio_read / io_uring 统一入口 */ 
    ssize_t (*write_iter) (struct kiocb *, struct iov_iter *); /* 新接口:write_iter / aio_write / io_uring 统一入口 */ 
    
    /* ③ 目录遍历 */ 
    int (*iterate) (struct file *, struct dir_context *); /* 旧版 readdir,已逐步淘汰 */ 
    int (*iterate_shared) (struct file *, struct dir_context *); /* 新版 readdir,允许并发的 getdents64,主流实现 */ 
    
    /* ④ 事件通知 */ 
    unsigned int (*poll) (struct file *, struct poll_table_struct *); /* select/poll/epoll 回调,返回 EPOLLIN/EPOLLOUT 等掩码 */
    
    /* ⑤ 控制类 */
    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); /* 32/64 通用 ioctl,无需内核大内核锁(BKL) */ 
    long (*compat_ioctl) (struct file *, unsigned int, unsigned long); /* 64 位内核服务 32 位用户态的兼容 ioctl */ 
    
    /* ⑥ 内存映射 */ 
    int (*mmap) (struct file *, struct vm_area_struct *); /* mmap(2) 入口,建立文件页到用户空间的映射 */
    
    /* ⑦ 生命周期 */ 
    int (*open) (struct inode *, struct file *); /* open(2) 最后阶段调用,可做私有数据初始化 */ 
    int (*flush) (struct file *, fl_owner_t id); /* 每次 close(2) 都会触发(可能多次,dup 时) */ 
    int (*release) (struct inode *, struct file *); /* 最后一个 fd 关闭时调用,做真正资源释放 */ 
    
    /* ⑧ 数据落盘 */ 
    int (*fsync) (struct file *, loff_t start, loff_t end, int datasync); /* fsync/fdatasync 回调,把缓存脏页刷回磁盘 */
    
    /* ⑨ 信号驱动 IO */ 
    int (*fasync) (int fd, struct file *, int on); /* 打开或关闭 FASYNC 标志,用于信号驱动 IO */ 
    
    /* ⑩ 文件锁 */ 
    int (*lock) (struct file *, int cmd, struct file_lock *); /* 旧版 posix lock,已逐步被 flock 替代 */ 
    int (*flock) (struct file *, int cmd, struct file_lock *); /* BSD flock 锁实现入口 */ 
    
    /* ⑪ 零拷贝相关 */ 
    ssize_t (*sendpage) (struct file *, struct page *, int offset, size_t size, loff_t *, int flags); /* sendfile(2) 内核到网络套接字的零拷贝写 */ 
    ssize_t (*splice_read) (struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int); 
    ssize_t (*splice_write) (struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int); /* splice(2)/tee(2)/vmsplice(2) 的管道零拷贝实现 */ 
    /* ⑫ 辅助 */ 
    unsigned long (*get_unmapped_area)(struct file *, unsigned long addr, unsigned long len, unsigned long pgoff); /* 为 mmap 在进程地址空间中寻找未映射区域 */ 
    int (*check_flags)(int flags); /* 允许文件系统校验 open(2) 的 flags(如 O_DIRECT) */ 
    int (*setlease)(struct file *, long arg, struct file_lock **, void **); /* fcntl(fd, F_SETLEASE, …) 租约锁实现 */ 
    .........
};
目录项 
struct dentry { 
    struct dentry *d_parent; // 父亲目录项 
    struct list_head d_child; // 该目录项的父目录的所有孩子目录项 
    struct list_head d_subdirs; // 该目录的所有子目录 
    struct qstr d_name; // 目录项名称 
    struct inode *d_inode; // 目录项对应的 inode 
    const struct dentry_operations *d_op; // 目录项操作 
    .......
};
目录项操作
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 *, unsigned int); 
    .......
};
//文件路径 
struct path{ 
    dentry *dentry; //目录项 
    vfsmount *mnt; //文件系统 
    ......
}
//文件系统挂载信息
struct vfsmount{ 
    dentry *mnt_root; //根目录 
    super_block *mnt_sb; 超级块 
    ......
}
超级块 
struct super_block {
    dev_t s_dev; // 设备标识符 
    unsigned long s_blocksize; // 块大小 
    struct dentry *s_root; // 文件系统的根目录目录项 
    void *s_fs_info; // 指向特定文件系统的超级块信息的指针 
    struct file_system_type *s_type; // 文件系统类型 
    const struct super_operations *s_op; // 超级块操作 
};
超级块操作 
struct super_operations { 
    struct inode *(*alloc_inode)(struct super_block *sb);
    void (*destroy_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 *); 
    // …… 其他操作 
};
文件系统类型 
struct file_system_type { 
    const char *name; // 文件系统名称 xfx, ext4 等 
    int fs_flags; // 文件系统标记信息 
    struct dentry *(*mount)(struct file_system_type *fs_type, int flags, const char *dev_name, void *data); // 挂载文件系统的方法 
    void (*kill_sb)(struct super_block *sb); // 删除 super_block 
    .....
};

虚拟文件系统数据结构大总结.png

vfsmount 文件系统挂载

image.png

应用程序与VFS的交互

Linux 中每个进程 都有自己的 task_struct

image.png 进程的task_struct结构体

struct  task_struct{
    //标志信息
    pid_t pid;                  // 进程ID
    pid_t tgid;                 // 线程组ID(进程的主线程ID)
    struct task_struct *group_leader;  // 线程组的领头线程

    //文件 & 文件系统 信息
    struct fs_struct *fs;       // 文件系统信息(当前目录、根目录等。fs包含的目录信息)
    struct files_struct *files; // 打开文件表(files管理打开的文件描述符)

    //内存管理 信息
    struct mm_struct *mm;       // 进程内存管理描述符(mm管理进程的虚拟内存空间)
    ......
}
fs_struct 只关心“路径”——进程看到的根、当前目录、umask
struct fs_struct {
    int              users;          // 引用计数(共享该结构的线程数)
    spinlock_t       lock;           // 保护下列路径指针
    seqcount_spinlock_t seq;         // 顺序锁,读路径时用

    /* 四大“路径”——所有进程都从这里解析相对路径 */
    struct path      root;           // 当前“根”(chroot 后变)
    struct path      pwd;            // 当前工作目录
    struct path      altroot;        // 可选的替代根(几乎不用)
    unsigned         altroot_dentry : 1;

    /* umask 掩码 */
    umode_t          umask;          // 新建文件默认权限掩码
    .....
};
files_struct 就是“打开文件描述符表”——fd 到 struct file * 的映射,进程每 open/close 一次就改这张表
struct files_struct {
    atomic_t         count;          // 引用计数
    struct fdtable  *fdt;            // 指向“动态表”的指针
    struct fdtable   fdtab;          // 内嵌的一份“静态表”
    int              next_fd;        // 缓存的“下一个可用 fd”
    unsigned long    close_on_exec_init[1];  // 内嵌位图:exec 时关闭
    unsigned long    open_fds_init[1];       // 内嵌位图:已打开 fd
    struct file     *fd_array[NR_OPEN_DEFAULT]; // 内嵌指针数组
};

task_struct中的file.png

1.打开文件的粗略流程

image.png

2.文件的读粗略流程

image.png

3.文件的写粗略流程

image.png

硬链接与软链接

image.png Linux操作系统中实现文件共享的方式有两种: 硬链接、软链接

1.硬链接(Hard Link)

image.png

2.软链接(Symbolic Link) 也叫符号链接(就像windows中的快捷方式)

image.png

拓展:Linux中的文件描述符

在 Linux 中,fd 指的是 file descriptor(文件描述符),它是一个非负整数,用于 标识一个打开的文件、套接字、管道或其他 I/O 资源。 文件描述符是操作系统内核用于管理 I/O 操作的抽象,用户程序(进程)通过文件描述符与这些资源进行交互

常见的文件描述符类型

image.png

文件描述符是 Linux 中与 I/O 资源交互的基本单位,除了常见的标准输入、输出和错误描述符外,还有多种特定用途的文件描述符,如 eventfd 和 timerfd,它们提供了更灵活的事件通知和定时器功能。通过这些文件描述符,程序可以高效地管理和处理各种 I/O 操作。

eventfd 简述

作用: 专门用于事件通知的文件描述符( fd ),eventfd 是 Linux 2.6.27 添加了一个新的特性,用来实现多进程或多线程的之间的事件通知的,也可以由内核通知用户空间应用程序(用于用户态和内核态的通信)。

代码位于: fs/eventfd.c

思考一个小问题:我们知道“文件”里是保存东西的,eventfd 既然对应了一个“文件”,那么这个“文件”的内容是什么呢?

划重点:eventfd 是一个计数相关的文件描述符(fd)。计数不为零是有可读事件发生,read 之后计数会清零,write 则会递增计数器

初始化

#include <sys/eventfd.h> 
int eventfd(unsigned int initval, int flags); 
(参数1)initval:创建eventfd时它所对应的 64 位计数器的初始值; 
(参数2)flags: eventfd文件描述符的标志,用以改变 eventfd 的行为,大多情况设置为 0 即可。如果是2.6.26或之前版本的内核,flags 必须设置为 0 
EFD_CLOEXEC (since Linux 2.6.27):文件被设置成 O_CLOEXEC,创建子进程 (fork) 时不继承父进程的文件描述符 
EFD_NONBLOCK(since Linux 2.6.27):设置文件描述符为非阻塞的,设置了这个标志后,如果没有数据可读,就返回一个 EAGAIN 错误,不会一直阻塞。
EFD_SEMAPHORE(since Linux 2.6.30):提供类似信号量语义的 read 操作,简单说就是计数值 count 递减 1。可以多次 read

读写操作

//先初始化 
int efd = eventfd(0, 0); 
if (efd == -1){ 
handle_error("eventfd"); 
} 
写操作, write 的时候,累加计数:
uint64_t u = 1;
ssize_t n; 
// 写 eventfd,必须是 64 位整数
n = write(efd, &u, sizeof(uint64_t));// 内部计数器变为 1 

u = 2n = write(efd, &u, sizeof(uint64_t));// 内部计数器变为 3 

u = 3n = write(efd, &u, sizeof(uint64_t));// 内部计数器变为 6 

读操作 
n = read(efd, &u, sizeof(uint64_t)); //read 的时候,读出计数器的值,并将内核中的计数器赋值为 0:
  1. Linux 一切皆文件,但 fd 各有不同;
  2. eventfd 实现了 read/write 的接口,本质是一个计数器的实现;
  3. eventfd 实现了 poll 接口,所以可以和 epoll 双剑合璧,实现事件的通知管理;
  4. eventfd 可以和 libaio & epoll 一起,实现 Linux 下的纯异步 IO;
  5. eventfd 监听可读事件才有意义;
  6. ext4 这种文件 fd 一直可读可写,所以实现 poll 毫无意义。eventfd 一直可写,所以监听可写毫无意义;
  7. eventfd 可以结合业务,做一个事件通知的通信机制,非常巧妙;

总结来自:Linux fd 系列 — eventfd 是什么? - 知乎 (zhihu.com)