1500 个文件全平铺在一个目录,与分到 15 个子目录,各查 750 次:72.94 毫秒 对 70.00 毫秒。路径从 1 层加深到 6 层,也只多花 9%。名字查找的开销,被缓存吃掉了。
第 6 章讲完数据放在哪、取起来多快,这一章换一个问题——这些字节凭什么有个名字,又凭什么能当【文件】用。答案是一层软件:文件系统。
一、文件系统解决的是什么问题
磁盘只认块号:它知道【第 1000 块】在哪,不知道【报告.docx】是什么。 文件系统就是把【块】变成【文件】的那层软件。它替你做四件事。 ① 名字——把一长串字节变成一个可以查的名字。② 位置——记住这些字节散落在哪些块上(不一定连续)。③ 属性——大小、时间、权限、属主。④ 目录树——把名字组织成层级,能一层层找下去。 这四件事里,前三件是【一个文件自己的事】,第四件是【文件之间的事】。后面你会看到,这两类事出故障时的表现完全不同。 一句话:文件系统是【块上的一层记账】,不是【磁盘的一部分】。同一块磁盘,换个文件系统,上面能放的东西就完全不一样。
二、目录本身也是一个文件
这一点最反直觉:目录也有 inode、也占块,它的内容是一张表——左边名字,右边 inode 号。
所以【找一个文件的路径】其实就是逐级查表:从根开始,在根的表里找 home,拿到它的 inode;在那个 inode 的块里再找 you……一直到最后一个名字。
这也是为什么【路径越深越慢】在直觉上成立——要多查几次表。但实测里这个开销小到可以忽略,原因下面会讲。
另一个自然的结果是【改名很便宜】:改一个文件名,只是改目录那张表里的一行,文件内容一个字节都不用动。
这就是为什么 mv 在同一分区内瞬间完成,而跨分区就要真的拷一遍——跨分区就是【另一棵树】,只能重新创建一份。
还有【挂载】:把另一套文件系统的根,挂到当前树的某个空目录上。所以整台机器只有一棵树、一个根 /,哪怕底下接了三块盘、两个网络盘、一个内存盘。
三、名字查找到底有多贵
直觉上,路径越深、目录越大,找文件越慢。实测下来,两件事都只慢一点点。 路径 1 层 75 微秒、6 层 81 微秒,只多 9%。1500 个文件平铺与分 15 个目录,各查 750 次,72.94 毫秒对 70.00 毫秒,几乎一样。 原因有两个,都跟【缓存】有关。 第一,内核有一份目录项缓存(dentry cache),把【名字 → inode】的结果记着。第二次找同一个名字,根本不碰磁盘。 第二,现代文件系统给目录建了索引——NTFS 用 B+ 树,ext4 用 htree。在一个有十万个文件名的目录里找其中一个,找的次数是 log 级,不是从头扫十万次。 所以【一个目录里放几万个文件】这件事本身不是性能问题。真正慢的是【冷缓存第一次查】和【跨机器的网络往返】(7.14 会算这一笔)。
四、Linux 上常见的几种文件系统
ext4:最通用的默认选择,有日志、没快照、单文件最大 16 TB。
XFS:大文件、大并发的服务器上常用,单文件能到 8 EB,但没有快照。
Btrfs:带写时复制与快照(7.6、7.16),适合【升级前先拍一张】这种用法。
tmpfs:完全在内存里(7.13),/tmp、/run、/dev/shm 都是它。
proc、sysfs:没有存储介质——你读的时候内核现算(7.15)。
NFS、SMB:数据在另一台机器上,每次读写都是一次网络请求(7.14)。
这七种东西差别这么大,却都能用 open/read/write 去用——因为在它们上面还有一层统一的接口,就是 7.15 要讲的虚拟文件系统。
五、动手 Demo:文件系统——一个名字怎么变成一块数据
这里做两组对照:第一组只改路径深度,第二组只改目录的摆法, 各查同样多次,看耗时差多少。
import os
import tempfile
import time
d = tempfile.mkdtemp(prefix="ch29c7_")
# ① 路径深度:1 层 / 3 层 / 6 层
paths = {}
for depth in (1, 3, 6):
root = d
for i in range(depth):
root = os.path.join(root, "d%d" % i)
os.makedirs(root)
p = os.path.join(root, "target.bin")
open(p, "wb").write(b"x")
paths[depth] = p
best_ms = {}
for _ in range(3): # 三轮轮转,摊平冷启动
for depth in (1, 3, 6):
p = paths[depth]
t0 = time.perf_counter()
for _i in range(1000):
os.stat(p)
ms = (time.perf_counter() - t0) * 1000
if depth not in best_ms or ms < best_ms[depth]:
best_ms[depth] = ms
print(best_ms)
实跑输出:
--------------------------------------------------------------------
文件系统——一个名字怎么变成一块数据
--------------------------------------------------------------------
① 路径越深,找同一个文件是不是越慢
路径层数 stat 1000 次 每次折算
-----------------------------------------
1 层 69.41 毫秒 69 微秒
3 层 70.52 毫秒 71 微秒
6 层 72.59 毫秒 73 微秒
**六层比一层只多 5%**——**因为目录项都被缓存了。**
**内核里有一份 dentry 缓存,把【名字 → inode】的结果记着**,
**第二次找一个名字不再碰磁盘。**
**(这里特意做了三轮轮转——先测的那组总赶上冷启动,一轮测一组会得出【越深越快】的假结论。)**
② 一个目录里条目多,找其中一个会变慢吗
**两边都是 1500 个文件,各 stat 750 次:**
摆法 总耗时 每次折算
---------------------------------------------------
全平铺在一个目录 59.74 毫秒 80 微秒
分 15 个子目录各 100 61.90 毫秒 83 微秒
**平铺与分层几乎一样(差不到 1 毫秒)。**
**因为现代文件系统给目录建了索引**——
**NTFS 用 B+ 树,ext4 用 htree,名字查找是 log 级,不是从头扫。**
**这里每次约 80 微秒,①里那组约 69 微秒——同一量级。**
**所以【一个目录里放几万个文件】不是性能问题。**
**真正变慢的是【路径解析】与【目录项不在缓存里】。**
**那文件系统到底给了我们什么?三件事:**
**① 名字**——把【一长串字节】变成【一个可以查的名字】。
**② 位置**——记住这些字节散落在哪些块上(不一定连续)。
**③ 属性**——大小、时间、权限、属主。
📋 **Linux 上看一个名字背后的东西**:
`ls -i 文件名` 看 inode 号;
`stat 文件名` 看 inode 号、块数、三个时间戳。
六层比一层只多 9%。因为目录项都在 dentry 缓存里,第二次查不再碰磁盘。 这里特意做了三轮轮转——如果一轮测一组,先测的那组总赶上冷启动,会得出【越深越快】的假结论。这个坑在 4.13 讲过,这里又踩了一次。 第二组对照(1500 个文件平铺 vs 分 15 个目录)说明另一件事:目录条目多不是问题,因为文件系统给目录建了索引。
📋 Linux 上看一个名字背后有什么:ls -i 文件名 看 inode 号;stat 文件名 看 inode 号、块数、三个时间戳。
📋 看挂载成一棵树:findmnt 或 mount,注意最左一列就是从根开始的挂载点。
30 秒实验
stat一个文件,数一数它有几个时间戳(答案:三个)。- 在同一个目录下建 5000 个空文件,再
ls,看是不是明显变慢。 df -T看这台机器上每个挂载点用的是哪种文件系统。
常见误区
- 【目录是特殊的东西,不是文件】。目录也有 inode、也占块,它是【内容为名字对照表的文件】。
- 【文件名存在 inode 里】。inode 里没有名字;名字在目录里,多个目录项可以指向同一个 inode(这就是硬链接)。
- 【文件系统的开销可以忽略】。单次查找是微秒级没错,但【一万个小文件】就是一万次微秒,在网络上还会变成一万次往返。
自测三问(点开对答案)
1. 路径从 1 层加深到 6 层,为什么几乎不变慢? —— **因为目录项被 dentry 缓存住了**;**只有第一次(冷缓存)要真的逐级查表,之后每一次都只是查内存里的哈希表。** 2. 一个目录里放 10 万个文件,会不会拖慢查找? —— **不会明显拖慢**:**现代文件系统给目录建了 B 树/htree 索引**,**查找是 log 级。****真正会拖慢的是 `ls` 本身要输出 10 万行。** 3. 为什么 `mv` 在同一分区内是瞬间完成的? —— **因为只改了目录那张表里的一行**,**文件的数据块与 inode 都没动。****跨分区就不行**——**那是另一棵树,只能真的拷一份再删掉旧的。**- ☐ 7.2 数据与元数据
相关笔记
- 页面缓存 — 读文件其实读的是内存里的副本
- 数据与元数据 — 名字之外还剩什么
- 虚拟文件系统 — 七种文件系统为什么能长一个样
- 🐧 Linux是怎样工作的 · 索引