跳转至

文件系统与 I/O

1. 文件抽象

1.1 文件的本质

文件是操作系统提供的持久化数据抽象——对用户呈现为命名的字节序列,屏蔽底层存储介质的物理细节。

文件元数据

属性 内容
名称 人类可读的标识符
类型 普通文件、目录、符号链接、设备文件等
大小 字节数
权限 owner/group/others 的 rwx
时间戳 创建时间、修改时间、访问时间
所有者 UID / GID
位置 数据块在磁盘上的位置

1.2 文件操作

基本系统调用:

open()   → 打开文件,返回文件描述符 fd
read()   → 从 fd 读取数据
write()  → 向 fd 写入数据
lseek()  → 移动文件偏移量(随机访问)
close()  → 关闭 fd
stat()   → 获取元数据

文件描述符(fd):进程级整数索引,指向内核中的打开文件表条目。

进程 fd 表 → 系统打开文件表(偏移量、访问模式)→ inode 表(元数据+数据位置)

2. 目录结构

2.1 目录的本质

目录是一种特殊文件,内容是文件名 → inode 号的映射表。

2.2 目录组织

结构 特点
单级目录 所有文件在同一目录(命名冲突)
两级目录 每用户一个目录
树形目录 层级结构(现代标准)
无环图目录 支持硬链接和符号链接

2.3 路径解析

/home/user/file.txt 为例:

  1. 从根目录 inode(inode 2)开始
  2. 读取根目录数据块,查找 home → 得到 inode 号
  3. 读取 home 的 inode,获取数据块
  4. 在数据块中查找 user → 得到 inode 号
  5. 重复直到找到 file.txt 的 inode

每一级都需要读取 inode + 读取目录数据块→ 深层路径的访问代价高。

2.4 链接

类型 机制 特点
硬链接 多个目录项指向同一 inode 不能跨文件系统,不能链接目录
符号链接 特殊文件,内容是目标路径字符串 可跨文件系统,可链接目录

inode 的链接计数(link count)归零时才释放数据块。

3. 磁盘空间分配

3.1 连续分配(Contiguous Allocation)

每个文件占用一段连续的磁盘块。

优势 劣势
顺序和随机访问都很快 外部碎片严重
实现简单(起始块 + 长度) 文件增长困难

适用于只读介质(如 CD-ROM 的 ISO 9660)。

3.2 链表分配(Linked Allocation)

每个磁盘块包含指向下一块的指针。

优势 劣势
无外部碎片 随机访问极慢(\(O(n)\) 寻道)
文件增长容易 指针占用空间

FAT(File Allocation Table):将链表指针集中存储在一张表中,改善随机访问。

3.3 索引分配(Indexed Allocation)

每个文件有一个索引块,存放所有数据块的地址。

Unix inode 的多级索引

inode:
├── 直接指针 ×12        → 直接指向数据块
├── 一级间接指针 ×1     → 指向索引块 → 数据块
├── 二级间接指针 ×1     → 索引块 → 索引块 → 数据块
└── 三级间接指针 ×1     → 索引块 → 索引块 → 索引块 → 数据块

假设块大小 4 KB、指针 4 字节(每块可存 1024 个指针):

级别 可寻址块数 可寻址大小
直接(12 个) 12 48 KB
一级间接 1024 4 MB
二级间接 \(1024^2\) 4 GB
三级间接 \(1024^3\) 4 TB
\[ \text{最大文件大小} = (12 + 1024 + 1024^2 + 1024^3) \times 4\text{ KB} \approx 4\text{ TB} \]

优势:小文件访问快(直接指针),大文件也能支持。

3.4 Extent-Based 分配

现代文件系统(ext4、XFS、Btrfs)使用 extent——连续块的描述符 (起始块, 长度)

  • 一个 extent 可描述大段连续空间
  • 减少元数据开销
  • 提升顺序 I/O 性能

4. 空闲空间管理

方法 数据结构 特点
位图(Bitmap) 每位对应一个块(0=空闲,1=占用) 紧凑,查找效率取决于搜索算法
空闲链表 空闲块链接成链表 分配快,但遍历慢
空闲块组 记录连续空闲块段 适合 extent-based 文件系统
B+ 树 以空闲区间为键 XFS、Btrfs 使用,查找 \(O(\log n)\)

5. 现代文件系统

文件系统 特点 使用场景
ext4 日志、extent、延迟分配 Linux 默认
XFS B+ 树索引、高并发、大文件优化 大数据、服务器
Btrfs COW、快照、校验和、RAID 高级 Linux 特性
ZFS 存储池、端到端校验、快照、去重 FreeBSD、存储服务器
NTFS MFT、ACL、加密 Windows
APFS COW、快照、加密、空间共享 macOS/iOS

日志文件系统(Journaling)

防止崩溃导致文件系统不一致:

模式 日志内容 性能 安全性
Journal(全日志) 元数据 + 数据 最慢 最高
Ordered(有序) 仅元数据(先写数据再写日志) 中等
Writeback(回写) 仅元数据(数据和日志顺序不定) 最快 中等

ext4 默认使用 ordered 模式。

COW(Copy-on-Write)文件系统

