文件系统和磁盘的区别
磁盘是一个存储设备(确切来说是块设备),可以划分为不同的磁盘分区。而在磁盘或者磁盘分区上,还可以在创建文件系统,并挂载到系统的某个目录。这样,系统就可以通过这个挂载目录,来读写文件。
换句话说,磁盘是存储数据的块设备,也是文件系统的载体。所以,文件系统确实还是要通过磁盘,来保证数据的持久化存储。
Linux 中一切皆文件。可以通过相同的文件接口,来访问磁盘和文件(比如 open、read、write、close 等)
- 通常说的“文件”,是指普通文件
- 磁盘和分区,是指块设备文件
在读写普通文件时,I/O 请求会首先经过文件系统,然后由文件系统负责,来与磁盘进行交互。而读写块设备文件时,会跳过文件系统,直接与磁盘交互,也就是所谓的“裸 I/O”。文件系统管理的缓存,是 Cache 的一部分;而裸磁盘的缓存,用的正是 Buffer。
裸磁盘,也称为原始磁盘,是一种未被任何文件系统(如NTFS、FAT32)格式化或管理的磁盘。换句话说,它是直接与磁盘硬件交互,而不通过操作系统的文件系统层进行访问。
缓存 I/O 与直接 I/O(裸磁盘 I/O )的对比
特性 | 缓存I/O(文件系统) | 直接I/O(裸磁盘) |
---|---|---|
数据流 | 磁盘 → 内核缓冲区 → 应用程序地址空间 | 磁盘 → 直接应用程序地址空间 |
缓存使用 | 使用文件系统管理的Cache | 使用磁盘的Buffer |
性能 | 适合常规文件操作,减少磁盘读写次数 | 适合高性能场景,如数据库,减少文件系统开销 |
应用场景 | 普通文件读写,系统默认方式 | 虚拟化、数据库优化、低级别磁盘操作 |
优点 | 保护系统安全,减少直接磁盘访问风险 | 降低数据复制开销,提高I/O效率 |
缺点 | 数据复制开销高,CPU和内存占用多 | 需要应用程序管理缓存,可能增加复杂性 |
Linux 文件系统如何工作
索引节点和目录项
- 索引节点(inode),和文件一一对应,存储在磁盘中,记录文件的元数据
- 目录项(dentry),记录文件的名字、索引节点以及其他目录项的关联关系
举例说明,为文件创建的硬链接,会对应不同的目录项,他们都连接到同一个文件,索引节点相同。
磁盘的最小单位是扇区,文件系统将连续的扇区组成逻辑块,以逻辑块为最小单位,来读写磁盘数据。常见的逻辑块 4KB,由连续的 8 个扇区组成。
磁盘在执行文件系统格式化时,分为三个区域:超级块、索引节点和数据块:
- 超级块:整个文件系统的状态
- 索引节点区:存储索引节点
- 数据块区:存储文件数据
虚拟文件系统
文件系统分类:
- 基于磁盘的文件系统:常见的 ext4、XFS、OverlayFS 等,都是这类文件系统
- 基于内存的文件系统:常说的虚拟文件系统,不需要磁盘空间,但是占用内存。比如,/proc 和 /sys
- 网络文件系统:用于访问其他计算机的文件系统,比如 NFS、SMB、ISCSI 等
注意:这些文件系统,要先挂载到 VFS 目录树中的某个子目录(称为挂载点),然后才能访问其中的文件。
文件系统 I/O
根据是否利用标准库缓存,分为缓冲 I/O 和非缓冲 I/O:
- 缓存 I/O:利用标准库缓存,加速文件访问,标准库内部利用系统调用访问文件
- 非缓存 I/O:直接通过系统调用访问文件,不再经过标准库缓存
注意:这里的“缓冲”,是指标准库内部实现的缓存,最终还是需要通过系统调用,而系统调用还会通过页缓存,来较少磁盘的 I/O 操作
根据是否利用操作系统的页缓存,分为直接 I/O 和非直接 I/O:
- 直接 I/O:跳过操作系统的页缓存,直接和文件系统交互来访问文件
- 非直接 I/O:先通过页缓存,再通过内核或者额外的系统调用,真正和磁盘交互(O_DIRECT 标志)
根据应用程序是否阻塞自身,分为阻塞 I/O 和非阻塞 I/O:
- 阻塞 I/O:是指应用程序执行 I/O 操作后,如果没有获得响应,就会阻塞当前线程
- 非阻塞 I/O:是指应用程序执行 I/O 操作后,不会阻塞当前的线程,可以继续执行其他的任务,随后再通过轮询或者事件通知的形式,获取调用的结果
根据是否等待相应结果,分为同步 I/O 和异步 I/O:
- 同步 I/O:应用程序执行 I/O 操作之后,要等到整个 I/O 完成后,才能获得 I/O 响应
- 异步 I/O:应用程序不用等待 I/O 完成,会继续执行,等到 I/O 执行完成,会以事件的方式通知应用程序
设置 O_SYNC 或者 O_DSYNC,代表同步 I/O。如果是 O_DSYNC,要等到文件数据写入磁盘之后,才能返回,如果是 O_SYNC,是在 O_DSYNC 的基础上,要求文件元数据写入磁盘,才返回。
设置 O_ASYNC,代表异步 I/O,系统会再通过 SIGIO 或者 SIFPOLL 通知进程。
性能观测
容量
df 命令查看磁盘空间
1 | $ df -h /dev/vdb |
当索引节点空间不足,但是索引空间充足时,可能是过多小文件导致的。解决方法一般是删除这些小文件,或者移动到索引节点充足的其他磁盘区。
缓存
可以使用 free 或者 vmstat,观察页缓存的大小;也可以查看 /proc/meminfo
1 | $ cat /proc/meminfo | grep -E "SReclaimable|Cached" |
内核使用 slab 机制,管理目录项和索引节点的缓存,/proc/meminfo 给出了整体的 slab 大小,/proc/slabinfo 可以查看每一种 slab 的缓存
1 | [root@pudding-160 ~]# cat /proc/slabinfo | grep -E '^#|dentry|inode' |
其中 dentry 代表目录项缓存,inode_cache 代表 VFS 索引节点缓存,其他的就是各种文件系统的索引节点缓存。
实际性能分析中,更常使用 slabtop 命令,来找出占用内存最多的缓存类型。
示例如下:可以看到,目录项占用了最多的 Slab 缓存,大约 3.91 G
1 | # 按下c按照缓存大小排序,按下a按照活跃对象数排序 |
Linux 磁盘 I/O 工作原理
磁盘
根据存储介质,磁盘分为:
- 机械磁盘,也称为硬盘驱动器(Hard Disk Driver),通常缩写为 HDD。机械磁盘主要有盘片和读写磁头组成,数据就存储在盘片的环状磁道中。在读写数据前,需要移动读写磁头,定位数据所在的磁道,才能访问数据。如果 I/O 请求刚好连续,就不需要磁道寻址,可以获得最佳性能。这就是连续 I/O 的工作原理。与之对应的是随机 I/O,它需要不停地移动磁头,来定位数据位置,读写速度就会比较慢。
- 固态磁盘(Silid State Disk),通常缩写为 SSD,由固态电子元件组成。固态磁盘不需要磁盘寻址,不管是连续 I/O,还是随机 I/O 的性能,都比机械磁盘要好得多。
无论是机械磁盘,还是固态磁盘,相同磁盘的随机 I/O 都要比连续 I/O 慢得多,原因是:
- 随机 I/O 需要更多的磁头寻道和盘片旋转,它的性能自然要比连续 I/O 慢
- 对于固态硬盘来说,虽然它的随机性能比机械磁盘好很多,但同样存在“先擦除再写入”的限制。随机读写会导致大量的垃圾回收,所以相对应的,随机 I/O 的性能比起连续 I/O 来,也还差了很多
- 连续 I/O 还可以通过预读的方式,来减少 I/O 请求的次数,这也是其性能优异的一个原因
最小读写单位:
- 机械硬盘的最小读写单位是扇区,一般是 512 字节
- 固态硬盘的最小读写单位是页,一般是 4KB 或者 8KB
按照接口,磁盘可分为 IDE(Integrated Drive Electronics)、SCSI(Small Computer System Interface) 、SAS(Serial Attached SCSI) 、SATA(Serial ATA) 、FC(Fibre Channel)等。
磁盘介入服务器时,按照不通的使用方式,会划分为不用的架构:
- 最简单的直接作为独立磁盘设备来使用
- 将多块磁盘组合成一个逻辑磁盘,构成冗余独立磁盘阵列(RAID),提高数据访问的性能,并增强数据存储的可靠性
- 最后一种,是将磁盘组合成网络存储集群,再通过 NFS、SMB、ISCSI 等网络存储协议,暴露给服务器使用
在 Linux 中,磁盘是作为一个块设备来管理,以块为单位来读写,支持随机读写。每个块设备赋予两个设备号,分别是主、次设备号,主设备号用在驱动程序中,用来区分设备类型;次设备号用来在多个同类设备编号。
通用块层
和 VFS 类似,为了减少不同设备的差异带来的影响,Linux 通过统一的通用块(块 I/O 层),管理不同的块设备。
块设备层是处在文件系统和磁盘驱动中间的一个块设备抽象层,主要功能是:
- 向上为文件系统和应用程序提供访问块设备的标准接口;向下,把各种异构的磁盘块设备抽象为统一的块设备,提供统一框架管理这些设备的驱动程序
- 通用块层还会给文件系统和应用程序发来 I/O 请求排队,并通过重新排序、请求合并等方式,提高磁盘读写的效率
对 I/O 请求排序的过程就是 I/0 调度,Linux 支持的四种 I/O 调度算法,分别是 NONE、NOOP、CFQ 以及 DeadLine:
- NONE,不适用任何调度,对 I/O 不做任何处理(常用在虚拟机,此时磁盘 I/O 完全由物理机复杂)
- NOOP,先入先出调度(常用在 SSD)
- CFQ(Completely Fair Schedule)完全公平调度器,很多 Linux 发行版的默认调度器,它为每个进程维护了一个 I/O 调度队列,按照时间片来均匀分配每个进程的 I/O 请求;还支持优先级调度,适用于大量进程的系统(如桌面、多媒体应用等)
- DeadLine 调度算法,分别为读、写请求创建了不同的 I/O 队列,可以提高机械磁盘的吞吐量,并确保打到最终期限(deadline)的请求被优化处理,多用在 I/O 压力比较重的场景,比如数据库等
I/O 栈
根据这张 I/O 栈的全景图,可以看出存储系统 I/O 的工作原理
- 文件系统层,包括虚拟文件系统和其他文件系统的具体实现。它为上层的应用程序,提供标准的文件访问接口;对下会通过快层,来存储和管理磁盘资源
- 通用块层,包括块设备 I/O 队列和 I/O 调度器。他会对文件系统的 I/O 请求进行排队,再通过重新排序和请求合并,然后才要发给下一级的设备层
- 设备层,包括存储设备和相应的驱动程序,负责最终物理设备的 I/O 操作
存储系统的 I/O,通常是整个系统最慢的一环。
Linux 通过多种缓存机制来优化 I/O 效率。为了优化文件访问的性能,会使用页缓存、索引节点缓存等多种机制,以减少对下层块设配的直接调用。同样,为了优化块设备,会使用缓冲区,来缓存块设备的数据。
磁盘性能指标
使用率、饱和度、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 的大小。换句话说,当使用率是 100% 的时候,磁盘依然有可能接受到新的 I/O 请求
- 随即读写多(如数据库、大量小文件)的情况下主要关注 IOPS,而顺序读写(如流媒体)的情况下,主要关注吞吐量
在为应用程序的服务器选型时,要先对磁盘的 I/O 性能进行基准测试,以便可以准确评估,磁盘性能是否可以满足应用程序的需求
磁盘 I/O 观测
使用 iostat 观测每块磁盘的使用情况,提供了每个磁盘的使用率、IOPS、吞吐量等各种常见的性能指标,这些指标实际上来自 /proc/diskstats
1 | $ iostat -dx 1 |
各个指标解读如下
注意:
- %util,就是我们前面提到的磁盘 I/O 使用率
- r/s + w/s,就是 IOPS
- rkB/s + wkB/s,就是吞吐量
- r_await+w_await,就是响应时间
在观测指标时,可以结合请求的大小(rareq-sz 和 wareq-sz)一起分析。
进程 I/O 观测
pidstat 可以实时查看某个进程的 I/O 情况
1 | $ pidstat -d 1 |
指标如下:
- 用户 ID(UID)和进程 ID(PID)
- 每秒读取的数据大小(kB_rd/s),单位是 KB
- 每秒发出的写请求数据大小(kB_wr/s),单位是 KB
- 每秒取消的写请求数据大小(KB_ccwr/s),单位是 KB