磁盘为系统提供了最基本的持久化存储。
文件系统则在磁盘的基础上,提供了一个用来管理文件的树状结构。
“Linux 一切皆文件”的深刻含义。无论是普通文件和块设备、还是网 络套接字和管道等,它们都通过统一的 VFS 接口来访问。
索引节点和目录项
文件系统,本身是对存储设备上的文件 进行组织管理的机制。组织方式不同,就会形成不 同的文件系统。
为方便管理,Linux 文件系统为每个文件都分配两个数据结构,索引节点(index node)和目录项(directory entry)。它们主要用来记录文件的元信息和目录结构。
- 索引节点,简称为 inode,用来记录文件的元数据,比如 inode 编号、文件大小、访问 权限、修改日期、数据的位置等。索引节点和文件一一对应,它跟文件内容一样,都会被 持久化存储到磁盘中。所以记住,索引节点同样占用磁盘空间。
- 目录项,简称为 dentry,用来记录文件的名字、索引节点指针以及与其他目录项的关联 关系。多个关联的目录项,就构成了文件系统的目录结构。不过,不同于索引节点,目录 项是由内核维护的一个内存数据结构,所以通常也被叫做目录项缓存。
目录项、索引节点、索引块、数据块以及超级块关系
- 索引节点是磁盘上每个文件实体的唯一标志
- 目录项维护的正是文件系统的树状结构。目录项和索引节点的关系是多对一,你可以简单理解为,一个文件可以有多个别名
- 举个例子,通过硬链接为文件创建的别名,就会对应不同的目录项,不过这些目录项本质上 还是链接同一个文件,所以,它们的索引节点相同。
索引节点、目录项纪录了文件的元数据,以及文件间的目录关系,磁盘读写的最小单位是扇区,然而扇区只有 512B 大小,如果每次都读写这么小的 单位,效率一定很低。所以,文件系统又把连续的扇区组成了逻辑块,然后每次都以逻辑块 为最小单元,来管理数据。常见的逻辑块大小为 4KB,也就是由连续的 8 个扇区组成。
processon
- 目录项本身就是一个内存缓存,而索引节点则是存储在磁盘中的数据。
- 磁盘在执行文件系统格式化时,会被分成三个存储区域,超级块、索引节点区和数据 块区。
- 超级块,存储整个文件系统的状态。
- 索引节点区,用来存储索引节点。
- 数据块区,则用来存储文件数据。
虚拟文件系统
为了支持各种不同的文件系统,Linux 内核在用户进程和文件系统的中间,又引入了一个抽 象层,也就是虚拟文件系统 VFS(Virtual File System)。
VFS 定义了一组所有文件系统都支持的数据结构和标准接口。 用户进程和内核中的其他子系统,只需要跟 VFS 提供的统一接口进行交互就可以了,而不需要再关心底层各种文件系统的实现细节。
VFS 内部又通过目录项、索引节点、逻辑块以及超级块等数据结构,来管理文件。
- 目录项,记录了文件的名字,以及文件与其他目录项之间的目录关系。
- 索引节点,记录了文件的元数据。
- 逻辑块,是由连续磁盘扇区构成的最小读写单元,用来存储文件数据。
- 超级块,用来记录文件系统整体的状态,如索引节点和逻辑块的使用情况等。
目录项是一个内存缓存;而超级块、索引节点和逻辑块,都是存储在磁盘中的持久化 数据。
系统调用、VFS、缓存、文件系统以及块存储
processon
文件系统可以分为三类。
- 第一类是基于磁盘的文件系统,也就是把数据直接存储在计算机本地挂载的磁盘中。常见 的 Ext4、XFS、OverlayFS 等,都是这类文件系统。
- 第二类是基于内存的文件系统,也就是我们常说的虚拟文件系统。不需要任何磁盘分配存储空间,但会占用内存。我们经常用到的 /proc 文件系统,其实就是一 种最常见的虚拟文件系统。此外,/sys 文件系统也属于这一类,主要向用户空间导出层 次化的内核对象。
- 第三类是网络文件系统,也就是用来访问其他计算机数据的文件系统,比如 NFS、 SMB、iSCSI 等。
这些文件系统,要先挂载到 VFS 目录树中的某个子目录(称为挂载点),然后才能访问其 中的文件。
- 基于磁盘的文件系统 在安装系统时,要先挂载一个根目 录(/),
- 在根目录下再把其他文件系统(比如其他的磁盘分区、/proc 文件系统、/sys 文 件系统、NFS 等)挂载进来。
文件系统 I/O
把文件系统挂载到挂载点后,就能通过挂载点,再去访问它管理的文件了。VFS 提供了一组标准的文件访问接口。这些接口以系统调用的方式,提供给应用程序使用。
就拿 cat 命令来说,它首先调用 open() ,打开一个文件;然后调用 read() ,读取文件的 内容;最后再调用 write() ,把文件内容输出到控制台的标准输出中。
文件 IO分类:缓冲与非缓冲 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_DIRECT 标志。如果没有设置过,默认 的是非直接 I/O。
- 非直接 I/O 正好相反,文件读写时,先要经过系统的页缓存,然后再由内核或额外的系 统调用,真正写入磁盘。
直接 I/O、非直接 I/O,本质上还是和文件系统交互。如果是在数据库等场景中,你还会看到,跳过文件系统读写磁盘的情况,也就是我们通常所说的裸 I/O。
应用程序是否阻塞自身运行:阻塞 I/O 和非阻塞 I/O
- 阻塞 I/O,是指应用程序执行 I/O 操作后,如果没有获得响应,就会阻塞当前线程, 自然就不能执行其他任务。
- 非阻塞 I/O,是指应用程序执行 I/O 操作后,不会阻塞当前的线程,可以继续执行其他的任务,随后再通过轮询或者事件通知****的形式,获取调用的结果。
- 访问管道或者网络套接字时,设置 O_NONBLOCK 标志,就表示用非阻塞方式访 问;而如果不做任何设置,默认的就是阻塞访问。
- 非阻塞 I/O,通常会跟 select/poll 配合,用在网络套接字的 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 基础上,要求文件元数据也要写入磁盘后,才能返回。
- 异步 I/O,是指应用程序执行 I/O 操作后,不用等待完成和完成后的响应,而是继续执行就可以。等到这次 I/O 完成后,响应会用事件通知的方式,告诉应用程序。
- 访问管道或者网络套接字时,设置了 O_ASYNC 选项后,相应的 I/O 就是异步 I/O。这样,内核会再通过 SIGIO 或者 SIGPOLL,来通知进程文件是否可读写。
同步异步和阻塞非阻塞的区别
- 不同角度的 I/O 划分方式:根据应用程序是否阻塞自身运行或者I/O 响应的通知方式的不同
- 描述的对象不同,阻塞 / 非阻塞针对的是 I/O 调用者(即应用程序),而同步 / 异步针 对的是 I/O 执行者(即系统)。
性能观测
容量
文件数据的使用情况
df 命 令,就能查看文件系统的磁盘空间使用情况
总空间用 1K-blocks 的数量来表示,你可以给 df 加上 -h 选项,以获得更好的可读性:
索引节点的使用情况
除了文件数据,索引节点也占用磁盘空间。 你可以给 df 命令加上 -i 参数,查看索引节点的使用情况:
索引节点的容量,(也就是 Inode 个数)是在格式化磁盘时设定好的,一般由格式化工具 自动生成。当你发现索引节点空间不足,但磁盘空间充足时,很可能就是过多小文件导致 的。
一般来说,删除这些小文件,或者把它们移动到索引节点充足的其他磁盘中,就可以 解决这个问题。
缓存
可以用 free 或 vmstat,来观察页缓存的大小。
Cache : 页缓存和可回收 Slab 缓存的和
free 输出的 Cache,是页缓存和可回收 Slab 缓存的和,可以从 /proc/meminfo ,直接得到它们的大小:
cat /proc/meminfo | grep -E "SReclaimable|Cached"
Buffer 以及 文件系统中的目录项和索引节点缓存
文件名以及文件之间的目录关系,都放在目录项缓存中。而这是一个基于内存的 数据结构,会根据需要动态构建。所以,查找文件时,Linux 就会动态构建不在缓存中的目 录项结构,导致 dentry 缓存升高。
除了目录项缓存增加,Buffer 的使用也会增加。如果你用** vmstat 1 **
观察一下,会发 现 Buffer 和 Cache 都在增长,Buffer 的增长是因为,构建目录项缓存所需的元数据(比如文件名称、索引节点 等),需要从文件系统中读取。
内核使用 Slab 机制,管理目录项和索引节点的缓存。/proc/meminfo 只给出了 Slab 的整体大小,具体到每一种 Slab 缓存,还要查看 /proc/slabinfo 这个文件。
所有目录项和各种文件系统索引节点的缓存情况:
cat /proc/slabinfo | grep -E '^#|dentry|inode'
- dentry 行表示目录项缓存,
- inode_cache 行,表示 VFS 索引节点缓存,其余 的则是各种文件系统的索引节点缓存。
/proc/slabinfo 的列比较多,在实际性能分析中,我 们更常使用 slabtop ,来找到占用内存最多的缓存类型。
按下 c 按照缓存大小排序,按下 a 按照活跃对象数排序
slabtop
文件系统总结
文件系统:是对存储设备上的文件,进行组织管理的一种机制。
VFS:为了支持各类不同的文件系统,Linux 在各种文件系统实现上,抽象了一层虚拟文件系统(VFS)。VFS 定义了一组所有文件系统都支持的数据结构和标准接口。这样,用户进程和内核中的 其他子系统,就只需要跟 VFS 提供的统一接口交互,而不需要关注文件系统的具 体实现;对具体的文件系统来说,只需要按照 VFS 的标准,就可以无缝支持各种应用程 序。
为了降低慢速磁盘对性能的影响,文件系统又通过页缓存、目录项缓存以及索引节点缓存, 缓和磁盘延迟对应用程序的影响。
Linux 磁盘分类
磁盘按照存储介质来分类
机械和SSD的区别是什么?
- 机械磁盘,也称为硬盘驱动器(Hard Disk Driver),通常缩写为 HDD。盘片和读写磁头组成,数据就存储在盘片的环状磁道中。在读写数据前,需要移动读写磁头,定位到数据所在的磁道,然后才能访问数据。
- 如果 I/O 请求刚好连续,那就不需要磁道寻址,也就是(顺序)连续 I/O 的工作原理。
- 随机 I/O,它需要不停地移动磁头,来定位数据位置,所以读写速度就会比较慢。
- 固态磁盘(Solid State Disk),通常缩写为 SSD,由固态电子元器件组成。固态磁盘不需要磁道寻址,所以,不管是连续 I/O,还是随机 I/O 的性能,都比机械磁盘要好得 多。
(顺序)连续 I/O 可以通过预读的方式,来减少 I/O 请求的次数,这也是其性能优异的 一个原因。很多性能优化的方案,也都会从这个角度出发,来优化 I/O 性能。
机械磁盘和固态磁盘还分别有一个最小的读写单位。机械磁盘的最小读写单位是扇区,一般大小为 512 字节。而固态磁盘的最小读写单位是页,通常大小是 4KB、8KB 等。
如果每次都读写 512 字节这么小的单位的话,效率很低。所 以,文件系统会把连续的扇区或页,组成逻辑块,然后以逻辑块作为最小单元来管理数据。 常见的逻辑块的大小是 4KB,也就是说,连续 8 个扇区,或者单独的一个页,都可以组成 一个逻辑块。
SSD的随机访问性能好吗?
无论机械磁盘,还是固态磁盘,相同磁盘的随机 I/O 都要比连续 I/O 慢很多,
对机械磁盘来说,随机 I/O 需要更多的磁头寻道和盘片旋转,它的性能自然要比连续 I/O 慢。
对固态磁盘来说,虽然它的随机性能比机械硬盘好很多,但同样存在“先擦除再写 入”的限制。随机读写会导致大量的垃圾回收,所以相对应的,随机 I/O 的性能比起连 续 I/O 来,也还是差了很多。
磁盘按照接口来分类
硬盘分为 IDE(Integrated Drive Electronics)、SCSI(Small Computer System Interface) 、SAS(Serial Attached SCSI) 、SATA(Serial ATA) 、FC(Fibre Channel) 等。
不同的接口,往往分配不同的设备名称。比如, IDE 设备会分配一个 hd 前缀的设备名, SCSI 和 SATA 设备会分配一个 sd 前缀的设备名。如果是多块同类型的磁盘,就会按照 a、b、c 等的字母顺序来编号。
磁盘按照架构来分类
当把磁盘接入服务器后,按照不同的使用方式,又可以把它们划 分为多种不同的架构。
- 作为独立磁盘设备来使用。往往还会根据需要,划分为不同 的逻辑分区,每个分区再用数字编号。/dev/sda 还可以分成两 个分区 /dev/sda1 和 /dev/sda2。
- 多块磁盘组合成一个逻辑磁盘,构成冗余独立磁盘阵列,也就 是 RAID(Redundant Array of Independent Disks),从而可以提高数据访问的性能, 并且增强数据存储的可靠性。
- RAID0 有最优的读写性能,但不提供数据冗余的功能。
- 而其他级别的 RAID,在提供数据冗余的基础上,对读写性能也有一定程度的优化。
- 最后一种架构,是把这些磁盘组合成一个网络存储集群,再通过 NFS、SMB、iSCSI 等网 络存储协议,暴露给服务器使用。
其实在 Linux 中,磁盘实际上是作为一个块设备来管理的,也就是以块为单位读写数据, 并且支持随机读写。每个块设备都会被赋予两个设备号,分别是主、次设备号。主设备号用 在驱动程序中,用来区分设备类型;而次设备号则是用来给多个同类设备编号。
通用块层
虚拟文件系统 VFS 类似,为了减小不同块设备的差异带来的影响, Linux 通过一个统一的通用块层,来管理各种不同的块设备。
processon
通用块层,其实是处在文件系统和磁盘驱动中间的一个块设备抽象层。它主要有两个功能 。
- 第一个功能跟虚拟文件系统的功能类似。向上,为文件系统和应用程序,提供访问块设备 的标准接口;向下,把各种异构的磁盘设备抽象为统一的块设备,并提供统一框架来管理 这些设备的驱动程序。
- 第二个功能,通用块层还会给文件系统和应用程序发来的 I/O 请求排队,并通过重新排序、请求合并等方式,提高磁盘读写的效率。也就是 I/O 调度。
通用块层的IO调度算法
事实上,Linux 内核支持四 种 I/O 调度算法,分别是 NONE、NOOP、CFQ 以及 DeadLine。
- **第一种 NONE **,更确切来说,并不能算 I/O 调度算法。因为它完全不使用任何 I/O 调度器,对文件系统和应用程序的 I/O 其实不做任何处理,常用在虚拟机中(此时磁盘 I/O 调 度完全由物理机负责)。
- **第二种 NOOP **,是最简单的一种 I/O 调度算法。它实际上是一个先入先出的队列,只做一 些最基本的请求合并,常用于 SSD 磁盘。
- 第三种 CFQ(Completely Fair Scheduler),也被称为完全公平调度器,是现在很多发行 版的默认 I/O 调度器,它为每个进程维护了一个 I/O 调度队列,并按照时间片来均匀分布 每个进程的 I/O 请求。类似于进程 CPU 调度,CFQ 还支持进程 I/O 的优先级调度,所以它适用于运行大量进程 的系统,像是桌面环境、多媒体应用等。
- DeadLine 调度算法,分别为读、写请求创建了不同的 I/O 队列,可以提高机械 磁盘的吞吐量,并确保达到最终期限(deadline)的请求被优先处理。DeadLine 调度算 法,多用在 I/O 压力比较重的场景,比如数据库等。
总结:I/O 栈==文件系统层、通用块层和设备层
- 文件系统层,包括虚拟文件系统和其他各种文件系统的具体实现。它为上层的应用程序, 提供标准的文件访问接口;对下会通过通用块层,来存储和管理磁盘数据。
- 通用块层,包括块设备 I/O 队列和 I/O 调度器。它会对文件系统的 I/O 请求进行排队, 再通过重新排序和请求合并,然后才要发送给下一级的设备层。
- 通用块层是 Linux 磁盘 I/O 的核心。向上,它为文件系统和应用程序,提供访问了 块设备的标准接口;向下,把各种异构的磁盘设备,抽象为统一的块设备,并会对文件系统 和应用程序发来的 I/O 请求进行重新排序、请求合并等,提高了磁盘访问的效率。
- 设备层,包括存储设备和相应的驱动程序,负责最终物理设备的 I/O 操作。
- **存储系统的 I/O **,通常是整个系统中最慢的一环。所以, Linux 通过多种缓存机制来优化 I/O 效率。
- 为了优化文件访问的性能,会使用页缓存、索引节点缓存、目录项缓存等多种缓存 机制,以减少对下层块设备的直接调用。
- 同样,为了优化块设备的访问效率,会使用缓冲区,来缓存块设备的数据。
磁盘 I/O 性能指标
使用率、饱 和度、IOPS、吞吐量以及响应时间等。这五个指标,是衡量磁盘性能的基本指标。
- 使用率,是指磁盘处理 I/O 的时间百分比。过高的使用率(比如超过 80%),通常意味 着磁盘 I/O 存在性能瓶颈。
- 使用率只考虑有没有 I/O,而不考虑 I/O 的大小,换句话说,当使用率是 100% 的时候,磁盘依然有可能接受新的 I/O 请求。
- 饱和度,是指磁盘处理 I/O 的繁忙程度。过高的饱和度,意味着磁盘存在严重的性能瓶 颈。当饱和度为 100% 时,磁盘无法接受新的 I/O 请求
- IOPS(Input/Output Per Second),是指每秒的 I/O 请求数。
- 吞吐量,是指每秒的 I/O 请求大小。
- 响应时间,是指 I/O 请求从发出到收到响应的间隔时间。
不要孤立地去比较某一指标,而要结合读写比例、I/O 类型(随机还是连续)以及 I/O 的大小,综合来分析。在数据库、大量小文件等这类随机读写比较多的场景中,IOPS 更能反映系统的 整体性能;而在多媒体等顺序读写较多的场景中,吞吐量才更能反映系统的整体性能。
磁盘 I/O 性能工具
磁盘 I/O 观测
在为应用程序的服务器选型时,要先对磁盘的 I/O 性能进行基准测试,以 便可以准确评估,磁盘性能是否可以满足应用程序的需求。推荐用性能测试工具 fio ,来测试磁盘的 IOPS、吞吐量以及响应时间等。
测试出,不同 I/O 大小(一般是 512B 至 1MB 中间的若干值)分别在随机读、顺序读、随机写、顺序写等各种场景下的性能情况。 用性能工具得到的这些指标,可以作为后续分析应用程序性能的依据。一旦发生性能问题, 你就可以把它们作为磁盘性能的极限值,进而评估磁盘 I/O 的使用情况。
每块磁盘的使用情况:iostat
iostat 是最常用的磁盘 I/O 性能观测工具,它提供了每个磁盘的使用率、IOPS、吞吐量等 各种常见的性能指标,当然,这些指标实际上来自 /proc/diskstats。
-d -x 表示显示所有磁盘 I/O 的指标
第一列的 Device 表示磁盘设备的名字
%util ,磁盘 I/O 使用率;
r/s+ w/s ,就是 IOPS;
rkB/s+wkB/s ,就是吞吐量;
r_await+w_await ,就是响应时间。
结合请求的大小( rareq-sz 和 wareq-sz)一起分析。从 iostat 并不能直接得到磁盘饱和度。可以把观测到的,平均请求队列长度或者读写请求完成的等待时间,跟基准测试的结果(比如通过 fio)进行对比,综合评估磁盘的饱和情况。
进程 I/O 观测
iostat 只提供磁盘整体的 I/O 性能数据,缺点在于,并不能知道具体是哪些进 程在进行磁盘读写。
pidstat 实时查看
pidstat 加上 -d 参数,你就可以 看到进程的 I/O 情况,
实时查看每一秒每个进程的 I/O 情况,包括下面这些内容。
用户 ID(UID)和进程 ID(PID) 。
每秒读取的数据大小(kB_rd/s) ,单位是 KB。
每秒发出的写请求数据大小(kB_wr/s) ,单位是 KB。
每秒取消的写请求数据大小(kB_ccwr/s) ,单位是 KB。
iotop:I/O 大小对进程排序
iotop。它是一个类似于 top 的工具,前两行分别表示,进程的磁盘读写大小总数和磁盘真实的读写大 小总数。因为缓存、缓冲区、I/O 合并等因素的影响,它们可能并不相等。
剩下的部分,则是从各个角度来分别表示进程的 I/O 情况,包括线程 ID、I/O 优先级、每 秒读磁盘的大小、每秒写磁盘的大小、换入和等待 I/O 的时钟百分比等。