文件管理(四)—— 文件存储空间管理

前言

文件物理结构解决的是非空闲磁盘块 的组织问题,而本节讨论的存储空间管理,则是对空闲磁盘块的管理。核心问题围绕三个角度展开:① 用什么数据结构记录空闲块?② 如何分配磁盘块?③ 如何回收磁盘块?

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\text{盘块号} = n \times i + j 盘块号=n×i+j

i= ⌊盘块号n⌋ ,j=盘块号  mod  n i = \left\lfloor \frac{\text{盘块号}}{n} \right\rfloor, \quad j = \text{盘块号} \bmod n 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)表示结束。

需要注意:

  1. 每组中的盘块号不要求连续;
  2. 每组最多保存固定数量的空闲盘块信息;
  3. 由于每组需要占用一个盘块保存下一组信息,因此该盘块不能直接作为普通空闲盘块使用。

5.2 分配过程

分配一个盘块

假设超级块中记录:

复制代码
空闲块数 = 100   空闲盘块:201,202,...,300

当文件需要一个盘块时:

  1. 操作系统直接从超级块中取出一个空闲盘块号,例如 201;
  2. 将盘块 201 分配给文件;
  3. 修改超级块中的空闲块数量:100 → 99

由于超级块已经在内存中,因此整个过程不需要访问磁盘。

分配一整组盘块

如果第一组空闲盘块全部被分配,只剩下最后一个盘块(例如300号盘块)时,需要特殊处理。

原因:

300号盘块中保存了下一组空闲盘块的信息,如果直接分配,会导致后续空闲盘块丢失。

因此步骤如下:

  1. 先读取300号盘块中的下一组信息;
  2. 将下一组信息复制到超级块中;
  3. 再将300号盘块分配给文件。

处理后:

复制代码
原:超级块 → 300号盘块 → 下一组

变为:超级块 → 下一组

保证超级块始终保存当前可用的第一组信息。

5.3 回收过程

回收盘块时,需要把释放的盘块重新加入第一组。

情况一:当前组未满

超级块:空闲块数 = 80

此时还有空间记录新的空闲盘块。

回收一个盘块:

  1. 将该盘块号加入当前组;
  2. 修改空闲块数:80 → 81

即可完成回收。

情况二:当前组已满

超级块:空闲块数 = 100

当前组已经无法继续加入新的盘块。

此时:

  1. 将超级块中当前组的信息复制到被回收的盘块中;

    复制代码
    回收盘块:
    
    保存旧第一组的信息
  2. 修改超级块,使其指向这个新的盘块;

  3. 将超级块中的空闲块数设置为 1。

结果:新的空闲盘块组被插入链表最前端。

6. 小结

方法 数据结构 适用分配方式 适用规模
空闲表法 空闲区间表 连续分配 小型
空闲盘块链 单块链表 离散分配 小型
空闲盘区链 盘区链表 连续/离散 中型
位视图法 二进制位串 连续/离散 通用
成组链接法 分组链 + 超级块 离散分配 大型(Unix)
相关推荐
liang_jy1 小时前
文件管理(三)—— 文件目录
面试·操作系统
feng尘6 小时前
深度解析布隆过滤器(Bloom Filter):原理、优缺点与 1000 万黑名单实战
后端·面试
触底反弹8 小时前
🔥 从 MySQL 到 Milvus:用 AI 日记本项目搞懂向量数据库和 RAG
javascript·人工智能·面试
网络安全零基础教程8 小时前
网络安全春招面试避坑与复盘指南
linux·网络·安全·web安全·面试·职场和发展
众人皆醒我独醉8 小时前
AI 怎么"理解"一个词的意思?—— Embedding 是把整个世界压缩成一组数字
人工智能·面试
windliang10 小时前
Claude Code 源码分析(一):从 claude 命令到 Agent 主循环
javascript·面试·ai编程
小宇子2B10 小时前
shrinker:页 LRU 之外的 VFS(虚拟文件系统)缓存怎么回收
操作系统
ShineWinsu13 小时前
对于Linux:自定义协议(基于TCP)实现网络计算器的解析
linux·网络·c++·网络协议·tcp/ip·面试·网络计算器
HeiSenBerg13 小时前
Kotlin Flow 从冷到热:一篇撑起面试的流式编程指南
面试