一层函数间接多花 85 纳秒(100 万次多 85 毫秒)——而一次磁盘 IO 是几十微秒到几毫秒。所以 Linux 敢在所有文件系统上面再加一层统一接口。
前面十四节讲了七种完全不同的文件系统,它们凭什么能用同一套 open/read/write?答案就是这一层:虚拟文件系统(VFS)。
一、VFS 长什么样
VFS 是一层【统一的抽象】,它规定了四样东西的数据结构:超级块(这个文件系统)、inode(一个文件)、dentry(一个目录项)、file(一个打开的文件)。
每个具体文件系统只要实现一组【操作函数】(open/read/write/lookup/mkdir……),就能挂到这一层下面。这一组函数在内核里叫 file_operations,本质就是一张函数指针表。
所以【调 read 的时候发生了什么】:系统调用进内核,VFS 看你这个文件属于哪个文件系统,从它的函数表里取出 read 那个指针,跳过去执行。
多出来的成本,就是【查一次表、跳一次】。这一节要量清楚:它到底有多贵。
一句话:VFS 是【文件系统的插座标准】。插座统一了,什么电器都能插。
二、这一层要花多少钱
实测:100 万次直接调用 91.53 毫秒,过一次字典查表再调用 176.83 毫秒——每次多 85 纳秒。
注意这 85 纳秒里,绝大部分是 Python 自己查字典的开销。内核里那层间接只是【从结构体里取一个函数指针】,几个纳秒。
关键是拿来跟什么比:一次页缓存命中是几十纳秒;读 SSD 一个块是几十微秒;读机械盘一个块是几毫秒。所以 VFS 那一层的代价是【十万分之一到百万分之一】。
这就是为什么 Linux 敢在【所有文件系统上面】再统一加一层。加了它以后,open/read/write 只写一套,ext4、XFS、Btrfs、tmpfs、NFS、/proc 全都接得上。
如果这一层要贵 10%,结论就完全反过来了——工程上的取舍往往就取决于【差了几个数量级】这一件事。
三、/proc 与 /sys:没有存储的文件系统
/proc 下面那些文件,磁盘上一个字节都没有。你 cat /proc/meminfo 的时候,内核【现算一遍】再把结果格式化给你。
所以它的大小永远显示 0,读一次的结果也可能每次不同。写它也不是【存起来】,而是【告诉内核做一件事】——比如 echo 1 > /proc/sys/net/ipv4/ip_forward 是改一个内核参数。
/sys 是同一套思路的另一半:/proc 偏【进程与内核状态】,/sys 偏【设备与驱动】。你在 /sys 里看到的目录结构,就是设备的拓扑关系。
它们证明了 VFS 的抽象是真的抽象:一个【没有存储介质、每次读都现算】的东西,居然能接在同一套接口下,于是 cat、grep、awk、watch 全都能直接用在它身上。
四、一切皆文件的真正含义
不是【所有东西都是磁盘上的文件】。而是【所有东西都能用同一套 open/read/write/close 去用】。
键盘、屏幕、磁盘、网络套接字、进程信息、内核参数,接口长一个样。所以 shell 里的 < > | 才能对任意东西生效。
这个设计的实际收益非常大:grep 不需要知道自己在读磁盘文件还是读设备还是读网络;tee 不需要知道自己写的是屏幕还是文件;watch 不需要知道数据是现算的还是读出来的。
代价是【有些操作对某些东西无意义】:对 /proc/meminfo 调 seek 没意义,对管道调 lseek 会报错。所以每一层都要【声明自己支持哪些操作】——这就是 file_operations 里那些函数指针允许为 NULL 的原因。
一句话收尾:VFS 用一层极便宜的间接,换来了整个系统【用一种方式描述一切】的能力。
五、动手 Demo:虚拟文件系统——多一层中转,贵不贵
这一节量一件事:多一层【查表再跳过去】的调用,每次要多花多少纳秒。 先量出来,再拿它跟一次磁盘 IO 比一比。
import time
N = 1000000
def impl(x):
return x + 1
TABLE = {"a": impl} # 相当于 file_operations
def direct():
t = 0
for _ in range(N):
t = impl(t)
return t
def indirect():
t = 0
for _ in range(N):
t = TABLE["a"](t) # 查表再跳
return t
for name, fn in (("直接调用", direct), ("查表再调用", indirect)):
t0 = time.perf_counter()
fn()
ms = (time.perf_counter() - t0) * 1000
print(name, "%.2f 毫秒" % ms)
实跑输出:
--------------------------------------------------------------------
虚拟文件系统——多一层中转,贵不贵
--------------------------------------------------------------------
① 先量一层函数间接的代价
调用方式 1000000 次耗时 每次多花
-------------------------------------------------
直接调用 81.82 毫秒 —
查一次表再调用 150.09 毫秒 68 纳秒
**这 68 纳秒里,绝大部分是 Python 自己查字典的开销。**
**内核里那层间接只是【从结构体里取一个函数指针】,几个纳秒。**
**跟一次真磁盘 IO(几十微秒到几毫秒)比,是千分之一到百万分之一。**
② 所以虚拟文件系统这一层,代价可以忽略
层 典型代价 相对一次磁盘 IO
----------------------------------------------------
VFS 那层函数指针 几纳秒 百万分之一
页缓存命中 几十纳秒 十万分之一
读 SSD 一个块 几十微秒 1
读机械盘一个块 几毫秒 100 倍
**这就是为什么 Linux 敢在【所有文件系统上面】再统一加一层。**
**加了它以后,open/read/write 只写一套,**
**ext4、XFS、Btrfs、tmpfs、NFS、/proc 全都接得上。**
③ /proc 与 /sys:没有存储的文件系统
**/proc 下面那些文件,磁盘上一个字节都没有。**
**你 `cat /proc/meminfo` 的时候,内核【现算一遍】再把结果格式化给你。**
**所以它的大小永远显示 0,读一次的结果也可能每次不同。**
**这也是【一切皆文件】最漂亮的一处**:
**进程、内存、中断这些本来没有【文件】形态的东西,套上同一套接口之后,**
**`cat`、`grep`、`awk` 全都能直接用了。**
📋 **Linux 上验证**:`ls -l /proc/meminfo`(大小是 0);
`cat /proc/meminfo | head -3`;
`cat /proc/self/status | head -5`(看的是【你自己这条 cat】)。
100 万次多花 85 毫秒,折算每次 85 纳秒。 这 85 纳秒里绝大部分是 Python 自己查字典的开销。内核里那层间接只是【从结构体里取一个函数指针】,几个纳秒。 跟一次真磁盘 IO(几十微秒到几毫秒)比,是千分之一到百万分之一。 所以这一层的代价可以忽略——这就是 Linux 敢在所有文件系统上面再加一层统一接口的原因。 注意这个实验的对照物是 Python,不是内核——它只能说明【一层的量级是纳秒】,不能说明【内核那层具体几纳秒】。这个区别要说清楚。
📋 Linux 上验证:ls -l /proc/meminfo(大小是 0);
📋 cat /proc/meminfo | head -3;
📋 cat /proc/self/status | head -5(看的是【你自己这条 cat】)。
30 秒实验
ls -l /proc/meminfo,为什么大小是 0?cat /proc/self/cmdline | tr '\0' ' ',看它报的是什么。echo 1 | sudo tee /proc/sys/net/ipv4/ip_forward,再cat一次看变了没。
常见误区
- 【/proc 里的文件是假的】。它们有 inode、有权限、能读能写;只是背后不是存储,是内核里的一段代码。
- 【VFS 是一层【翻译】所以很慢】。它只是查表跳转,纳秒级,跟 IO 比可以忽略。
- 【所有文件系统支持的操作都一样】。每个文件系统声明自己支持哪些;在管道上
lseek会返回ESPIPE。
自测三问(点开对答案)
1. VFS 的代价为什么可以忽略? —— **因为它只是【查一次表、跳一次】**,**纳秒级**;**而它要包裹的那些操作是微秒到毫秒级。** 2. `/proc/meminfo` 的大小为什么是 0? —— **因为它背后没有存储**;**每次读都是内核现算一遍再格式化。****所以它没有【字节数】这个概念。** 3. 【一切皆文件】真正的意思是? —— **不是【都是磁盘上的文件】,而是【都能用同一套系统调用】**。**所以 `grep`、`tee`、`watch` 不用关心对面是什么。**- ☐ 7.16 Btrfs
相关笔记
- 文件的种类 — 七种文件靠它统一
- 网络文件系统 — 靠它接口不变
- 系统调用 — 过一趟门本身要多少钱
- 🐧 Linux是怎样工作的 · 索引