文件系统核心概念

深入理解文件系统的设计理念和实现原理。

文件系统的本质

什么是文件系统

文件系统是操作系统用于组织和管理存储设备上数据的方法,它解决三个核心问题:

  1. 数据组织:如何在物理介质上组织字节流
  2. 命名空间:如何通过名称定位数据
  3. 元数据管理:如何记录文件属性和结构信息

文件系统的层次

应用程序
    ↓
POSIX 文件 API(open, read, write, close)
    ↓
虚拟文件系统层(VFS)
    ↓
具体文件系统(Ext4, XFS, Btrfs)
    ↓
块设备层(Block Layer)
    ↓
设备驱动(SCSI, SATA, NVMe)
    ↓
物理存储设备

inode:文件的灵魂

inode 的概念

inode(index node,索引节点)是 Unix/Linux 文件系统中存储文件元数据的数据结构。

关键理解

  • 文件名不存储在 inode 中,而是存储在目录项(dentry)中
  • inode 包含文件的所有属性,但不包含文件名和数据
  • 每个文件有唯一的 inode 号

inode 包含的信息

文件属性

  • 文件类型:普通文件、目录、符号链接、设备文件等
  • 权限:rwx 权限位,所有者、组、其他用户
  • 所有者:UID(用户ID)和 GID(组ID)
  • 大小:文件字节数
  • 时间戳
    • atime:访问时间
    • mtime:修改时间(内容)
    • ctime:状态改变时间(元数据)
  • 链接数:硬链接计数
  • 数据块指针:指向实际数据的位置

文件名与 inode 的关系

目录是特殊的文件,内容是文件名到 inode 的映射:

目录 /home/user/:
  ┌─────────────┬─────────┐
  │ 文件名      │ inode   │
  ├─────────────┼─────────┤
  │ .           │ 1234    │ ← 当前目录
  │ ..          │ 89      │ ← 父目录
  │ file.txt    │ 5678    │
  │ script.sh   │ 9012    │
  └─────────────┴─────────┘

重要结论

  • 同一个文件可以有多个文件名(硬链接)
  • 删除文件名不一定删除文件(只有链接数为0才删除)
  • 移动文件只是改变目录项,不移动数据

硬链接 vs 软链接

硬链接(Hard Link)

  • 直接指向 inode
  • 所有硬链接地位相等
  • 不能跨文件系统
  • 不能链接目录(防止循环)
  • 删除任一链接不影响其他链接

软链接(Symbolic Link)

  • 独立的 inode,内容是目标路径
  • 类似 Windows 的快捷方式
  • 可以跨文件系统
  • 可以链接目录
  • 目标删除后成为悬空链接

文件数据的组织

数据块寻址

直接块指针

  • 简单快速
  • 适合小文件
  • 有数量限制(通常12个)

间接块指针

一级间接:
inode → 间接块 → 数据块

二级间接:
inode → 间接块1 → 间接块2 → 数据块

三级间接:
inode → 间接块1 → 间接块2 → 间接块3 → 数据块

问题

  • 大文件需要多次磁盘访问
  • 元数据开销大
  • 碎片化严重

Extent 机制(Ext4)

设计思想:用连续块范围代替单个块指针

传统方式(100个块):
inode: [块1, 块2, 块3, ..., 块100]  ← 100个指针

Extent 方式:
inode: [(起始块1, 长度50), (起始块51, 长度50)]  ← 2个extent

优势

  • 减少元数据
  • 提高大文件性能
  • 减少磁盘碎片
  • 加快文件系统检查(fsck)速度

目录的实现

目录是特殊的文件

目录的本质是一个包含 <文件名, inode> 映射的文件。

小目录

  • 线性列表
  • 顺序查找
  • 简单但慢(O(n))

大目录(HTree)

  • 哈希树结构
  • 快速查找(O(log n))
  • Ext4 默认启用 dir_index 特性

路径解析过程

