Appearance
文件的物理结构
2026 大纲 四(一)6 文件的物理结构。
文件内第 8000 个字节,到底在盘上哪儿
上一节把逻辑结构讲完了,它的产出是一个数:文件内偏移量。 这一节要把这个数变成一个物理位置——第 8000 个字节在哪个盘块的第几个字节。
块内偏移好办,除一下就有了。真正的问题是前一半:文件的第
这是一条映射。而三种分配方式的全部分歧,就在于把这条映射存在哪里:
- 存在目录项里——但那儿只放得下一个数,所以只能要求文件块在盘上连着放, 第
块 起始块号 ,一个加法搞定。这是连续分配。 - 存在数据块自己里——每块末尾留个指针指向下一块。这是隐式链接。
- 抽出来集中存——要么全盘一张大表(FAT),要么每个文件一张小表(索引分配)。
这一个选择推出后面的全部优缺点,两条判据就够用:
支不支持随机存取,看"能不能不读磁盘就算出第
有没有外部碎片,看"分配是否要求物理上连成一片"。只有连续分配要求,所以也只有它有外部碎片,也只有它"必须预知文件大小"、 "增长要整体搬家"——三件事同源。
这一节是 file 章出题最密的一处,而且大题几乎年年落在这里。 后半部分的混合索引计算要练到熟,它的每一步都是固定动作。
一、连续分配
磁盘被划分为大小相等的磁盘块(block),物理结构要解决的就是如何把文件数据分配到这些块上。
连续分配让每个文件占用一组连续的磁盘块,目录项记录起始块号和长度——像在书架上给一本书留一整排连续格子:读起来最快,但中间插不进新书。访问第 起始块号 + i。
目录项: {文件名: "data.txt", 起始块: 5, 长度: 4}
磁盘块: ... | 5 | 6 | 7 | 8 | ...
└── data.txt ──┘| 优点 | 缺点 |
|---|---|
| 支持顺序存取和随机存取 | 产生外部碎片 |
| 顺序读写速度最快(磁头不需要移动) | 文件不能动态增长 |
| 实现简单,只需起始块号+长度 | 必须预先知道文件大小 |
外部碎片是怎么一步步长出来的:一串建删动作在 20 块磁盘上的逐步演化(想看清"总量够却放不下"是怎么发生的时展开)
碎片不是一次分配造成的,是删除与重建反复交替攒出来的。设磁盘有 20 个块,初始全空,依次执行:
| 步骤 | 动作 | 磁盘状态(. 空闲) | 最大连续空闲段 |
|---|---|---|---|
| ① | 建 A(5)、B(4)、C(6)、D(3) | AAAAABBBBCCCCCCDDD.. | 2 |
| ② | 删 B | AAAAA....CCCCCCDDD.. | 4 |
| ③ | 删 D | AAAAA....CCCCCC..... | 5 |
| ④ | 建 E(3)(首次适应,放进 B 留下的 4 块洞) | AAAAAEEE.CCCCCC..... | 5 |
| ⑤ | 建 F(2)(前面只剩 1 块的洞,装不下,只能放到尾部) | AAAAAEEE.CCCCCCFF... | 3 |
到第 ⑤ 步,磁盘上空闲块共
三条推论直接从这里出来:
- 只有"要求连成一片"的分配方式才有外部碎片。 链接分配和索引分配按单块离散分配,任何一个空闲块都能用上。
- 首次适应/最佳适应这些策略只能减缓、不能消除。 它们改变的是洞怎么被挑选,改变不了"删除会把连续区域打断"这件事。
- 唯一的根治办法是紧凑(compaction)——把所有文件搬到磁盘一端合并空洞,代价是把磁盘上大量数据搬一遍。
二、链接分配
隐式链接
每个磁盘块中保留一个指针域指向下一块,目录项记录起始块和结束块——像寻宝游戏,每个线索只告诉你下一站在哪。
目录项: {文件名: "log.txt", 起始块: 2, 结束块: 10}
块2 块5 块8 块10
┌─────┬──┐ ┌─────┬──┐ ┌─────┬──┐ ┌─────┬────┐
│数据 │→5│→│数据 │→8│→│数据│→10│→│数据 │null│
└─────┴──┘ └─────┴──┘ └─────┴──┘ └─────┴────┘| 优点 | 缺点 |
|---|---|
| 无外部碎片 | 只能顺序存取,不支持随机存取 |
| 文件可动态增长 | 指针占用空间 |
| 无需预知文件大小 | 指针损坏会导致后续全部数据丢失 |
显式链接(FAT)
将所有磁盘块的链接指针集中存放在一张文件分配表(FAT, File Allocation Table)中,FAT 常驻内存。
FAT 表:
磁盘块号: 0 1 2 3 4 5 6 7 8 ...
指针值: -1 -1 5 -1 -1 8 -1 -1 10 ...
↓ ↓ ↓
起始块2 → 块5 → 块8 → 块10(结束)| 优点 | 缺点 |
|---|---|
| 支持随机存取(在内存中的 FAT 中查找) | FAT 需要占用内存空间 |
| 检索速度比隐式链接快得多 | 磁盘块越多,FAT 越大 |
为什么要把指针从数据块里抽出来,以及 FAT 到底要占多少内存(想看清 FAT 为什么撑不到大容量盘时展开)
隐式链接把指针塞在数据块内部还有一个隐蔽后果:块内被指针占走几个字节后,每块可用的数据字节数不再是 2 的幂,"第 offset 字节在第几块"就只能真做除法而不能用移位。很多文件系统因此宁可把指针单独抽出来集中存放,保持数据块整块可用——这就是 FAT。
设磁盘容量 1 TB,块大小 4 KB,每个 FAT 表项占 4 B。
第 1 步:把容量换算成块数。
FAT 是每个磁盘块一个表项的数组,表项数完全由块数决定、与文件多少无关。这一点是 FAT 与索引分配最大的结构差别——索引块只为已用的块建项,FAT 为全盘每一块都建项。
第 2 步:乘以表项宽度。
1 GB 的表要常驻内存——这才是 FAT 真正的边界:它不是"占点内存",而是随磁盘容量线性增长,最终大到无法常驻。
第 3 步:把块开大一档再算一次。 块大小改为 32 KB:
块变大 8 倍 → 块数变为
第 4 步:对比位示图。 同一块盘若改用位示图管理空闲空间(每块 1 位):
两者都是"每块一项"的全盘数组,差别只在每项几位——FAT 每项 4 B = 32 bit,位示图每项 1 bit,正好相差 32 倍。但它们回答的问题不同:位示图只回答"这块空不空",FAT 还要回答"这块的下一块是谁",所以位示图不能替代 FAT。
三、索引分配
为每个文件建立一个索引块,索引块中存放该文件所有磁盘块的块号。
目录项: {文件名: "code.c", 索引块: 15}
索引块15
┌────────┐
│ 块号2 │ → 数据块2
│ 块号8 │ → 数据块8
│ 块号5 │ → 数据块5
│ 块号12 │ → 数据块12
└────────┘| 优点 | 缺点 |
|---|---|
| 支持随机存取 | 索引块本身占用磁盘空间 |
| 无外部碎片 | 小文件也需要一个索引块(浪费) |
| 文件可动态增长 | 大文件需要多级索引 |
小文件到底被这一块惩罚得多厉害:四档文件大小的空间利用率(想看清"开销固定一块"的量级时展开)
块大小 4 KB、块号占 4 B:
| 文件实际大小 | 数据块 | 索引块 | 实占块数 | 空间利用率 |
|---|---|---|---|---|
| 1 KB | 1 | 1 | 2 | |
| 4 KB | 1 | 1 | 2 | |
| 40 KB | 10 | 1 | 11 | |
| 4 MB | 1024 | 1 | 1025 |
一个 1 KB 的文件要占掉 8 KB 磁盘(1 个数据块 + 1 个索引块,各自还有内部碎片),而索引块里 1024 个表项只用掉 1 个(4 B),其余 1023 个表项、共 4092 字节全是空的。由此得到一条判断:索引分配的开销与文件大小无关,是固定的一块,文件越小这一块占比越高——混合索引正是为了消掉它。
大文件的索引组织
一个索引块装不下所有块号时有三种方案。下面统一取块大小 4 KB、块号占 4 B,一个索引块可存
① 链接索引:多个索引块用指针串联,最后一个表项指向下一个索引块。
② 多级索引:类似多级页表,用索引块指向索引块。
| 索引级数 | 可寻址的最大文件 |
|---|---|
| 一级 | |
| 二级 | |
| 三级 |
③ 混合索引(Unix 方案):inode 中同时使用直接地址项与一~三次间接地址项。
为什么要留一批直接指针,而不是一上来就用间接?因为绝大多数文件很小:直接地址项让"不超过 12 块(48 KB)"这一类文件在 inode 读进内存后一次索引块都不用读,访问任意一块都只要 1 次磁盘 I/O。这就是"为常见情形优化,为罕见情形保底"。
四部分寻址的是互不重叠的文件区间,所以最大文件大小是它们的和:4 KB 块、4 B 块号、12 个直接项时为
四部分逐项的可寻址块数与容量对照(合计 4 TB + 4 GB + 4 MB + 48 KB ≈ 4.004 TB;想逐级核对每一段各是多少时展开)
| 部分 | 可寻址块数 | 可寻址容量 |
|---|---|---|
| 直接地址项 | ||
| 一次间接 | ||
| 二次间接 | ||
| 三次间接 | ||
| 合计 |
换一组参数的完整走法:从 k 到四部分容量、再到某个偏移量要几次 I/O(想把这类计算的每一步都走一遍时展开)
设磁盘块大小 2 KB、每个块号占 4 B,索引结点中设 8 个直接地址项、一次/二次/三次间接各 1 个。求(1)单个文件最大大小;(2)索引结点已读入内存、无缓存命中时,访问文件偏移量 3 MB 处的一个字节需要几次磁盘 I/O。
第 1 步:算一个索引块能存几个块号。
第 2 步:逐部分算可寻址容量。 每多一级间接就多乘一个
| 部分 | 可寻址块数 | 容量 |
|---|---|---|
| 8 个直接地址项 | ||
| 一次间接 | ||
| 二次间接 | ||
| 三次间接 |
第 3 步:求和。
四部分寻址的是互不重叠的文件区间,所以最大文件大小是它们的和而不是最大的那一项。这一步最容易漏——很多人只答 256 GB。
第 4 步:把累计阈值排出来,定位 3 MB 落在哪一级。
| 累计到 | 覆盖的文件偏移范围 |
|---|---|
| 直接地址项 | |
| + 一次间接 | |
| + 二次间接 |
第 5 步:数 I/O 次数。 每一级间接就是一个必须先读回来才能继续的索引块,最后再读一次数据块:
| 步骤 | 读什么 | I/O |
|---|---|---|
| 1 | 读二次间接指向的一级索引块 | 1 |
| 2 | 读其中一项指向的二级索引块 | 1 |
| 3 | 读数据块(目标字节所在) | 1 |
| 合计 | 3 次 |
换前提怎么调:索引结点不在内存 → 所有数字 +1;读一个完整数据块 vs 读一个字节 → I/O 次数相同(块是最小传输单位);跨块连续读取 N 字节 → 要算多个数据块的 I/O,但中间索引块只读一次;块号改为占 8 B →
四、三种分配方式对比
| 分配方式 | 判据:第 i 块的块号怎么来 | 顺序存取 | 随机存取 | 外部碎片 | 文件增长 | 适用场景 |
|---|---|---|---|---|---|---|
| 连续分配 | 起始块号 + i,纯加法 | 最快 | 支持 | 有 | 困难 | 文件大小固定、只读介质 |
| 隐式链接 | 必须逐块读磁盘才知道下一块 | 支持 | 不支持 | 无 | 方便 | 顺序存取文件 |
| 显式链接(FAT) | 查常驻内存的 FAT,不读盘 | 支持 | 支持 | 无 | 方便 | FAT 文件系统 |
| 索引分配 | 读一次索引块拿到全部块号 | 支持 | 支持 | 无 | 方便 | Unix/Linux |
大文件索引组织:三者怎么选
| 方案 | 定位第 i 块要读几个索引块 | 空间开销 | 什么时候选它 |
|---|---|---|---|
| 链接索引 | 最坏 | 只为已用块建项,最省 | 文件基本顺序访问;随机访问代价随文件增大而线性上升,是三者中唯一不支持高效随机存取的 |
| 多级索引 | 固定 | 小文件也要走满 | 文件大小分布集中(都很大),此时统一级数不吃亏 |
| 混合索引 | 小文件 0 个,大文件最多 3 个 | 小文件不建索引块,大文件按需展开 | 文件大小分布极不均匀(大量小文件 + 少量大文件),即通用文件系统的真实情形 |
判据一句话:看文件大小的分布。 通用操作系统面对的一定是极不均匀那一种,所以 Unix 选了混合索引。
考点速记
- 三条路线的唯一分歧:"第
个逻辑块在哪个物理块"这条映射存在哪里——存目录项(只需起点,靠加法算)→ 连续分配;存数据块自己里 → 隐式链接;抽出来集中存 → FAT 与索引分配。后面所有优缺点都是这一条选择的后果。 - 支不支持随机存取的统一判据:能不能不读磁盘就算出第
块的块号。 连续(起始块号 )、FAT(查常驻内存的表)、索引(读一次索引块拿到全部块号)都支持;⚠️只有隐式链接不支持。 - 外部碎片只有连续分配有,判据是"分配是否要求物理上连成一片"。本质是空闲总量够,但没有一段够长;根治只有紧凑,而磁盘上做紧凑代价太大。⚠️内部碎片四种方式都有。 "必须预知文件大小""增长要整体搬家"与外部碎片同源。
- ⚠️隐式链接与显式链接只差"指针放哪":隐式放在每个数据块内部,要知道下一块必须先读回当前块 ⇒ 不支持随机存取;显式抽出集中成 FAT 并常驻内存,沿链跳转不访问磁盘 ⇒ 支持。这两条最容易记反。
- FAT 与索引块的规模差,判据是为谁建项:FAT 为全盘每一个块建项(哪怕空闲、哪怕属于别的文件),大小只随磁盘容量增长且须常驻内存;索引块只为本文件已占用的块建项。这是 FAT 撑不到大容量盘的根本原因。
- 索引分配的开销与文件大小无关,固定一块:4 KB 块下一个 1 KB 的文件实占 8 KB,利用率仅
。混合索引正是为了消掉这一块——小文件用直接地址项,一个索引块都不建。 - 多级索引通式:
, 级可寻址 。⚠️1024 和 256 都只是 的取值,不是要背的数。 - ⚠️最大文件大小
四部分之和:直接项 + 一次 + 二次 + 三次间接寻址的是互不重叠的文件区间,所以要相加。只答"三次间接 4 TB"漏掉了另外三项。 - 访问 I/O 次数(索引结点已在内存):直接块 1 次 / 一次间接 2 次 / 二次间接 3 次 / 三次间接 4 次;索引结点还要读盘则各 +1。⚠️ 判断落在哪一级要把累计阈值算出来再比——一次间接覆盖的不是"0 起的 1 MB"而是"直接部分之后的 1 MB"。
- 直接地址项个数有教材分歧:UNIX System V 共 13 项、10 个直接;Linux ext2/ext3 共 15 项、12 个直接。做题以题目给出的个数为准。
- 块开大的两头代价:块大 ⇒ 块数少 ⇒ FAT/位示图小、索引层级浅、顺序读一次搬得多;但块大 ⇒ 每个文件平均浪费半个块的内部碎片。
这一节在真题里被考过的形式:
这是 file 章出题最密的一节——十余道题里有五道是大题分问。 但问法只有四类,按类练比按题练省得多。
- ① 给场景问该选哪种分配方式(2009-28、2013-24、2020-24)。判据只有速记第二、三条。 2009-28 问"适合随机访问且易于文件扩展",答索引分配——连续分配随机访问也行但不易扩展 (要整体搬家),链接分配易扩展但隐式的不支持随机访问。2013-24 问"支持 CD-ROM 视频快速随机播放", 答连续分配——只读介质不存在扩展问题,外部碎片这个唯一缺点被场景消掉了, 而它的顺序读性能最好。⚠️ 这一类题的关键是看题干把哪个缺点消掉了。
- ② 算最大文件大小(2010-30、2013-26、2018-46、2022-45)。固定四步: 先由块大小与块号字节数算
→ 分别算直接 / 一次 / 二次 / 三次间接各能寻址多少字节 → 四部分相加 → 化成合适的单位。⚠️ 两处必错:漏加前三部分(速记第八条)、 把题目给的直接地址项个数换成记忆里的 10 或 12(速记第十条)。 - ③ 给一个文件内偏移量,问要几次磁盘 I/O、或该块的块号在哪(2015-29、2018-46、2022-45、2026-46)。 做法是先算出四部分各自覆盖的累计区间,再看这个偏移落在第几段,然后套速记第九条的次数表。 ⚠️ 一次间接覆盖的区间起点是直接部分的终点,不是 0——直接拿"1 MB"去比会判错一级。
- ④ 综合大题:给一套文件系统参数,连着问最大文件、目录项格式、块号位宽、字段优化 (2012-46、2014-46、2016-47、2026-46)。这一类把本章多节串起来, 常见分问有"目录项至少要多少字节"(
文件名长度 + 位换算成字节)、 "把某字段去掉能省多少"、"改成链接分配后插入一条记录要读写多少次盘"(2014-46)。 ⚠️ 2016-47 考的是 FAT 下按名存取的完整流程,要说清"查目录得首块号 → 顺 FAT 链跳转", 而FAT 常驻内存所以跳转不产生磁盘 I/O——这一句是给分点。
复习优先级:必须拿满,且要练熟计算。 第②③类是每年大题的固定动作, 练到不看步骤也能走完;第①类靠速记第二、三条两句判据;第④类没有捷径, 但它的每个分问都能拆回前三类。最贵的两个坑是"四部分要相加"和"累计区间起点不是 0", 这两处每错一次就是整问失分。
易错:把隐式链接和显式链接的随机存取能力记反。指针在数据块里的(隐式)不支持,抽出来成 FAT 的支持。
易错:算最大文件大小时只算三次间接。四部分寻址的是互不重叠的区间,必须相加。
易错:凭记忆用"10 个直接地址项"或"12 个"。教材有两套口径,一律以题目给出的为准。
易错:判断偏移落在哪一级时拿一次间接的容量直接比。一次间接的起点是直接部分的终点,要用累计阈值。
易错:认为索引分配对小文件也划算。开销固定一块,1 KB 文件在 4 KB 块下利用率只有 12.5%。
易错:认为 FAT 和索引块规模相当。FAT 为全盘每一块建项(含空闲块),索引块只为本文件已用块建项。
易错:认为只有连续分配有碎片。外部碎片只有它有,内部碎片四种都有。
易错:认为连续分配一定不如索引分配。只读介质(CD-ROM)上它的唯一缺点被消掉,顺序读性能最好。
教材出处
- 汤小丹《计算机操作系统》印刷版 p258–p259(8.1.4 增量式索引组织方式 / 图 8-8 混合索引方式):UNIX System V 的索引结点设 13 个地址项
i.addr(0)~i.addr(12),其中 10 个直接地址项,4 KB 块时"文件不大于 40 KB 时便可直接从索引结点中读出该文件的全部盘块号";一次间址块可存放 1 K 个盘块号、允许文件长达 4 MB;二次间址 4 GB;三次间址 4 TB。 - 同书 p258 指出多级索引的主要缺点是"访问一个盘块时,其所需启动磁盘的次数随着索引级数的增加而增多,即使是对于小文件也是如此",并据此引出混合(增量式)索引——这正是本篇"为什么要留直接地址项"的依据。
相关知识
文件的逻辑结构|目录|文件元数据与索引节点|外存空闲空间管理|磁盘