Btrfs、ZFS 的核心设计:

  • 永不原地修改数据——总是写到新位置
  • 天然支持快照(只需保留旧指针)
  • 天然支持数据一致性(写入是原子的)
  • 代价:可能产生碎片化

6. RAID

RAID(Redundant Array of Independent Disks):用多块磁盘组合实现高性能和/或高可靠性。

RAID 级别 结构 容量利用率 容错 读性能 写性能
RAID 0 条带化(Striping) 100% \(N\times\) \(N\times\)
RAID 1 镜像(Mirroring) 50% 1 盘故障 \(N\times\) \(1\times\)
RAID 5 分布式奇偶校验 \((N-1)/N\) 1 盘故障 \((N-1)\times\) 写放大(需读-改-写校验)
RAID 6 双重校验(P+Q) \((N-2)/N\) 2 盘故障 \((N-2)\times\) 写放大更严重
RAID 10 先镜像再条带化 50% 每对中 1 盘 \(N\times\) \(N/2\times\)
\[ \text{RAID 5 可用容量} = (N - 1) \times \text{单盘容量} \]

RAID 不是备份

RAID 提供硬件故障容错,但不防止人为删除、软件 bug、勒索软件。始终需要独立备份。

7. I/O 系统

7.1 I/O 方式

方式 机制 CPU 参与度 适用场景
程序控制 I/O(Polling) CPU 轮询设备状态寄存器 持续占用 低延迟、简单设备
中断驱动 I/O 设备完成后发中断通知 CPU 仅在开始和完成时 通用
DMA(Direct Memory Access) DMA 控制器直接在设备和内存间传输 仅在开始和完成时 大块数据传输

DMA 流程

  1. CPU 设置 DMA 控制器(源地址、目标地址、传输大小)
  2. DMA 控制器执行数据传输(CPU 可做其他工作)
  3. 传输完成 → DMA 控制器发中断通知 CPU

7.2 I/O 调度器

磁盘调度(HDD)

磁盘寻道时间是主要延迟来源。调度算法优化磁头移动:

算法 策略 特点
FCFS 按请求到达顺序 公平但寻道距离大
SSTF 选最近的请求(Shortest Seek Time First) 可能饥饿
SCAN(电梯算法) 磁头单向扫描到底再反向 中等等待时间
C-SCAN(循环扫描) 单向扫描到底,跳回起点 更公平
LOOK / C-LOOK 只到最远请求而非磁盘边界 SCAN 的改进版

现代 Linux I/O 调度器

  • mq-deadline:保证请求在截止时间内完成(默认 HDD)
  • BFQ(Budget Fair Queueing):桌面交互响应优化
  • none(noop):适合 SSD(无需寻道优化)

SSD 特性

特性 与 HDD 的区别
无寻道延迟 随机读写性能远优于 HDD
写入放大 写前需擦除(erase block > write page)
磨损均衡 FTL 控制器分散写入,延长寿命
TRIM 通知 SSD 哪些块不再使用,优化 GC
并行通道 多通道/多 Die 并行提升带宽
\[ \text{SSD 写入放大因子 (WAF)} = \frac{\text{实际写入闪存的数据量}}{\text{主机写入的数据量}} \]

理想 WAF = 1,实际通常 2-10(取决于工作负载和 GC 效率)。

7.3 I/O 软件栈

用户空间:  应用程序 → 标准库 (stdio)
           ─────── 系统调用边界 ───────
内核空间:  VFS (虚拟文件系统层)
           → 具体文件系统 (ext4/XFS/...)
           → Page Cache (页面缓存)
           → Block I/O 层 (I/O 调度)
           → 设备驱动
           → 硬件控制器
硬件:      磁盘 / SSD / NVMe

7.4 Page Cache

Linux 使用空闲内存作为页面缓存

  • 读缓存:磁盘数据读入内存后保留,后续读命中缓存
  • 写缓存:写操作先写入 Page Cache(dirty page),后台刷盘
  • 预读(Read-ahead):检测顺序读模式,提前读入后续数据
读路径:  应用 → Page Cache 命中? → 是: 直接返回
                                  → 否: 从磁盘读入 Page Cache → 返回

写路径:  应用 → 写入 Page Cache (标记 dirty)
         → pdflush/writeback 线程定期刷盘
         → 或 fsync() 强制刷盘

7.5 零拷贝(Zero-Copy)

传统文件发送:磁盘 → 内核缓冲区 → 用户缓冲区 → Socket 缓冲区 → 网卡(4 次拷贝)。

sendfile() 系统调用:磁盘 → 内核缓冲区 → 网卡(2 次拷贝),跳过用户空间。

mmap + write:另一种减少拷贝的方式。

8. 存储接口演进

接口 协议 带宽 延迟 队列深度
SATA AHCI ~600 MB/s ~100 μs 32
SAS SCSI ~2.4 GB/s ~100 μs 256
NVMe (PCIe 4.0) NVMe ~7 GB/s ~10 μs 65535
NVMe (PCIe 5.0) NVMe ~14 GB/s ~10 μs 65535

NVMe 的优势:专为闪存设计,多队列(每 CPU 核心独立队列)、低延迟、高并行。


导航


评论 #