虚拟文件系统
好的,我们来深入探讨一下虚拟文件系统(Virtual File System, VFS) 以及其核心的四大对象。
什么是虚拟文件系统?
虚拟文件系统是操作系统内核中的一个软件抽象层。它并不是一个真正的文件系统(如 ext4、NTFS、FAT32),而是存在于内存中的一套通用接口和数据结构。
核心目的与要解决的问题
想象一下,你的 Linux 系统上同时挂载了:
- 一个
ext4格式的根分区 - 一个
XFS格式的数据分区 - 一个
NTFS格式的 Windows 分区(通过ntfs-3g驱动) - 一个通过网络访问的
NFS共享目录
如果没有 VFS,应用程序(如 cat、cp)需要知道每种文件系统的具体细节才能读写文件。这几乎是不可能的,因为:
- 复杂性:应用程序代码会变得极其复杂。
- 可移植性差:添加一个新的文件系统类型需要修改所有应用程序。
- 统一访问:无法使用
cp命令在ext4和NTFS分区之间拷贝文件。
VFS 完美地解决了这些问题。它通过在具体的文件系统之上建立一个抽象层,为应用程序和内核的其他部分提供了一个统一、一致的文件操作接口。
VFS 如何工作?
VFS 定义了所有文件系统都必须支持的基本概念和操作(如“打开”、“读”、“写”、“创建链接”等)。当应用程序发出一个系统调用(如 read())时:
- 应用程序调用通用的
read()函数。 - 内核/VFS层接管这个调用。它处理路径解析、权限检查等通用逻辑。
- VFS 找到目标文件,并确定它位于哪个具体的文件系统上(比如
ext4)。 - VFS 调用该具体文件系统 提供的、用于实现
read操作的具体函数。这些函数是文件系统驱动的一部分。 - 具体的文件系统驱动 与硬件(或网络)交互,执行真正的数据读取。
- 数据返回给 VFS,再最终返回给应用程序。
对应用程序来说,它只是在和一个“统一的文件系统”打交道,完全感知不到底层是 ext4 还是 NTFS。这就是 VFS 的魔力所在。
VFS 的四大对象(数据结构)
VFS 的核心是四大对象(或称为结构体)。它们是在内存中创建的数据结构,用于代表和管理文件系统中的各种实体。这些对象都包含一个关键部分:操作函数集。
1. 超级块对象
- 代表什么? 代表一个已挂载的文件系统。每次你挂载一个磁盘分区(如
/dev/sda1到/home),VFS 就会在内存中创建一个超级块对象。 - 包含什么信息?
- 文件系统的类型(如 ext4, xfs)。
- 文件系统的块大小、总块数、空闲块数。
- 指向具体文件系统超级块读取/写入函数的指针(
super_operations)。 - 指向该文件系统的根目录的目录项对象(dentry) 的指针。
- 作用:它是文件系统的“名片”,内核通过它来了解这个挂载实例的整体信息。
2. 索引节点对象
- 代表什么? 代表一个文件本身(注意:这里的“文件”包括普通文件、目录、设备文件等一切在文件系统中的东西)。一个 inode 对应磁盘上的一个 inode,但它是内存中的表示,包含了更多运行时状态。
- 包含什么信息?
- 文件的元数据:权限 (
i_mode)、所有者 (i_uid,i_gid)、大小 (i_size)、时间戳 (i_atime,i_mtime,i_ctime)。 - 指向文件数据块在磁盘上位置的指针。
- 引用计数(有多少个进程正在使用此文件)。
- 指向具体文件系统 inode 操作函数的指针(
inode_operations)和文件操作函数的指针(file_operations)。
- 文件的元数据:权限 (
- 关键点:inode 不存储文件名。它只存储文件的唯一标识和属性。
3. 目录项对象
- 代表什么? 代表一个目录项,即路径中的一个组成部分。例如,路径
/home/user/file.txt由四个目录项组成:/、home、user、file.txt。 - 为什么需要它? 为了提高性能。解析路径(如
/home/user/file.txt)是一个非常频繁且昂贵的操作(需要多次读取磁盘)。目录项对象在内存中构建一个目录项缓存,用于缓存最近访问过的目录结构,使得路径查找速度极快。 - 包含什么信息?
- 目录项名称(如 “user”)。
- 指向其父目录项和子目录项的指针(用于构建缓存树)。
- 指向该目录项对应的索引节点对象的指针。
- 指向具体文件系统目录项操作函数的指针(
dentry_operations)。
- 重要特性:目录项对象是动态生成的,是内存中的结构,没有直接的磁盘对应物。它纯粹为了提升性能而存在。
4. 文件对象
- 代表什么? 代表一个进程与一个已打开文件的交互上下文。
- 关键理解:一个文件(inode)可以被多个进程同时打开(如
vi和tail -f同时操作一个文件)。每个进程的“打开”操作都会创建一个独立的文件对象。它描述了进程如何与文件交互。 - 包含什么信息?
- 当前的读写偏移量(cursor position)。每个进程都有自己的偏移量。
- 打开文件时指定的模式(只读、只写、读写等)。
- 指向该文件对应的目录项对象的指针(从而找到 inode)。
- 指向具体文件系统文件操作函数的指针(
file_operations),如read,write,mmap等。
四者关系总结与一个生动的比喻
让我们用一个政府档案馆的比喻来理解它们的关系:
- 超级块:就像整个档案馆的建筑蓝图和管理规定。它描述了档案馆有多大,有哪些区域,遵守什么规则。
- 索引节点:就像档案馆里的一份原始档案袋。袋子上贴着所有元数据(档案编号、创建日期、保密等级、存放位置),但不写档案名称。袋子里是实际的内容(数据块)。
- 目录项:就像档案馆的索引卡片和指引路标。它告诉你“某某名称的档案”对应“哪个编号的档案袋”。这些卡片和路标(目录项缓存)放在方便查阅的大厅里(内存),让你能快速找到目标,而不用每次都跑进档案库深处(磁盘)去翻找。
- 文件对象:就像一位研究员获得的调阅许可。许可上规定了研究员可以在哪个房间阅读档案、可以从哪一页开始读(偏移量)、是只能看还是可以做笔记(模式)。多个研究员可以同时调阅同一份原始档案(inode),但各自拿着自己的许可(文件对象),互不干扰。
当进程打开一个文件时,内核会为它创建这样一个上下文(文件对象),并通过目录项缓存快速找到对应的 inode,最终通过超级块知道如何去操作底层的具体文件系统。这个过程高效且统一,完美地隐藏了不同文件系统的差异。
Do not communicate by sharing memory; instead, share memory by communicating.

浙公网安备 33010602011771号