目录

简单反推文件系统

2025年11月17日

俺对 Linux 文件系统(VFS)的理解

我最近复习了一下文件系统,按我的理解,其实一开始系统里最核心的东西就两个:superblockinode

  • superblock 表示整个文件系统的元信息
  • inode 表示某个文件本身的元数据

从最简化的情况来说,这俩东西就能构建一个最基础的文件系统了。 另外,在传统 UNIX 的设计里,inode 本身就 不保存文件名,也不记录自己父目录是谁,这一点很关键,也是后面很多结构出现的原因。


为什么需要 dentry?

后来 Linux 又搞了一个 dentry(目录项) 出来,本质是为了优化路径查找。

因为 inode 里啥名字都没存,也不知道自己在哪个目录下,所以如果我们完全靠 inode 一层层解析目录文件,那每次都要读磁盘,特别慢。

Linux 于是做了这样一件事:在 VFS 层搞一个 “内存版的目录项”,也就是 dentry,它大概包含:

  • name(文件名)
  • inode(对应的节点)
  • parent dentry(父目录的内存项)

有了这个结构以后:

  • 路径解析能非常快地完成
  • 绝大部分路径都会被缓存,不必反复去读磁盘
  • 没命中再去磁盘读真正的目录文件,把结果填入 dentry cache

需要说明的是:

dentry 是内存结构,不是磁盘结构。磁盘上的目录项还是各文件系统自己格式存的。

所以我现在更愿意把 dentry 理解为: 给路径解析按了一个很凶的缓存加速器。


为什么需要 file?

接着是 file 结构,它不是文件本体,而是:

进程打开某个文件时产生的“文件实例”

为什么要有 file?很简单: 我们要把 文件是什么(inode)进程怎么用它(偏移量、flags、权限等) 分开。

所以 file 主要解决两个问题:

  • inode 是文件本体,不属于任何进程
  • file 是某个进程对这个文件的“会话状态”(offset、open flags 等)

多个进程打开同一个 inode 时,各自的偏移量不会干扰。 file 里还挂着对应的操作方法(read/write 等),同一个 inode 在不同上下文下也能以不同方式访问。


缓存 & 引用计数

dentry、inode、file 都有引用计数:

  • 引用计数不为 0 就不会被回收
  • dentry 和 inode 都有自己的缓存池(dcache / icache)
  • file 基本不缓存,因为它严格属于进程的打开状态

命中缓存就不走磁盘,miss 才往底层文件系统要数据。

引用计数这种设计在内核里太常见了,这里也一样。


这些结构很早就有吗?

superblock、inode、file 这些东西在传统 UNIX 里就已经有雏形了。 但 dentry 是 Linux 自己搞出来的加速路径解析的机制,在 2.x 时代才真正成体系。

另外,Linux 把整套结构统一放在 VFS 这一层,底下 ext4、xfs、btrfs、NFS 等文件系统都共享这套路径解析逻辑,这是它体系结构里很重要的一点。


小结

最开始 superblock + inode 就能搞出一个可用的文件系统,但为了让路径解析快一些,才出现了 dentry;为了让进程能独立管理打开的文件而不互相影响,才出现了 file。 dentry 和 inode 都带缓存和引用计数,命中就不下磁盘,整个 VFS 的性能也就上去了。 这些设计看起来多,但其实不是胡乱堆的,而是一步一步从功能和性能需求里演化出来的。


注:本文只是我反推了一遍 VFS 的设计思路,大概猜测先人可能也是这样一步步考虑的。