前言
文件物理结构解决的是非空闲磁盘块 的组织问题,而本节讨论的存储空间管理,则是对空闲磁盘块的管理。核心问题围绕三个角度展开:① 用什么数据结构记录空闲块?② 如何分配磁盘块?③ 如何回收磁盘块?
1. 存储空间的划分与初始化
文件卷(逻辑卷/逻辑盘):即 Windows 中的 C 盘、D 盘等,是操作系统管理文件的基本单位。一个物理磁盘可以划分成多个文件卷;某些系统也支持多个物理磁盘合并为一个文件卷(用于超大型文件系统)。
每个文件卷内部进一步划分为两个区域:
| 区域 | 存放内容 |
|---|---|
| 目录区 | 文件目录(FCB/inode)、空闲表、位视图、超级块等管理数据 |
| 文件区 | 普通文件的实际数据 |
2. 空闲表法
用一张空闲表记录每个连续空闲区间的起始盘块号和长度,与内存管理中的动态分区分配空闲表原理相同。
分配 :采用首次适应、最佳适应或最坏适应等算法,从空闲表中找到满足需求的连续空闲区,摘出所需块并更新表项。适用于连续分配的物理结构。
回收:回收时需根据回收区与已有空闲区的相邻关系分四种情况处理(与动态分区分配回收相同):
| 情况 | 处理方式 | 表项数量变化 |
|---|---|---|
| 前后都无相邻空闲区 | 新增一个表项 | +1 |
| 前后都有相邻空闲区 | 合并为一个表项 | -1 |
| 仅前面有相邻空闲区 | 扩展前面表项 | 不变 |
| 仅后面有相邻空闲区 | 扩展后面表项 | 不变 |
空闲表法不适合大型文件系统,因为空闲表可能变得很长。
3. 空闲链表法
以链表形式组织空闲块,分为两种变体,盘块(block)是磁盘分配的基本单位,盘区(extent)是一组连续盘块组成的区域:
3.1 空闲盘块链
以单个盘块为节点组成链表,每个空闲盘块中存有指向下一个空闲盘块的指针。操作系统保存链头和链尾指针。
- 分配 :从链头依次摘下 k 个盘块,修改链头指针。适用于离散分配。
- 回收:将回收的盘块挂到链尾,修改链尾指针。
缺点:分配多个盘块需多次操作,效率偏低。
3.2 空闲盘区链
以连续空闲区间(盘区) 为节点,每个盘区的第一个盘块中记录该区长度和下一个盘区的指针。
- 分配 :用首次适应等算法在链中找合适的盘区;若无连续的 k 个块,可跨盘区拼凑分配。既适用于连续分配 ,也适用于离散分配。
- 回收:与空闲表法类似,注意相邻盘区的合并。
空闲盘区链一次可摘出一整片连续空间,分配多个块时效率高于空闲盘块链。
4. 位视图法(位图法)
用二进制位串 表示磁盘空间状态,每一位对应一个盘块:0 表示空闲,1 表示已分配(具体含义以题目为准)。
4.1 数据组织
位视图通常按字存储,若字长为 n 位,则用 (字号 i, 位号 j) 二元组定位某一盘块。
转换公式 (以字号、位号、盘块号均从 0 开始为例):
盘块号=n×i+j
i=⌊n盘块号⌋,j=盘块号modn
若从 1 开始编号,公式需相应调整。题目中一定要看清楚是从 0 还是从 1 开始。
示例(字长 n = 16):
- 字号 1、位号 10 → 盘块号 = 16×1 + 10 = 26
- 盘块号 31 → 字号 = 31÷16 = 1 ,位号 = 31%16 = 15
4.2 分配与回收
- 分配:顺序扫描位视图,找到 k 个为 0 的位,由字号和位号计算出盘块号,将对应位置 1。连续/离散分配均适用。
- 回收:由回收盘块号计算字号和位号,将对应位置 0。
5. 成组链接法
成组链接法是 Unix 文件系统中常用的一种空闲空间管理方法,适用于大型文件系统。它通过将大量空闲盘块分组管理,避免了空闲表过大或空闲链表过长的问题,提高了空闲空间管理效率。
5.1 数据结构
成组链接法的基本思想是:
将空闲盘块分成若干组,每组保存一批空闲盘块的编号,同时组与组之间形成链接关系。
系统使用超级块(superblock) 保存当前第一组空闲盘块的信息。超级块属于文件系统元数据区域,系统启动时会将其读入内存并长期保存,因此访问第一组信息时不需要读磁盘。
例如:
css
超级块:
[空闲块数=100 | 空闲盘块号:201,202,...,300]
↓
300号盘块:
[空闲块数=100 | 空闲盘块号:301,302,...,400]
↓
400号盘块:
[空闲块数=100 | 空闲盘块号:401,402,...,500]
↓
最后一组:
[空闲块数 | 空闲盘块号列表 | 下一组=-1]
其中:
- 超级块保存第一组空闲盘块的信息;
- 每组的最后一个空闲盘块用于保存下一组空闲盘块的信息;
- 最后一组没有下一组,因此使用特殊值(如 -1)表示结束。
需要注意:
- 每组中的盘块号不要求连续;
- 每组最多保存固定数量的空闲盘块信息;
- 由于每组需要占用一个盘块保存下一组信息,因此该盘块不能直接作为普通空闲盘块使用。
5.2 分配过程
分配一个盘块
假设超级块中记录:
空闲块数 = 100 空闲盘块:201,202,...,300
当文件需要一个盘块时:
- 操作系统直接从超级块中取出一个空闲盘块号,例如 201;
- 将盘块 201 分配给文件;
- 修改超级块中的空闲块数量:100 → 99
由于超级块已经在内存中,因此整个过程不需要访问磁盘。
分配一整组盘块
如果第一组空闲盘块全部被分配,只剩下最后一个盘块(例如300号盘块)时,需要特殊处理。
原因:
300号盘块中保存了下一组空闲盘块的信息,如果直接分配,会导致后续空闲盘块丢失。
因此步骤如下:
- 先读取300号盘块中的下一组信息;
- 将下一组信息复制到超级块中;
- 再将300号盘块分配给文件。
处理后:
原:超级块 → 300号盘块 → 下一组
变为:超级块 → 下一组
保证超级块始终保存当前可用的第一组信息。
5.3 回收过程
回收盘块时,需要把释放的盘块重新加入第一组。
情况一:当前组未满
超级块:空闲块数 = 80
此时还有空间记录新的空闲盘块。
回收一个盘块:
- 将该盘块号加入当前组;
- 修改空闲块数:80 → 81
即可完成回收。
情况二:当前组已满
超级块:空闲块数 = 100
当前组已经无法继续加入新的盘块。
此时:
-
将超级块中当前组的信息复制到被回收的盘块中;
回收盘块: 保存旧第一组的信息 -
修改超级块,使其指向这个新的盘块;
-
将超级块中的空闲块数设置为 1。
结果:新的空闲盘块组被插入链表最前端。
6. 小结
| 方法 | 数据结构 | 适用分配方式 | 适用规模 |
|---|---|---|---|
| 空闲表法 | 空闲区间表 | 连续分配 | 小型 |
| 空闲盘块链 | 单块链表 | 离散分配 | 小型 |
| 空闲盘区链 | 盘区链表 | 连续/离散 | 中型 |
| 位视图法 | 二进制位串 | 连续/离散 | 通用 |
| 成组链接法 | 分组链 + 超级块 | 离散分配 | 大型(Unix) |