文件的存储方式、extent 与空闲空间管理怎么理解
前面把目录项、inode、dentry、fd 这些对象关系先理顺之后,再回头看文件系统,接下来最自然的问题其实是两件事:
- 一个文件的数据到底放在哪
- 文件系统又是怎么知道哪些空间还空着
前者更偏“文件的存储方式”,后者更偏“空闲空间管理”。这两块问题经常会放在一起讲,但本质上其实是在回答两个不同的问题:
文件存储解决的是“这个文件的数据块怎么组织、怎么找到”,空闲空间管理解决的是“还有哪些块可以继续分配”。
所以这一篇我想把这两条线先拆开,再放回到同一张图里看。
一、先从磁盘布局看文件系统在管什么
从最粗的角度看,文件系统在磁盘上通常至少要管理下面这些东西:
- superblock
- inode 区
- 数据块区
- 空闲空间管理结构,例如位图
这里面:
1. inode 区
inode 里存的是文件元信息,以及文件数据位置的映射信息,例如:
- 文件类型
- 权限
- 大小
- 时间戳
- 链接计数
- 数据块映射
2. 数据块区
真正的文件内容和目录内容,最终都在数据块区。
3. 空闲空间管理结构
它负责记录:
- 哪些 inode 还没被占用
- 哪些数据块还空闲
所以从磁盘管理角度看,文件系统至少要同时回答两件事:
- 这个文件已经占了哪些块
- 还有哪些块将来可以继续分配
二、文件存储本质上在回答什么
文件存储本质上是在回答:
一个文件的内容,最终分布在哪些磁盘块上。
比如一个文件大小是 20KB,如果 block 大小是 4KB,那么这个文件大概需要 5 个 block。
问题就在于:
- 这 5 个 block 是不是连续的
- 如果不连续,应该怎么把它们组织起来
- 文件变大了应该怎么扩展
- 删除后这些块怎么回收
这就是文件分配方式真正要解决的事。
三、经典的 3 种文件分配方式
教材里一般会把文件分配方式分成三类:
- 连续分配
- 链式分配
- 索引分配
这里要特别注意,FAT 更准确地说是链式分配的一种改进,而不是完全独立的第四类。至于位图、空闲链表这些,则属于空闲空间管理方式,不属于文件分配方式本身。
1. 连续分配
连续分配的思路最直接:
一个文件占据一段连续的磁盘块。
例如:
file A -> block 10 ~ block 14优点
- 实现简单
- 顺序读写性能很好
- 随机访问也方便,因为偏移很好算
缺点
- 文件增长很麻烦
- 容易产生外部碎片
- 很难兼顾灵活性和空间利用率
所以它虽然直观,但对通用文件系统来说并不够灵活。
2. 链式分配
链式分配的思路是:
文件的每个块里记录“下一个块是谁”。
像链表一样把文件的块串起来。
优点
- 文件扩展比较方便
- 不要求连续空间
- 外部碎片问题没那么严重
缺点
- 随机访问很差
- 查第 n 块要一路往后跟
- 块里还要额外留出空间保存链接信息
FAT 本质上也是链式分配思想,只不过它把“下一块是谁”的关系集中存到了 FAT 表里,而不是散落在每个数据块里。
3. 索引分配
索引分配的思路是:
不在数据块里存链,而是专门建立索引结构,记录这个文件用了哪些块。
inode 就是这个思路下最典型的结构之一。
优点
- 随机访问方便
- 文件块不要求连续
- 比链式更适合通用文件系统
缺点
- 小文件也会有一定索引开销
- 大文件时索引结构会变复杂
传统教材里经常会讲一套经典模型:inode 里有多个块地址入口,其中前面一些是直接指针,后面还有一级、二级、三级间接指针。这套模型很好理解,但在现代文件系统里,已经越来越常被 extent 思想替代。
四、extent 是什么
extent 可以理解成:
不再按“一个块一个块记”,而是按“连续区间”来记。
假设一个文件占了下面这些连续块:
100, 101, 102, 103, 104, 105, 106, 107如果按传统方式一个个记录,就要存 8 个块号。用 extent 就可以直接写成:
(start=100, len=8)表示:
- 从 100 号块开始
- 连续 8 个块都属于这个文件
1. extent 和连续分配的区别
这两个概念很像,但不能混。
- 连续分配要求整个文件都占据一整段连续空间
- extent 只要求某一段内部连续
所以一个文件完全可以由多个 extent 组成,例如:
extent1 = (100, 8)
extent2 = (300, 5)
extent3 = (900, 12)这说明:
- 每个 extent 内部连续
- 整个文件不必全局连续
所以 extent 更像是:
索引分配的一种优化形式,用连续区间来代替“一个块一个块记”。
2. extent 解决了什么问题
extent 的核心价值主要有三点:
- 减少元数据
- 更适合大文件
- 尽量保持顺序读写友好性
如果文件很大,而又经常能拿到成片连续空间,那么 extent 比传统逐块记录更省元数据,也更高效。
五、extent tree 是什么
如果一个文件只有少量 extent,那么 inode 里直接存几个 extent 就够了。
但如果文件很大、很碎,extent 数量变多,inode 里放不下,这时候就需要:
用一棵多级索引树来管理 extent。
这就是 extent tree。
1. extent tree 索引的是什么
extent tree 索引的不是文件名,也不是 inode,而是:
文件逻辑块范围 -> extent / 子节点
换句话说,它真正解决的问题是:
某个文件逻辑块,最终应该映射到哪个 extent。
2. 逻辑块和物理块的区别
这里我后来觉得必须单独区分:
- 逻辑块:站在文件内部看,是 0、1、2、3…… 这样连续编号的块
- 物理块:站在磁盘上看,是具体落在哪些 block 上
所以逻辑块通常是连续的,真正可能不连续的是它们映射到的物理块。
extent tree 管理的,正是:
连续逻辑块区间 -> 连续物理块区间
3. extent tree 的节点怎么理解
我现在更倾向于把 extent tree 理解成一棵 B+ 树风格的多级索引树,但不要把它机械等同成数据库里的那种标准 B+ 树。
更准确地说:
- 非叶子节点存的是逻辑块范围索引项,也就是某个子节点管理范围的最小逻辑块起点,以及对应子节点位置
- 叶子节点才存真正的 extent,也就是逻辑块起点、长度和物理块起点
也就是说:
- 非叶子节点负责找路
- 叶子节点负责给出真正映射
4. 为什么要存逻辑块起点
因为查找时,系统要做的是:
- 给我一个文件逻辑块号
- 找到它落在哪个 extent 里
- 再根据偏移量换算出物理块号
所以 extent 里通常至少要有三样东西:
- 逻辑块起点
- 长度
- 物理块起点
三者缺一不可。
5. extent tree 不一定总是多层
这一点我一开始也容易误解。其实 extent tree 的层数取决于 extent 数量:
- extent 很少时,inode 里直接存 extent 就够了
- extent 多一些时,inode 里放根,下面接叶子
- extent 非常多时,才会逐渐长成多层树
所以它更像是一种可扩展的组织方式,而不是“每个文件都一定有一棵固定形态的大树”。
六、空闲空间管理在解决什么问题
文件存储解决的是“文件已经占了哪些块”,而空闲空间管理解决的是:
还有哪些块没被占用,未来还能分给谁。
如果文件系统不知道哪些块空闲,就无法:
- 创建新文件
- 扩展已有文件
- 删除后回收空间
而且它要同时管理:
- 空闲 inode
- 空闲数据块
七、经典的空闲空间管理方式
1. 位图法
这是最常见也最好理解的一种。
它的思路是:
- 一个 bit 表示一个块是否空闲
0表示空闲1表示已分配
例如:
1 1 0 0 1 0 1表示某些块已经被占用,某些块还是空闲的。
优点
- 简单直观
- 容易判断某个块是否空闲
- 也比较容易找连续空闲区间
缺点
- 位图本身也要占空间
- 磁盘很大时,位图会比较大
但总体来说,位图法非常实用,所以现代文件系统里依然很常见。
2. 空闲链表法
把所有空闲块串成链表。
优点
- 实现简单
缺点
- 查找连续空闲空间不方便
- 随机找空闲块效率一般
- 链表遍历本身有额外开销
3. 成组链接法
这是对链表法的改进。不是每次只记录“下一个空闲块”,而是一次记录一组空闲块号,减少遍历链表的次数。
4. 计数法
如果一段空闲空间本来就是连续的,就可以记成:
(start=100, count=20)也就是从 100 开始有 20 个连续空闲块。
这种方式和 extent 的思路其实很像,都是尽量按区间描述,而不是一个一个记。
八、现代文件系统更常见的组合方式
如果只看现代文件系统的大方向,我现在更愿意这样概括:
索引分配思路 + extent 优化 + 位图/分组管理空闲空间
也就是说:
1. 文件数据位置
更多通过 inode 和 extent / extent tree 来管理。
2. 空闲空间
更多通过位图、分组管理、连续区间优化来记录。
3. 分配策略
文件系统在真正分配块时,通常还会考虑:
- 尽量连续
- 尽量减少碎片
- 尽量让同一个文件、同一个目录相关数据彼此靠近
所以它不是只会“找一个空闲块就完事”,而是会尽量兼顾:
- 性能
- 空间利用率
- 碎片控制
九、小结
把这一块整理完之后,我现在更愿意这样总结文件的存储和空闲空间管理:
- 文件存储解决的是“文件的数据块怎么组织、怎么找到”
- 经典文件分配方式主要有连续分配、链式分配、索引分配
- FAT 本质上更接近链式分配的改进版,不是独立第四类
- 空闲表、位图、空闲链表这些属于空闲空间管理方式,不属于文件分配方式本身
- 现代文件系统更常用 inode + extent / extent tree 来管理文件数据位置
- extent 本质上是“连续区间描述”,一个文件可以由多个 extent 组成
- extent tree 是在 extent 太多时,用来索引逻辑块范围的一棵多级树
- 空闲空间管理则负责记录哪些 inode 和数据块还没被占用,常见方法有位图、链表、计数法等
把这条线搭起来之后,后面再去看 page cache、读写流程、零拷贝、碎片、writeback 这些问题,就更容易知道自己在文件系统地图里的哪个位置了。
