文件系统核心概念
深入理解文件系统的设计理念和实现原理。
文件系统的本质
什么是文件系统
文件系统是操作系统用于组织和管理存储设备上数据的方法,它解决三个核心问题:
- 数据组织:如何在物理介质上组织字节流
- 命名空间:如何通过名称定位数据
- 元数据管理:如何记录文件属性和结构信息
文件系统的层次
应用程序
↓
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 的过程:
- 根目录:从根 inode(通常是2)开始
- home:在根目录中查找 "home" 的 inode
- user:在 home 目录中查找 "user" 的 inode
- file.txt:在 user 目录中查找 "file.txt" 的 inode
- 读取数据:根据 inode 找到数据块
优化:
- 目录缓存:dentry cache 缓存路径解析结果
- inode 缓存:inode cache 缓存常用 inode
文件系统一致性
为什么需要日志
问题场景:写文件需要修改多个结构
- 分配数据块
- 更新 inode
- 更新目录项
- 更新位图
如果在第2步断电,会发生什么?
- 数据块已分配但 inode 未更新
- 位图显示已使用但无法访问
- 空间泄漏
日志文件系统
核心思想:先写日志,再写实际数据
工作流程:
- 日志开始:记录事务开始标记
- 写日志:将要修改的内容写入日志区
- 提交:标记事务完成
- 写入:将修改写入实际位置
- 清理:删除日志记录
崩溃恢复:
- 检查日志是否有未完成的事务
- 重放已提交的事务
- 丢弃未提交的事务
日志模式
Journal(完全日志):
- 数据和元数据都写日志
- 最安全但最慢
- 数据写两遍
Ordered(有序模式):
- 只有元数据写日志
- 数据先于元数据写入
- Ext4 默认模式
- 平衡性能和安全
Writeback(回写模式):
- 只有元数据写日志
- 数据和元数据写入顺序不保证
- 最快但可能数据损坏
文件系统性能
I/O 路径
应用程序 write()
↓
Page Cache(页缓存)
↓
文件系统(Ext4)
↓
Block Layer(I/O 调度)
↓
设备驱动
↓
磁盘硬件
页缓存(Page Cache)
作用:
- 缓存最近访问的文件数据
- 减少磁盘 I/O
- 提高读写性能
写操作流程:
- 写入页缓存(立即返回)
- 页面标记为脏(dirty)
- 后台线程刷新到磁盘
读操作优化:
- 预读(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:
- 横向扩展
- 无单点故障
思考题:
- 为什么删除大文件很快,而创建大文件很慢?
- 硬链接和软链接的本质区别是什么?
- 为什么 SSD 不需要复杂的 I/O 调度器?