让时间回到 1980 年

5.25" 软盘:单面 180 KiB

  • 360 个 512B 扇区 (sectors)
  • 在这样的设备上实现文件系统,应该选用怎样的数据结构?

需求分析

相当小的文件系统

  • 目录中一般只有几个、十几个文件
  • 文件以小文件为主 (几个 block 以内)

文件的实现方式

  • struct block * 的链表
    • 任何复杂的高级数据结构都显得浪费

目录的实现方式

  • 目录就是一个普通的文件 (虚拟磁盘;“目录文件”)
  • 操作系统会对文件的内容作为目录的解读
    • 文件内容就是一个 struct dentry[];

用链表存储数据:两种设计

  1. 在每个数据块后放置指针
    • 优点:实现简单、无须单独开辟存储空间
    • 缺点:数据的大小不是 $2^k$; 单纯的 lseek 需要读整块数据
  2. 将指针集中存放在文件系统的某个区域
    • 优点:局部性好;lseek 更快
    • 缺点:集中存放的数据损坏将导致数据丢失

哪种方式的缺陷是致命、难以解决的?

集中保存所有指针

集中存储的指针容易损坏?存 $n$ 份就行!

  • FAT-12/16/32 (FAT entry,即 “next 指针” 的大小)

“File Allocation Table” 文件系统

RTFM 得到必要的细节

  • 诸如 tutorial、博客都不可靠
  • 还会丢失很多重要的细节
if (CountofClusters < 4085) {
  // Volume is FAT12 (2 MiB for 512B cluster)
} else if (CountofCluster < 65525) {
  // Volume is FAT16 (32 MiB for 512B cluster)
} else {
  // Volume is FAT32
}

FAT: 链接存储的文件

“FAT” 的 “next” 数组

  • 0: free; 2...MAX: allocated;
  • ffffff7: bad cluster; ffffff8-ffffffe, -1: end-of-file

目录树实现:目录文件

以普通文件的方式存储 “目录” 这个数据结构

  • FAT: 目录 = 32-byte 定长目录项的集合
  • 操作系统在解析时把标记为目录的目录项 “当做” 目录即可
    • 可以用连续的若干个目录项存储 “长文件名”
  • 思考题:为什么不把元数据 (大小、文件名、……) 保存在 vector<struct block *> file 的头部?

Talk is Cheap, Show Me the Code!

首先,观察 “快速格式化” (mkfs.fat) 是如何工作的

  • 老朋友:strace

然后,把整个磁盘镜像 mmap 进内存

  • 照抄手册,遍历目录树 (fat-tree demo),试试镜像

另一个有趣的问题:文件系统恢复

  • 快速格式化 = FAT 表丢失
    • 所有的文件内容 (包括目录文件) 都还在
    • 只是在数据结构眼里看起来都是 “free block”
  • 首先需要猜出文件系统的参数 (SecPerClus, BytsPerSec, FATSz32, BPB_RootClus, ...)
  • 本质上是 cluster 的分类和建立 “可能后继关系”

FAT: 性能与可靠性

性能

  • 小文件简直太合适了
  • 但大文件的随机访问就不行了
    • 4 GB 的文件跳到末尾 (4 KB cluster) 有 $2^{20}$ 次链表 next 操作
    • 缓存能部分解决这个问题
  • 在 FAT 时代,磁盘连续访问性能更佳
    • 使用时间久的磁盘会产生碎片 (fragmentation)
      • malloc 也会产生碎片,不过对性能影响不太大

可靠性

  • 维护若干个 FAT 的副本防止元数据损坏 (额外的开销)
  • 损坏的 cluster 在 FAT 中标记