“一切皆文件” 不是口号。ext4、btrfs、procfs、sysfs、FUSE、cgroupfs、网络 socket 都在同一套 VFS(Virtual File System)接口下被读写。VFS 的核心做法只有两条:用四个对象 superblock / inode / dentry / file 表达层次关系,再用操作表(function pointer struct)让每种文件系统注入自己的实现。本文按这个骨架讲清楚结构、缓存与 RCU 快路径。
参考实现以 Linux 6.x 主线为准,文件路径默认指向
include/linux/fs.h 与
include/linux/dcache.h。
一、先看图
flowchart TD
APP[用户 open/read/write] --> FD[struct file<br/>fd 表项]
FD --> DENTRY[struct dentry<br/>目录项缓存]
DENTRY --> INODE[struct inode<br/>元数据]
INODE --> SB[struct super_block<br/>文件系统实例]
SB --> FS[具体 FS 实现<br/>ext4/btrfs/xfs...]
FD -.f_op.-> FOP[file_operations<br/>read/write/mmap...]
INODE -.i_op.-> IOP[inode_operations<br/>lookup/create/link...]
SB -.s_op.-> SOP[super_operations<br/>alloc_inode/sync_fs...]
classDef vfs fill:#388bfd22,stroke:#388bfd,color:#adbac7;
classDef ops fill:#a371f722,stroke:#a371f7,color:#adbac7;
class FD,DENTRY,INODE,SB vfs
class FOP,IOP,SOP ops
二、superblock
每个挂载的文件系统实例一个 super_block:
struct super_block {
struct list_head s_list; // 全局链表
dev_t s_dev; // 设备号
unsigned long s_blocksize;
struct file_system_type *s_type;
const struct super_operations *s_op;
struct dentry *s_root; // 根 dentry
struct hlist_bl_head s_roots;
/* 其余字段省略 */
};s_op 包含
alloc_inode、destroy_inode、sync_fs、statfs
等。
三、inode
每个文件/目录/符号链接一个 inode(内存中的):
struct inode {
umode_t i_mode; // 权限 + 文件类型
kuid_t i_uid; kgid_t i_gid;
loff_t i_size;
struct timespec64 i_atime, i_mtime, i_ctime;
const struct inode_operations *i_op;
const struct file_operations *i_fop;
struct super_block *i_sb;
struct address_space *i_mapping; // 页缓存
unsigned long i_ino;
/* 其余字段省略 */
};i_op:lookup、create、mkdir、rename、permission…i_fop:open 时复制给struct file
inode 缓存(inode_cache)在 slab 里,hash
索引。
四、dentry
dentry 缓存路径组件 → inode 的映射:
struct dentry {
struct dentry *d_parent;
struct qstr d_name; // 组件名:hash + len + name
struct inode *d_inode; // NULL = negative dentry
const struct dentry_operations *d_op;
struct super_block *d_sb;
struct hlist_bl_node d_hash; // dcache hash 桶
struct list_head d_subdirs;
/* 其余字段省略 */
};dcache
全局 hash 表 dentry_hashtable:按 (parent,
name_hash) 查。命中 dcache
的路径查找只需内存操作,不碰磁盘。
negative dentry
d_inode = NULL 表示”这个名字不存在”——缓存
ENOENT,避免重复磁盘查找。
cat /proc/sys/fs/dentry-state
# nr_dentry nr_unused age_limit want_pages nr_negative五、struct file
open() 创建一个
struct file:
struct file {
struct path f_path; // {vfsmount, dentry}
struct inode *f_inode;
const struct file_operations *f_op;
atomic_long_t f_count; // 引用计数
unsigned int f_flags; // O_RDONLY / O_WRONLY / O_RDWR ...
fmode_t f_mode;
loff_t f_pos; // 当前偏移
struct address_space *f_mapping;
/* 其余字段省略 */
};file 是”打开实例”——同一 inode 可有多个 file(多次 open)。
六、操作表模式
VFS 的设计模式:每层一个操作表(函数指针结构体),具体 FS 填充实现。
// ext4 注册
static const struct inode_operations ext4_dir_inode_operations = {
.create = ext4_create,
.lookup = ext4_lookup,
.link = ext4_link,
.mkdir = ext4_mkdir,
/* 其余成员省略 */
};这是经典的”C 语言面向对象”。
七、mount 与 vfsmount
struct vfsmount {
struct dentry *mnt_root;
struct super_block *mnt_sb;
int mnt_flags;
};mount -t ext4 /dev/sda1 /mnt → 创建
super_block + vfsmount + 根 dentry。
mount namespace 隔离不同进程看到的挂载树。
八、RCU 路径查找
路径查找入口是
path_lookupat(),正常情况下先进入
rcu-walk 快路径(设置
LOOKUP_RCU):
- 全程不取 dentry 引用计数,也不加锁。
- RCU 读侧锁保护 dcache 链表的遍历。
- 每一步用
read_seqcount_retry(&dentry->d_seq, ...)校验 dentry / inode 是否被并发修改。 - 一路成功 → 完成查找,整条路径几乎无原子操作开销。
- 任一步骤校验失败、需要 I/O、需要
->d_revalidate等慢路径 → 通过unlazy_walk()降级到 ref-walk 模式:取引用计数、清掉LOOKUP_RCU,从该 dentry 开始按常规加锁方式继续。
这套设计让 stat() / access() /
重复打开热路径文件这类高频操作几乎只付出内存访问成本。详细的状态机和
mount 穿越在下一篇 路径名解析
展开。
九、观察
# dcache / inode cache 大小(按 slab cache 计)
cat /proc/slabinfo | grep -E 'dentry|inode_cache'
# 当前挂载
cat /proc/mounts
# 内核已注册的文件系统类型
cat /proc/filesystems
# 系统级打开文件数
cat /proc/sys/fs/file-nr
# 输出三列:allocated unused max
# negative dentry 计数等
cat /proc/sys/fs/dentry-state十、小结
- VFS 四层:superblock(FS 实例)→ inode(文件元数据)→ dentry(路径缓存)→ file(打开实例)
- ops 表是 C 语言的多态
- dcache + negative dentry 让路径查找命中内存
- RCU lookup 让热路径零锁
参考文献
Documentation/filesystems/vfs.rst(最权威)- Bovet & Cesati, “Understanding the Linux Kernel” 3rd §12
- Robert Love, “Linux Kernel Development” 3rd §13
include/linux/fs.h、include/linux/dcache.h
工具
/proc/slabinfo:观察dentry、inode_cache占用/proc/mounts、/proc/sys/fs/dentry-state、/proc/sys/fs/file-nrmountstats(来自 nfs-utils,可用于 NFS 挂载延迟拆解)bpftrace -e 'kprobe:vfs_read { @[comm]=count(); }'之类的 eBPF/kprobe 观测 VFS 入口
上一篇:用户态分配器:jemalloc vs tcmalloc 下一篇:路径名解析
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【存储工程】文件系统基础:inode、目录与 VFS
磁盘是一个线性的块数组,但没有人愿意用"第 48372 号扇区"来定位自己的文档。文件系统(File System)就是那个把"名字"映射到"数据"的翻译层——它把一片平坦的块空间组织成人类可以理解的层级结构,同时维护着每个文件的权限、大小、时间戳等元数据(Metadata)。
【操作系统百科】VFS I/O 路径全景
一次 read(fd, buf, n) 从用户态到磁盘要穿过多少层?本文追踪 VFS read/write 全路径——file_operations、iter_iov、iomap、页缓存命中与 miss、直接 I/O 旁路、块层提交、fsnotify 插桩。
【操作系统百科】内核内存调试
内核内存 bug 是最难追的:UAF、OOB、double free、leak 都可能沉默数月。本文讲 KASAN 三种模式、KFENCE 生产采样、kmemleak、SLUB_DEBUG、UBSAN/KCSAN 联动。
操作系统百科
Linux 6.x 视角下的操作系统系列索引:110 篇覆盖调度、虚拟内存、文件系统与 I/O、并发、隔离、可观测性,按主题、阅读路径与关键问题三种入口组织。