# Disk Diagnosis

* 文件系统层：包括虚拟文件系统和各种文件系统的具体实现。
* 通用块层：包括块设备 I/O 队列和 I/O 调度器。
* 设备层：包括存储设备和相应的驱动程序，负责最终物理设备的 I/O 操作。

![](/files/-MMFA9xBQfsPvY8-t2zA)

I/O 通常是整个系统中最慢的一环，Linux 提供多种缓存机制来优化 I/O。如文件系统使用了页缓存、索引节点缓存、目录项缓存；块设备使用了缓冲区。

## 文件系统

### 索引节点和目录项

文件系统在磁盘的基础上，提供了一个用来管理文件的树状结构。Linux 为每个文件都分配两个数据结构，索引节点（index node）和目录项（directory entry）：

* **索引节点，inode**：记录文件的元数据，如 inode 编号、文件大小、访问权限、修改日期、数据位置等。inode 和文件一一对应，会被持久化到磁盘。在磁盘格式化的时候设定好的。
* **目录项，dentry**：记录文件的名字、inode 指针、与其它 dentry 的关联关系。由内核维护的**内存**数据结构，也叫目录项缓存。

dentry 与 inode 是多对一的关系，即一个文件可以有多个别名。通过硬链接为文件创建别名，就会有不同的目录项，这些目录项的索引节点相同。

磁盘读写的最小单位是扇区（512B），文件系统把连续的扇区组成**逻辑块**，以逻辑块为最小单元来管理数据。常见的逻辑块为 4KB，即 8 个连续扇区。

![](/files/-MLvWASfgNBQ4voJe3lB)

内存一节提到文件内容会缓存到页缓存 Cache 中，索引节点也会缓存到内存中。另外，磁盘在执行文件系统格式化时，会分成三个存储区，**超级块**会存储整个文件系统的状态。

内核使用 slab 机制管理目录项和索引节点。

可通过 df 查看磁盘容量。free, vmstat, /proc/meminfo, /proc/slabinfo, slabtop 等工具查看文件的缓存信息。

### VFS

为了支持各种不同的文件系统，Linux 内核在用户进程和文件系统中，引入一个 VFS（Virtual File System） 抽象层。VFS 定义了一组标准的数据结构和接口，所以用户进程使用时不需要关心底层文件系统的实现细节。

![](/files/-MLvXIooqZzTnzaMe1mb)

Linux 支持多种文件系统，这些文件系统首先得挂载到 VFS 目录树中的某个子目录（挂载点），然后才能访问。支持的文件系统主要分为：

* 基于磁盘：Ext4、XFS、OverlayFS 等。
* 基于内存：/proc、/sys 等。
* 网络：NFS、SMB、iSCSI 等。

### I/O 分类

是否利用标准库缓存：

* 缓冲 IO：使用标准库缓存来加速文件访问，标准库内部再通过系统调用访问文件。
* 非缓冲 IO：直接通过系统调用。

是否利用操作系统的页缓存：

* 直接 IO：跳过操作系统的页缓存，直接与文件系统交互。需要指定 O\_DIRECT。
* 非直接 IO：文件读写时，先经过页缓存。默认选项。

{% hint style="info" %}
直接 IO 与非直接 IO 都是与文件系统交互。跳过文件系统直接读写磁盘叫做裸 IO。
{% endhint %}

应用程序是否阻塞自身运行（应用程序的角度）：

* 阻塞 IO：应用程序执行 IO 操作后，在获取响应之前会阻塞当前线程。
* 非阻塞 IO：执行 IO 后，不阻塞，可以执行其它任务。随后通过轮询或事件通知的方式获取调用结果。

系统响应 IO 请求的方式（内核系统的角度）：

* 同步 IO：系统收到 IO 请求后，等 IO 完成后才告知应用程序的 IO 结果。
* 异步 IO：系统受到 IO 请求后，立即告诉应用程序请求已收到，随后处理 IO，处理完成后，通过事件通知的方式告诉应用程序结果。

O\_SYNC，O\_DSYNC 代表同步，O\_SYNC 是在 O\_DSYNC 基础上，文件元数据写入磁盘后才能返回。

## 磁盘

常见的磁盘按存储介质可分为两类：

* 机械磁盘：HDD（Hard Disk Driver），由盘片和磁头组成，数据存储在盘片的环状磁道中，读写数据时需要先移动磁头到位置。最小读写单位是扇区，一般是 512B。
* 固态磁盘：SSD（Solid State Disk），不需要磁道寻址，连续 I/O 和随机 I/O 性能都比 HDD 好得多。最小读写单位是页，一般是 4KB、8KB 等。

文件系统会把连续的扇区或块组成逻辑块，常见逻辑块大小是 4KB，即 8 个扇区或一个页。

同类磁盘的随机 I/O 都比连续 I/O 慢很多：

* HDD：随机 I/O 需要更多的磁头寻道和盘片旋转。
* SSD：存在“先擦除再写入”的机制，随机 I/O 导致大量地垃圾回收。
* 连续 I/O 有预读机制，可以减少 I/O 次数。

