1. 概述
回顾一下,文件系统是一种用于组织和管理存储设备上文件的机制。Linux 在各种文件系统实现之上抽象出虚拟文件系统(VFS),VFS 定义了一组通用的数据结构和标准接口:
- 对应用程序:只需与 VFS 的统一接口交互,无需关注具体文件系统实现。
- 对文件系统:只需遵循 VFS 标准,即可支持各种应用程序。
VFS 使用以下关键数据结构管理文件:
- 目录项(dentry) :记录文件名及文件与目录之间的关系(为内存缓存)。
- 索引节点(inode) :记录文件元数据。
- 逻辑块(block) :由磁盘连续扇区组成,是存储文件数据的最小读写单元。
- 超级块(superblock) :记录文件系统整体状态,如索引节点和逻辑块的使用情况。
其中,目录项保存在内存中作为缓存,而超级块、索引节点和逻辑块是存储在磁盘中的持久化数据。
接下里学习了解Linux 磁盘 I/O 的工作原理。
首先要了解磁盘,磁盘是用于持久化存储的设备,根据存储介质的不同,可以分为两类:
- 机械磁盘(HDD)
- 结构:由盘片和读写磁头组成,数据存储在盘片的环状磁道中。
- 读写过程:
-
- 需要移动读写磁头定位到数据所在的磁道后才能访问数据。
- 连续 I/O 性能较优,因为不需要频繁磁头寻址;随机 I/O 则因频繁移动磁头性能较差。
- 最小读写单位:扇区(一般大小为 512 字节)。
- 固态磁盘(SSD)
- 结构:由固态电子元器件组成,无需磁头寻址。
- 性能特点:
-
- 连续 I/O 和随机 I/O 性能均比机械磁盘优异。
- 但随机 I/O 受“先擦除再写入”限制,性能较连续 I/O 仍有差距。
- 随机 I/O 会触发垃圾回收,影响性能。
- 最小读写单位:页(通常为 4KB 或 8KB)。
逻辑块
为提高读写效率,文件系统将多个连续的扇区或页组合为逻辑块,并以逻辑块为最小单位管理数据(常见大小为 4KB)。
磁盘分类
磁盘还可以按照接口类型分类:
- 接口类型:IDE、SCSI、SAS、SATA、FC 等。
- 设备命名:
-
- IDE 设备名以
hd开头。 - SCSI 和 SATA 设备名以
sd开头。 - 多块磁盘按字母编号,如
sda、sdb。
- IDE 设备名以
磁盘架构
- 独立磁盘设备
-
- 可以划分为多个逻辑分区,例如
/dev/sda1和/dev/sda2。
- 可以划分为多个逻辑分区,例如
- RAID(冗余独立磁盘阵列)
-
- 用途:提高性能和数据可靠性。
- 级别:
-
-
- RAID0:最优读写性能,无数据冗余。
- RAID1、RAID5、RAID10:提供数据冗余,同时优化一定的读写性能。
-
- 网络存储集群
-
- 实现:将磁盘组合为网络存储,通过 NFS、SMB、iSCSI 等协议供服务器使用。
Linux中的磁盘管理
- 管理方式:磁盘作为块设备管理,以块为单位支持随机读写。
- 设备号:
-
- 主设备号:用于驱动程序中,区分设备类型。
- 次设备号:用于编号多个同类设备。
通过以上方式,Linux 实现了对磁盘的高效管理和性能优化。
像VFS一样,块设备的管理也有类似的标准接口,通用块层。通用块层是 Linux 文件系统和磁盘驱动之间的抽象层,主要功能包括:
主要功能
- 标准接口:
-
- 向上:为文件系统和应用程序提供统一的块设备访问接口。
- 向下:将各种异构的磁盘设备抽象为统一的块设备,并提供统一的驱动程序管理框架。
- I/O 请求优化:
-
- 对 I/O 请求进行排队,通过重新排序和请求合并提高磁盘读写效率。
- 排序过程被称为 I/O 调度。
Linux 内核支持的 I/O 调度算法
- NONE:
-
- 无调度器,不对 I/O 请求进行任何处理。
- 常用于虚拟机,由物理机负责磁盘 I/O 调度。
- NOOP:
-
- 最简单的调度算法,采用先进先出(FIFO)的队列。
- 仅进行基本的请求合并,适合 SSD 等无需复杂调度的设备。
- CFQ(Completely Fair Scheduler) :
-
- 为每个进程维护独立的 I/O 调度队列,均匀分配时间片。
- 支持 I/O 优先级调度,适合运行大量进程的系统,如桌面环境和多媒体应用。
- DeadLine:
-
- 为读写请求分别创建队列,确保达到 最终期限(deadline)的请求优先处理。
- 提高机械磁盘吞吐量,适合 I/O 压力大的场景(如数据库)。
通用块层通过这些机制,有效减小了不同块设备之间的差异,提高了系统的 I/O 性能。
清楚了磁盘和通用块层的工作原理,结合文件系统原理,我们就可以整体来看 Linux 存储系统的 I/O 原理了。
我们可以把 Linux 存储系统的 I/O 栈,由上到下分为三个层次,分别是文件系统层、通用块层和设备层。
(图片来自 Linux Storage Stack Diagram )
- 文件系统层,包括虚拟文件系统和其他各种文件系统的具体实现。它为上层的应用程序,提供标准的文件访问接口;对下会通过通用块层,来存储和管理磁盘数据。
- 通用块层,包括块设备 I/O 队列和 I/O 调度器。它会对文件系统的 I/O 请求进行排队,再通过重新排序和请求合并,然后才要发送给下一级的设备层。
- 设备层,包括存储设备和相应的驱动程序,负责最终物理设备的 I/O 操作。
需要注意,通用块层是 Linux 磁盘 I/O 的核心。向上,它为文件系统和应用程序,提供访问了块设备的标准接口;向下,把各种异构的磁盘设备,抽象为统一的块设备,并会对文件系统和应用程序发来的 I/O 请求进行重新排序、请求合并等,提高了磁盘访问的效率。
根据上述基础知识想象一个I/O的整个请求生命周期(若有错误,请指正及补充)
- 用户空间发起请求
-
- 应用程序通过
read()或write()系统调用发起 I/O 请求。 - 请求通过系统调用接口进入内核态,可能涉及用户空间与内核空间的数据拷贝。
- 应用程序通过
- 进入 VFS 层
-
- VFS(虚拟文件系统)将用户空间的请求标准化,转化为文件系统支持的操作(如读取索引节点或写入数据块)。
- VFS 根据挂载点选择合适的底层文件系统(如 ext4、xfs 等)进行交互。
- 文件系统处理
-
- 文件系统检查 目录项缓存(dentry cache) 和 索引节点(inode cache) ,判断文件或目录是否存在,以及是否可直接操作。
- 若文件数据存在于 页缓存,直接读取或更新缓存,避免磁盘 I/O。
- 若数据不在缓存中,则发起块设备 I/O 请求。文件系统根据逻辑块地址生成块设备层的 I/O 请求。
- 生成 BIO 请求
-
- 文件系统将 I/O 请求封装为 BIO(Block I/O)对象,描述需要操作的数据块范围及操作类型(读/写)。
- BIO 对象被提交到 块设备层。
- I/O 调度与优化
-
- 在块设备层,BIO 请求进入 I/O 调度器队列(如 CFQ、Deadline)。
- 调度器对请求进行排序、合并(如合并相邻块操作)或优先级调整,以减少寻道和旋转延迟,提高磁盘吞吐量。
- 对于多队列架构(如 blk-mq),利用多队列并发处理请求,适合高速存储设备(如 SSD)。
- 转发到 SCSI 子系统(或其他设备接口)
-
- I/O 请求被转发到合适的 协议子系统,如 SCSI 中层,并进一步解析 BIO 请求为设备命令(如读写块命令)。
- SCSI 低层驱动通过特定协议(如 SATA、SAS、NVMe)将命令发送到目标设备。
- 对于非 SCSI 设备(如 NVMe 或 eMMC),直接使用相应驱动跳过 SCSI 层。
- 物理设备执行
-
- 存储设备(如 HDD、SSD)接收到 I/O 请求,执行实际的读写操作。
- 对于 HDD,涉及寻道、旋转、数据读写;对于 SSD,直接进行页读写或块擦除后写入。
- 设备将结果(成功或失败)返回内核。
- 数据返回用户空间
-
- 数据通过驱动程序返回至块设备层,再经页缓存或直接 I/O 机制返回至文件系统和 VFS 层。
- 最终,数据返回用户空间,I/O 请求完成,用户进程收到操作结果。
2. 性能指标
熟悉原理之后需要了解磁盘性能指标都有哪些,这样才可以在实际生产环境中做磁盘性能分析。有常见的五大磁盘性能指标;使用率、饱和度、IOPS、吞吐量以及响应时间等。
- 使用率,是指磁盘处理 I/O 的时间百分比。过高的使用率(比如超过 80%),通常意味着磁盘 I/O 存在性能瓶颈。
- 饱和度,是指磁盘处理 I/O 的繁忙程度。过高的饱和度,意味着磁盘存在严重的性能瓶颈。当饱和度为 100% 时,磁盘无法接受新的 I/O 请求。
- IOPS(Input/Output Per Second),是指每秒的 I/O 请求数。
- 吞吐量,是指每秒的 I/O 请求大小。
- 响应时间,是指 I/O 请求从发出到收到响应的间隔时间。
磁盘性能分析需要综合考虑使用率、读写比例、I/O 类型(随机或连续)、I/O 大小等因素,不能孤立地比较单一指标。在不同场景下,适用的关键性能指标不同,例如数据库场景适合关注 IOPS,而多媒体场景适合关注 吞吐量。
为了准确评估磁盘性能是否满足需求,应结合应用特点,使用性能测试工具(如 fio)进行基准测试,重点测试 IOPS、吞吐量、响应时间,并在不同 I/O 大小和读写场景下获取性能数据。这些基准测试结果不仅能评估磁盘性能,还能作为后续应用性能问题分析的依据。
熟悉了指标之后,就需要进行观测。首先我们对磁盘I/O进行观测,一般我们熟悉的观测工具就是iostat,它提供了每个磁盘的使用率、IOPS、吞吐量等各种常见的性能指标,当然,这些指标实际上来自 /proc/diskstats。
# -d -x表示显示所有磁盘I/O的指标
$ iostat -d -x 1
Device r/s w/s rkB/s wkB/s rrqm/s wrqm/s %rrqm %wrqm r_await w_await aqu-sz rareq-sz wareq-sz svctm %util
loop0 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
loop1 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
sda 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
sdb 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
iostat 指标解读
其中:
- %util ,就是我们前面提到的磁盘 I/O 使用率;
- r/s+ w/s ,就是 IOPS;
- rkB/s+wkB/s ,就是吞吐量;
- r_await+w_await ,就是响应时间。
实际分析性能时,也要考虑rareq-sz 和 wareq-sz。
下一步对进程I/O进行观测,iostat提供了磁盘整体的I/O性能情况,在实际分析过程中,一般要进一步知道是哪个程序使用了这么多的I/O资源,此时就需要用到pidstat或者iotop。
$ pidstat -d 1
13:39:51 UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command
13:39:52 102 916 0.00 4.00 0.00 0 rsyslogd
- 用户 ID(UID)和进程 ID(PID) 。
- 每秒读取的数据大小(kB_rd/s) ,单位是 KB。
- 每秒发出的写请求数据大小(kB_wr/s) ,单位是 KB。
- 每秒取消的写请求数据大小(kB_ccwr/s) ,单位是 KB。
- 块 I/O 延迟(iodelay),包括等待同步块 I/O 和换入块 I/O 结束的时间,单位是时钟周期。
当然,还可以使用iotop(不过这个工具大部分环境可能没有)
$ iotop
Total DISK READ : 0.00 B/s | Total DISK WRITE : 7.85 K/s
Actual DISK READ: 0.00 B/s | Actual DISK WRITE: 0.00 B/s
TID PRIO USER DISK READ DISK WRITE SWAPIN IO> COMMAND
15055 be/3 root 0.00 B/s 7.85 K/s 0.00 % 0.00 % systemd-journald