查找 /home/user/file.txt 的过程:

  1. 根目录:从根 inode(通常是2)开始
  2. home:在根目录中查找 "home" 的 inode
  3. user:在 home 目录中查找 "user" 的 inode
  4. file.txt:在 user 目录中查找 "file.txt" 的 inode
  5. 读取数据:根据 inode 找到数据块

优化

  • 目录缓存:dentry cache 缓存路径解析结果
  • inode 缓存:inode cache 缓存常用 inode

文件系统一致性

为什么需要日志

问题场景:写文件需要修改多个结构

  1. 分配数据块
  2. 更新 inode
  3. 更新目录项
  4. 更新位图

如果在第2步断电,会发生什么?

  • 数据块已分配但 inode 未更新
  • 位图显示已使用但无法访问
  • 空间泄漏

日志文件系统

核心思想:先写日志,再写实际数据

工作流程

  1. 日志开始:记录事务开始标记
  2. 写日志:将要修改的内容写入日志区
  3. 提交:标记事务完成
  4. 写入:将修改写入实际位置
  5. 清理:删除日志记录

崩溃恢复

  • 检查日志是否有未完成的事务
  • 重放已提交的事务
  • 丢弃未提交的事务

日志模式

Journal(完全日志)

  • 数据和元数据都写日志
  • 最安全但最慢
  • 数据写两遍

Ordered(有序模式)

  • 只有元数据写日志
  • 数据先于元数据写入
  • Ext4 默认模式
  • 平衡性能和安全

Writeback(回写模式)

  • 只有元数据写日志
  • 数据和元数据写入顺序不保证
  • 最快但可能数据损坏

文件系统性能

I/O 路径

应用程序 write()
    ↓
Page Cache(页缓存)
    ↓
文件系统(Ext4)
    ↓
Block Layer(I/O 调度)
    ↓
设备驱动
    ↓
磁盘硬件

页缓存(Page Cache)

作用

  • 缓存最近访问的文件数据
  • 减少磁盘 I/O
  • 提高读写性能

写操作流程

  1. 写入页缓存(立即返回)
  2. 页面标记为脏(dirty)
  3. 后台线程刷新到磁盘

读操作优化

  • 预读(readahead):提前读取后续数据
  • 缓存命中:直接从内存返回

I/O 调度器

作用:优化磁盘访问顺序,减少磁盘寻道

CFQ(完全公平队列)

  • 为每个进程维护队列
  • 轮流服务各进程
  • 防止饿死

Deadline(截止时间调度)

  • 保证请求在截止时间内完成
  • 适合实时性要求高的场景

NOOP(无操作)

  • 简单的 FIFO 队列
  • 适合 SSD(无寻道时间)

特殊文件系统

procfs(/proc)

特点

  • 虚拟文件系统,不占用磁盘空间
  • 动态生成内容
  • 提供内核和进程信息

用途

  • /proc/cpuinfo:CPU 信息
  • /proc/meminfo:内存信息
  • /proc/[pid]/:进程信息

sysfs(/sys)

特点

  • 内核对象的层次化视图
  • 设备模型的用户空间接口

用途

  • 设备管理
  • 驱动程序参数
  • 内核模块信息

tmpfs

特点

  • 基于内存的文件系统
  • 速度极快
  • 重启后数据丢失

用途

  • /tmp:临时文件
  • /dev/shm:共享内存
  • /run:运行时数据

文件系统的未来

新特性

Copy-on-Write(写时复制)

  • Btrfs、ZFS 采用
  • 快照功能
  • 数据去重

压缩和加密

  • 透明压缩节省空间
  • 文件级加密保护隐私

校验和

  • 检测数据损坏
  • 静默数据损坏(bit rot)检测

分布式文件系统

Ceph

  • 对象存储、块存储、文件系统
  • 高可用、高扩展

GlusterFS

  • 横向扩展
  • 无单点故障

思考题

  1. 为什么删除大文件很快,而创建大文件很慢?
  2. 硬链接和软链接的本质区别是什么?
  3. 为什么 SSD 不需要复杂的 I/O 调度器?