实现文件系统
实现文件系统
实现文件系统
实现文件系统:敌人和朋友
敌人:
读/写放大
存储设备的特性:被迫只能读写连续的一块数据
不能像 memory hierarchy 那样随心所欲 new/delete
朋友:
局部性
适当地排布数据,使得临近的数据有 “一同访问” 的倾向
数据暂时停留在内存,延迟写回
实现文件系统
让时间回到 1980 年
5.25" 软盘:单面 160 KiB
320 个 512B 扇区 (sectors)
在这样的设备上实现文件系统,应该选用怎样的数据结构?
实现文件系统
需求分析
相当小的文件系统
目录中一般只有几个、十几个文件
以小文件为主 (几个 block 以内)
文件的实现方式
struct block *
的链表
任何复杂的高级数据结构都显得浪费
目录的实现方式
目录就是一个普通的文件 (虚拟磁盘;“
目录文件
”)
操作系统会把文件内容解读成
struct dentry[];
实现文件系统
用链表存储数据:两种设计
1. 在每个数据块后放置指针
优点
:实现简单、无须单独开辟存储空间
缺点
:数据的大小不是
2
k
2^k
2
k
; lseek 读放大
2. 将指针集中存放在文件系统的某个区域
优点
:局部性好;lseek 更快
缺点
:集中存放的数据损坏将导致数据丢失
哪种方式的缺陷是致命、难以解决的?
实现文件系统
文件系统设计者:先算账
160 KB 的软盘,512 字节扇区
320 个扇区
12-bit entry, 480B
竟然只要
只有一个扇区
就能存下 next[]
容量翻倍,也就 2 个扇区
File Allocation Table: 集中保存所有指针
在
内存
里缓存一份 FAT (天生一次也要读完一个 512B)
延迟写回,读/写放大的问题就完全解决了
但
可靠性
是个大问题
实现文件系统
集中保存所有指针
集中存储的指针容易损坏?
存
n
n
n
份就行!延迟写回,写放大就不那么严重了
实现文件系统
FAT: 链接存储的文件
File Allocation Table
: int next[];
next[i] == 0: Free (本块未分配)
next[i] == -1: EOF (本块是文件的最后一个块)
实现文件系统
目录树实现:目录文件
目录就是个普通文件
但在 metadata 里标记一下 (mode = directory)
操作系统把数据理解成 struct dirent[]
Quiz: 为什么不把元数据 (大小、文件名、……) 保存在文件的头部?
DOS: “8 + 3” 文件名 “AUTOEXEC.BAT”
Linux: /etc/xdg/autostart/
Windows: shell:startup
实现文件系统
历史的包袱
总有一天,8 + 3 文件名会不够用的
那就只能打补丁了
实现文件系统
我们甚至可以直接观察 FAT 文件系统
观察 “快速格式化” (mkfs.fat) 是如何工作的
老朋友:strace
然后,把整个磁盘镜像 mmap 进内存
照抄手册,遍历目录树 (fat-tree demo),试试
镜像
另一个有趣的问题:文件系统恢复
快速格式化 = FAT 表丢失
所有的文件内容 (包括目录文件) 都还在
只是在数据结构眼里看起来都是 “free block”
猜出文件系统的参数 (SecPerClus, BytsPerSec, ...),恢复 next 关系
实现文件系统
FAT: 性能与可靠性
性能
+
小文件简直太合适了
-
但大文件的随机访问就不行了
4 GB 的文件跳到末尾 (4 KB cluster) 有
2
20
2^{20}
2
20
次 next 操作
缓存能部分解决这个问题
在 FAT 时代,磁盘连续访问性能更佳
使用时间久的磁盘会产生碎片 (fragmentation)
malloc 也会产生碎片,不过对性能影响不太大
可靠性
维护若干个 FAT 的副本防止元数据损坏 (轻微写放大)