虚拟文件系统 Virtual File System
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
.....
};
vfsmount 文件系统挂载
应用程序与VFS的交互
Linux 中每个进程 都有自己的 task_struct
进程的
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]; // 内嵌指针数组
};
1.打开文件的粗略流程
2.文件的读粗略流程
3.文件的写粗略流程
硬链接与软链接
Linux操作系统中实现文件共享的方式有两种: 硬链接、软链接
1.硬链接(Hard Link)
2.软链接(Symbolic Link) 也叫符号链接(就像windows中的快捷方式)
拓展:Linux中的文件描述符
在 Linux 中,fd 指的是 file descriptor(文件描述符),它是一个非负整数,用于 标识一个打开的文件、套接字、管道或其他 I/O 资源。 文件描述符是操作系统内核用于管理 I/O 操作的抽象,用户程序(进程)通过文件描述符与这些资源进行交互
常见的文件描述符类型
文件描述符是 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 = 2;
n = write(efd, &u, sizeof(uint64_t));// 内部计数器变为 3
u = 3;
n = write(efd, &u, sizeof(uint64_t));// 内部计数器变为 6
读操作
n = read(efd, &u, sizeof(uint64_t)); //read 的时候,读出计数器的值,并将内核中的计数器赋值为 0:
- Linux 一切皆文件,但 fd 各有不同;
- eventfd 实现了 read/write 的接口,本质是一个计数器的实现;
- eventfd 实现了 poll 接口,所以可以和 epoll 双剑合璧,实现事件的通知管理;
- eventfd 可以和 libaio & epoll 一起,实现 Linux 下的纯异步 IO;
- eventfd 监听可读事件才有意义;
- ext4 这种文件 fd 一直可读可写,所以实现 poll 毫无意义。eventfd 一直可写,所以监听可写毫无意义;
- eventfd 可以结合业务,做一个事件通知的通信机制,非常巧妙;