跳转到内容

文件系统

文件系统把块设备组织成文件与目录。本章重点讲崩溃一致性:崩溃可能发生在任意两次写之间,文件系统如何用日志保证崩溃后仍是合法状态——这是它与数据库共享的核心问题(→ 日志与恢复)。

  • 超级块(superblock):文件系统的元数据(大小、空闲块位置);
  • inode:文件的元数据(大小、权限、时间戳、数据块指针)。文件名不是 inode 的一部分——inode 是"文件"的本体,目录只存"名字 → inode 号"的映射;
  • 目录:目录即文件——内容是 (名字, inode) 表,递归构成树。硬链接 = 两个名字指向同一 inode(引用计数);符号链接 = 存路径的独立文件;
  • 空闲空间管理:位图(bitmap)或空闲链表。

文件 = inode + 数据块。读取:目录查名字得 inode → 读 inode → 按数据块指针读数据。数据块指针分直接/间接/双间接,大文件由间接块扩展。

一次写文件要动多处:inode、数据块、位图、目录。崩溃可能发生在任意两次写之间——写完数据没写 inode(文件是垃圾)、改了位图没写数据(空间泄漏)……**崩溃一致性(crash consistency)**要求:崩溃后磁盘上仍是一个合法(哪怕旧的)状态。

崩溃后启动时全盘扫描修复不一致。缺点:修复慢(TB 级要小时)且只能修复到"合法",不保证"你想要的"。

日志(write-ahead logging)先写意图再写数据:把要做的写操作先追加到日志区(一次写),fsync 日志后再执行真正写,完成后标记提交。崩溃后重放日志,未提交的事务丢弃——恢复只扫日志,快且原子。

三种日志模式:

模式日志内容性能一致性
journal元数据 + 数据慢(写两遍)最强
ordered(ext4 默认)元数据;数据先于提交写中元数据一致
writeback只元数据快数据可能陈旧

ext4、XFS、NTFS 都是日志文件系统。

另一条路:永不原地覆写。更新时写新位置、原子切换根指针——任何时刻磁盘都是一致的旧版本或新版本,崩溃即回滚。ZFS 与 Btrfs 的设计;快照免费(旧版本还在)。代价:随机写变成跨盘写(写放大)、碎片化。它与数据库的 MVCC(→ 并发控制)是同一思想在文件系统层的重现。

fsync 的行为在不同系统上差异巨大——这是著名的实战坑:

  • 理论:fsync 把脏页刷到磁盘才返回;
  • 现实:SSD 的易失缓存、磁盘的写缓存、文件系统的日志策略都可能让"fsync 过"的数据在掉电后丢失;
  • 更微妙的:fsync 目录才保证"新创建的文件"持久化(元数据与数据要分别刷)。

SQLite 的作者测试了各文件系统与介质上 fsync 的真实行为,结论是"每个系统的 fsync 语义都不同"。工程结论:持久化路径要显式 fsync(数据 + 目录),不要依赖缓存;数据库的可靠性建立在正确的 fsync 序列上(→ 日志与恢复)。

  • 文件 = inode + 数据块;目录即文件,文件名在目录不在 inode
  • 崩溃可能发生在任意两次写之间;崩溃一致性是文件系统的核心问题
  • 日志先写意图再写数据,保证崩溃后能原子恢复;ext4/XFS/NTFS 都是日志 FS
  • CoW 文件系统(ZFS/Btrfs)永不覆写,快照免费,代价是写放大
  • fsync 的语义跨系统差异巨大:持久化要显式 fsync 数据与目录