按接口分类一般有：IDE、SCSI、SAS、SATA、FC 等。不同接口有不同的设备名称，IDE 会有 hd 前缀，SCSI 和 SATA 会有 sd 前缀。若有多块同类型磁盘，则按照 a, b, c 来编号。磁盘还可以划分逻辑分区，每个分区再用数字编号，如 /dev/sda1, /dev/sda2。

还可以把多块磁盘组成逻辑磁盘，即 RAID。RAID0 读写性能最优，但不提供数据冗余；其它级别的 RAID，提供数冗余，读写也有一定优化。

Linux 每个块设备都会有主、次设备号，主设备号用于驱动程序，次设备号用于给多个同类设备编号。

### 通用块层

文件系统有 VFS，通用 Linux 提供一个统一的统一块层来管理不同的块设备，是处于文件系统和磁盘驱动中间的一个块设备抽象层。主要有两个功能：

* 为文件系统提供访问块设备的标准接口。
* 对文件系统和应用程序发来的 I/O 请求排队，通过重新排序、请求合并等方式提高磁盘读写效率。

对 I/O 请求排序，就是 I/O 调度。Linux 内核提供四种调度算法：

* NONE：不使用任何调度器。常用于虚拟机，磁盘 I/O 调度又物理机负责。
* NOOP：先入先出，仅做一些基本的请求合并，常用于 SSD。
* CFQ（Completely Fair Scheduler）：很多发行版的默认选项，为每个进程维护一个 I/O 调度队列，按时间片均匀分布每个进程的 I/O 请求。
* DeadLine：为读写请求创建不同的 I/O 队列，多用于 I/O 压力比较重的场景，如数据库。

### 性能指标

* 使用率：磁盘处理 I/O 的时机百分比。一般超过 80% 意味着性能瓶颈。
* 饱和度：磁盘处理 I/O 的繁忙程度。当饱和度为 100% 时，磁盘无法接受新的 I/O 请求。
* IOPS（Input/Output Per Second）：每秒的 I/O 请求数。
* 吞吐量：每秒的 I/O 请求大小。
* 响应时间：I/O 请求发起到响应的间隔时间。

{% hint style="info" %}
使用率仅考虑有没有 I/O，不考虑 I/O 大小。当使用率达到 100% 时，仍可能接受新的 I/O 请求。
{% endhint %}

性能指标需要多个综合分析，如数据库、小文件随机读写较多的场景，IOPS 更好；多媒体等顺序读写较多的场景，吞吐量更好。

磁盘基准测试可用 fio。磁盘指标可用 iostat、pidstat、iotop、/proc/diskstats 等工具查看。

## 总结

### 指标

![](/files/-MMQCzzxhjR_Xks9K6FN)

### 指标 -> 工具

![](/files/-MMQDVcGRKt7miZ3kuz4)

### 工具 -> 指标

![](/files/-MMQGOQwbgKcM31qi7hL)

### 分析思路

先运行输出指标较多的工具，如 iostat、vmstat、pidstat 等，再缩小范围

![](/files/-MMQGvn5Smj29J10WNag)

### 优化思路

#### 应用程序优化

* 追加写替代随机写
* 借助缓存 IO，充分利用系统缓存
* 程序内部构建自己的缓存。如 C 标准库 fopen、fread 会利用库函数的缓存；直接使用 open、read 等系统调用时，就只能利用系统的页缓存和缓冲区。
* 用 mmap 代替 read、write。
* fsync() 替代 O\_SYNC。
* 使用 cgroups 的 IO 子系统。
* 在使用 CFQ 调度器时，使用 ionice 调整优先级。

#### 文件系统优化

* 选择合适的文件系统。
* 优化文件系统的配置。特性（ext\_addr, dir\_index），日志模式（journal, ordered, writeback），挂载（noatime）等。tune2fs 调整特性，/etc/fstab 或 mount 调整日志模式和挂载。
* 优化文件系统缓存。
  * pdflush 脏页的刷新频率。如 dirty\_expire\_centisecs, dirty\_writeback\_centisecs
  * pdflush 脏页的限额。如 dirty\_background\_ratio, dirty\_ratio
  * 内核目录项缓存和索引节点缓存。vfs\_cache\_pressure
* 若不需要持久化，可以用内存文件系统 tmpfs。如 /dev/shm。

#### 磁盘优化

* SSD 替换 HDD。
* 使用 RAID。
* 选择合适的 IO 调度算法。SSD 和虚拟机磁盘使用 noop，数据库使用 deadline。
* 磁盘隔离。比如日志和数据可以放不同的盘。
* 若顺序读比较多，可以增大预读数据。
  * 调整 /sys/block/sdb/queue/read\_ahead\_kb
  * 使用 blockdev 工具。
* 优化内核块设备 IO 选项。如磁盘队列长度 /sys/block/sdb/queue/nr\_requests
* 查看磁盘是否有硬件问题。dmesg 查看硬件 IO 错误日志；badblocks、smartctl 等工具检查硬件问题；e2fsck 检查文件系统错误；fsck 修复错误等。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://yunzhao.gitbook.io/notes/computer-science/linux/i-o-diagnosis.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
