文件系统定义
- 操作系统中实现文件统一管理的一组软件和相关数据的集合
- 负责组织、存储、检索、共享和保护文件信息
文件系统功能
- 实现”按名存取”,将用户提供的文件名映射为物理存储位置
- 提供方便的操作和统一的调用接口,屏蔽底层存储设备细节
- 组织、分配、回收文件的存储空间
- 负责文件的存储、检索、共享和保护
文件系统分层架构
文件系统通常采用层次化设计,各层分工明确:
- 用户接口层:提供系统调用接口(open/read/write/close 等)
- 目录管理层:解析路径名,定位文件目录项
- 访问控制层:验证用户权限,确保安全性
- 逻辑文件系统层 + 缓冲区管理层:将用户请求转换为逻辑块号,管理文件缓冲区
- 物理文件系统层:将逻辑块号转换为物理块号,与设备驱动交互
- 设备驱动层:直接与硬件设备(磁盘、SSD)通信,完成实际的 I/O 操作
分层优点:模块化、易于维护和扩展,各层可独立实现和优化。
传统文件系统调用
创建 / 删除
create(filename, attributes)- 检查文件是否已存在(遍历目录)
- 分配一个新的 FCB(inode)
- 将 FCB 写入磁盘
- 更新目录项,添加新文件名到父目录
- 参数:文件名(路径名)、设备名(卷名)、FCB 信息(权限、类型等)
delete(filename)- 检查文件是否存在及权限
- 释放文件占用的所有数据块(回收至空闲空间管理)
- 释放 FCB(inode)
- 从目录中删除对应的目录项
- 如果是硬链接且链接数 > 1,只减少链接计数,不删除数据
打开 / 关闭
open(filename, flags)- 根据文件名搜索目录,找到对应的 FCB
- 将 FCB 从磁盘读入内存(到系统级打开文件表)
- 创建文件描述符(fd)或文件句柄,返回给用户进程
- 建立用户与文件的连接,维护文件的读写位置指针
- 文件描述符:一个整数索引,指向进程级打开文件表中的表项
close(fd)- 写回 FCB 到磁盘(更新文件大小、修改时间等)
- 释放进程级打开文件表中的表项
- 递减系统级打开文件表中的引用计数,引用计数为 0 时释放并写回 FCB
读 / 写
read(fd, buf, count)- 根据文件描述符找到对应的打开文件表项
- 从当前读写位置开始读取 count 字节
- 通过文件系统的映射机制找到数据所在的磁盘块
- 将数据读入缓冲区(buf),然后拷贝到用户空间
- 更新文件读写位置指针
write(fd, buf, count)- 类似读取,但方向相反
- 如果当前块写满,需分配新的磁盘块
- 更新文件大小和修改时间
- 更新读写位置指针
lseek(fd, offset, whence):定位读写位置,实现随机访问
文件描述符与打开文件表
文件描述符是进程和打开文件之间的桥梁。
- 每进程打开文件表(进程级):
- 每个进程维护自己的打开文件表
- 表项包含:文件描述符、指向系统级打开文件表项的指针
fork()时,子进程继承父进程的打开文件表
- 系统级打开文件表(全局):
- 整个系统共享一张打开文件表
- 表项包含:文件读写位置指针、打开模式、引用计数、FCB 指针(指向内存中的 inode)
- 多个文件描述符可以指向同一个系统级表项(如
dup()复制文件描述符时)
- 内存 inode 表:
- 存储从磁盘读入内存的 inode(FCB)
- 多个系统级表项可共享同一个内存 inode(如硬链接打开的文件)
- 当引用计数为 0 时,回写并释放
文件缓冲区管理
- 引入缓冲区的目的:缓解 CPU 与 I/O 设备之间的速度差异,减少物理 I/O 次数
- 预读(Read-Ahead):程序顺序读取文件时,系统预测性地提前将后续块读入缓存
- 基于”局部性原理”:如果程序读取第 n 块,很可能接下来读取第 n+1 块
- 每次读入更大块(如 8KB 或 16KB),减少 I/O 次数
- 写缓冲(Write Buffer / Delayed Write):写操作先写入内存缓冲区,延迟写入磁盘
- 避免每次
write()都触发磁盘 I/O - 缓冲区满或显式调用
sync()/fsync()时真正写入磁盘 - 缺点:系统崩溃可能导致数据丢失(文件系统日志 / Journaling 可缓解此问题)
- 避免每次
UNIX inode(索引节点)
- inode 是 UNIX/Linux 文件系统的核心数据结构,存储文件的全部元数据(FCB 信息)
- 文件名不存储在 inode 中,而是存储在目录文件中,使得同一个 inode 可以有多个文件名(硬链接)
- inode 包含以下关键字段:
- 文件类型与权限(
st_mode) - 文件链接计数(
st_nlink) - 文件所有者和所属组(
st_uid,st_gid) - 文件大小(
st_size) - 时间戳(
st_atime,st_mtime,st_ctime) - 数据块指针数组(直接块 + 间接块指针)
- 文件类型与权限(
混合索引结构(UNIX)
UNIX 采用混合索引结构来管理文件数据块的地址,兼顾小文件和大文件:
- 索引节点中共有 13 个块指针(以传统 UNIX V7 和早期 Linux ext2 为例):
- 10 个直接块指针:直接指向数据块
- 1 个单级间接块指针:指向一个索引块,该索引块包含若干数据块指针
- 1 个二级间接块指针:指向索引块→索引块→数据块
- 1 个三级间接块指针:指向索引块→索引块→索引块→数据块
最大文件大小计算(以 1KB 块大小为例)
- 每个索引块(间接块)可存放 1024 / 4 = 256 个块指针(假设指针大小 4 字节)
- 直接块:10 × 1KB = 10KB
- 单级间接:256 × 1KB = 256KB
- 二级间接:256 × 256 × 1KB = 64MB
- 三级间接:256 × 256 × 256 × 1KB = 16GB
- 最大文件大小 ≈ 16GB + 64MB + 256KB + 10KB ≈ 16.06GB
不同块大小的最大文件大小:
| 块大小 | 最大文件大小 |
|---|---|
| 1KB | ~16GB |
| 2KB | ~256GB |
| 4KB | ~4TB |
(实际 ext4 等现代文件系统使用 extent 树代替了简单的混合索引,支持更大文件)
为什么采用混合索引?
- 适应不同大小的文件:
- 小文件(< 10KB):只需使用直接块,无需额外索引块,开销极小
- 中等文件:通过单级或二级间接,无需三级间接,节省索引块
- 大文件:通过多级间接,理论上支持 TB 级大文件
- 空间与性能的权衡:多数文件很小(据统计,大部分文件 < 10KB),混合索引为小文件优化
- 灵活性:文件动态增长时可逐级扩展索引结构,不需要预先分配
内存映射文件(Memory-Mapped File)
- 另一种文件访问接口
- 将文件的一部分或全部映射到进程的虚拟地址空间
- 进程通过指针直接读写内存,操作系统自动将修改写回文件
- 优点:无需 read/write 系统调用开销,简化编程,数据共享方便(多个进程映射同一文件)
- 缺点:不适合超大文件(地址空间有限),映射区域大小需与页大小对齐
文件系统的组成
- 卷管理:管理磁盘分区(卷),跟踪分区信息和超级块
- 目录管理:维护目录层次结构,支持路径解析和目录操作
- 文件空间管理:分配和回收磁盘块(空闲空间管理)
- 文件操作实现:实现 create/delete/open/close/read/write 等系统调用
- 文件保护与安全:访问控制、权限验证、加密等