操作系统学习笔记 · 第 19 课 · 文件系统——数据怎么存在磁盘上
从这一课起进入第五阶段:存储与 I/O。前面讲的内存是"电"里存的东西,一断电就没了;而你的文件,是要持久地存在磁盘上、关了机还能找回来的。那磁盘上一堆裸字节,是怎么被组织成你看到的"文件、文件夹"的?"文件名"和"文件"是同一个东西吗?这一课,我们走进文件系统。
从"文件名其实是个假象"说起
你在文件管理器里看到的是一棵漂亮的目录树:/home/user/文档/笔记.md。但磁盘上根本不存在"树"——磁盘就是一大片连续的字节。
那这个树状结构是谁"造"出来的?答案是文件系统:它用一套规则,把磁盘上那些裸字节,组织成"文件/目录"这种人类能懂的结构,并记录每个文件的数据放在哪、多大、归谁。
这一课,我们就拆开这套规则看。
19.1 文件系统解决什么问题
一句话理解:裸磁盘是一堆字节,文件系统负责把它们组织成"文件/目录"这种人类能懂的结构,并记录每个文件的数据放在哪、多大、谁拥有。
文件系统要回答三个基本问题:
- 数据放在哪:一个文件的内容,具体落在磁盘的哪些块上?
- 元信息是什么:文件多大、什么权限、谁拥有、什么时候改的?
- 怎么组织成树:目录和文件怎么关联,形成你看到的层级?
一句话:文件系统 = 数据块(存内容)+ 元数据(存描述)+ 目录(存层级关系)。
19.2 inode——文件的"身份证"
这是文件系统里最重要的一个概念,必须吃透:
一句话理解:文件名不是文件的本质,inode 才是。 一个 inode 存文件的所有元信息(大小、权限、拥有者、时间戳、数据块位置),文件名只是指向 inode 的一个"标签"。
很多人以为"文件 = 文件名 + 内容"。其实错了。真正的结构是:
- inode:存这个文件的一切元信息——大小、权限、拥有者、三个时间戳(atime 访问时间 / mtime 修改时间 / ctime 元数据变更时间)、数据块的位置;
- 文件名:只是一个指向 inode 的标签,存在目录里。
一个惊人的推论:一个 inode 可以有多个文件名。你在一个 inode 上挂两个名字,这两个名字指向同一个文件——这就是硬链接(hard link)。
生活类比:inode 是一个"人",文件名是"称呼"。你可以有好几个昵称(硬链接),但本质上是同一个人。删掉一个昵称,人还在;只有当所有昵称都删光、引用计数归零,这个人(inode)才真正消失。
19.3 目录是什么
一句话理解:目录也是一个文件,内容是一张表:文件名 → inode 号。所以"打开文件" = 从目录查 inode → 从 inode 找数据块。目录不是什么特殊东西,它也是一个文件,只不过内容是一张对照表:
文件名 inode 号
a.txt 12345
b.txt 67890所以你要打开 /home/user/a.txt,过程是:
- 从根目录
/查到home的 inode; - 从
home的 inode 查到user的 inode; - 从
user目录里查到a.txt对应的 inode 号; - 从那个 inode 里,找到
a.txt的数据块位置,读出来。
一句话:路径是一层层"查目录表"的过程。这也解释了为什么"目录里不能有两个同名文件"——因为目录表里 文件名 → inode 是唯一的。19.4 分配方式:数据块怎么放
一个文件的内容,在磁盘上怎么放?三种经典方式:
| 方式 | 做法 | 优点 | 缺点 |
|---|---|---|---|
| 连续分配 | 文件占连续磁盘块 | 顺序读极快 | 有外碎片、文件难增长 |
| 链式分配 | 每块存"下一块指针" | 无外碎片 | 随机访问要顺着链走,慢 |
| 索引分配 | inode 存一张"块指针表" | 随机访问快、无外碎片 | 表本身要空间 |
- 连续分配:像磁带,文件内容连续排在一起。顺序读极快,但文件要增长时麻烦(后面没地方了),还会产生外部碎片;
- 链式分配:像链表,每块末尾存"下一块在哪"。没有外碎片,但要读第 100 块,得顺着链走 99 步,随机访问慢;
- 索引分配:inode 里存一张"块指针表",直接记录每个数据块在哪。要读第 100 块,直接查表第 100 项 → 随机访问快,也无外碎片。缺点是要占空间存这张表。
一句话理解:现代文件系统多用索引分配(inode 里存数据块指针,大文件用多级间接块)。
ext4 的做法:inode 里有 12 个直接指针(直接指向数据块,够小文件用)+ 一级/二级/三级间接指针(指向"指向数据块的块",给大文件用)。小文件直接用 12 个直接指针就够,快;超大文件用多级间接,能覆盖巨大空间。
19.5 FAT vs ext4
两种典型文件系统对比:
| FAT | ext4 | |
|---|---|---|
| 组织 | 文件分配表(链式) | inode + 块组(索引) |
| 大文件/性能 | 一般 | 好(extent 连续块) |
| 日志 | 无 | 有(journal) |
| 适用 | U 盘、嵌入式、跨系统 | Linux 主力 |
- FAT:老牌的"文件分配表"方案,本质是链式分配。因为跨平台兼容性好,至今还在 U 盘、相机存储卡里用;
- ext4:Linux 的主力,用 inode + 块组(索引分配),配合 extent(连续区段),大文件性能好,还有日志(下一节)。
一句话:FAT 胜在通用,ext4 胜在性能和可靠性。
19.6 日志(journaling)为什么重要
最后一个关键点:日志。
一句话理解:写文件往往要改多个地方(inode、数据块、目录),中途断电会"改一半"。日志先把"要做什么"记下来,崩溃后能回放或回滚,保证一致性。
问题:写一个文件,可能要同时改 inode、数据块、目录表 好几个地方。如果改到一半突然断电,就会出现"改了一半"的烂摊子——比如数据写了,但 inode 没更新,文件就"丢"了。
日志的解法:
- 写之前,先把"我要做什么"记进日志区;
- 再动手改真正的数据;
- 如果中途崩溃,重启时检查日志:没写完的回滚,写完了但没生效的回放,保证文件系统始终处于一致状态。
所以 ext4 默认开日志,突然断电后重启,系统能自动恢复,很少出现文件系统损坏。这就是"日志"的价值——把"改一半"的风险,用"先记账"来化解。
动手实验:命令观察 inode 和文件占用
touch demo.txt
echo "hello os" > demo.txt
ls -i demo.txt # 看 inode 号
stat demo.txt # 看 inode 详情:大小、块数、时间戳、权限
df -h # 看文件系统总容量/已用
df -i # 看 inode 用量(小文件多会耗尽 inode)
ln demo.txt demo_hard.txt # 硬链接:两个名字同一个 inode
ls -i demo.txt demo_hard.txt # inode 号相同重点观察:
ls -i显示的 inode 号;stat里的Blocks(占用块数,常是 8 块——因为最小分配单位是 4KB,一个文件至少占一个块,stat按 512B 扇区计,所以显示 8)、Inode号、三个时间戳(atime/mtime/ctime);- 建立硬链接后,
demo.txt和demo_hard.txt的 inode 号完全相同——验证了"两个名字指向同一个 inode"。
深入点:两个进阶话题
① extent(区段)
ext4 的"索引分配"里,其实不是逐个块存指针,而是用 extent:
用"起始块 + 长度"描述一段连续块,代替逐个块指针。一个大文件可能只需几条 extent 就描述完了,指针表小、效率高。
② inode 耗尽
一个经典坑:磁盘明明还有空间,却报 "No space left on device"?
可能不是容量满了,而是 inode 用光了——比如目录里塞了海量小文件,每个文件都要一个 inode,inode 先被耗尽。df -i 能看出来(inode 使用率 100% 而容量还有剩)。小结与思考题
这一课,我们拆开了文件系统的内核:
- inode 是文件的本质:存元信息和数据块位置;文件名只是指向它的标签;
- 目录 = 一张"文件名→inode"表:打开文件就是一层层查表;
- 三种分配方式:连续(快但碎片)、链式(无碎片但慢)、索引(随机快,现代主流);
- 硬链接:多个名字指向同一个 inode;
- 日志:先记账再动手,崩溃后能回放/回滚,保证一致性。
留三个问题:
- 文件名和 inode 是什么关系?硬链接的本质是什么?
- 连续分配、链式分配、索引分配各自优缺点?
- 文件系统的"日志"解决什么问题?
文件是怎么存进磁盘的,我们搞懂了。但还有一个问题没问:磁盘这种"外设",到底是怎么跟 CPU 对话的? 你敲键盘、读硬盘,中间经过了什么?是 CPU 一直盯着它,还是它有消息了"喊"CPU 一声?下一课,我们走进 I/O 与设备,看清外设和 CPU 之间的三种对话方式。