24-基础篇:Linux磁盘I/O是怎么工作的(上)





磁盘

按照存储介质分类:机械磁盘和固态磁盘

  1. 机械磁盘,也称为硬盘驱动器,通常缩写为HDD
    机械磁盘主要由盘片和读写磁头组成,数据就存储在盘片的环状磁道中
    在读写数据前,需要移动读写磁头,定位到数据所在的磁道,然后才能访问数据

    连续I/O,请求刚好连续,那就不需要磁道寻址,可以获得最佳性能

    随机I/O,它需要不停地移动磁头,来定位数据位置,所以读写速度就会比较慢

  2. 固态磁盘,通常缩写为SSD,由固态电子元器件组成
    固态磁盘不需要磁道寻址,所以不管是连续I/O,还是随机I/O的性能,都比机械磁盘要好得多


其实,无论机械磁盘,还是固态磁盘,相同磁盘的随机I/O都要比连续I/O慢很多,原因也很明显

  1. 对机械磁盘来说,由于随机I/O需要更多的磁头寻道和盘片旋转,它的性能自然要比连续I/O慢
  2. 对固态磁盘来说,虽然它的随机性能比机械硬盘好很多,但同样存在“先擦除再写入”的限制
    随机读写会导致大量的垃圾回收,所以相对应的,随机I/O的性能比起连续I/O来,也还是差了很多
    连续I/O还可以通过预读的方式,来减少I/O请求的次数,这也是其性能优异的一个原因
    很多性能优化的方案,也都会从这个角度出发,来优化I/O性能

此外,机械磁盘和固态磁盘还分别有一个最小的读写单位

  1. 机械磁盘的最小读写单位是扇区,一般大小为512字节
  2. 固态磁盘的最小读写单位是页,通常大小是4KB、8KB等

按照接口分类:

可以把硬盘分为IDE、SCSI 、SAS、SATA 、FC等

不同的接口,往往分配不同的设备名称
比如, IDE设备会分配一个hd前缀的设备名
SCSI和SATA设备会分配一个sd前缀的设备名
如果是多块同类型的磁盘,就会按照a、b、c等的字母顺序来编号


按照使用方式划分为多种不同的架构:

  1. 直接作为独立磁盘设备来使用。
    这些磁盘,往往还会根据需要,划分为不同的逻辑分区,每个分区再用数字编号
    例如/dev/sda,还可以分成两个分区/dev/sda1和/dev/sda2
  2. RAID冗余独立磁盘阵列。
    可以提高数据访问的性能,并且增强数据存储的可靠性
    根据容量、性能和可靠性需求的不同
    RAID一般可以划分为多个级别,如RAID0、RAID1、RAID5、RAID10等
    1. RAID0有最优的读写性能,但不提供数据冗余的功能
    2. 其他级别的RAID,在提供数据冗余的基础上,对读写性能也有一定程度的优化
  3. 磁盘组合成一个网络存储集群。
    通过 NFS、SMB、iSCSI 等网 络存储协议,暴露给服务器使用

在Linux中,磁盘实际上是作为一个块设备来管理的,也就是以块为单位读写数据,并且支持随机读写
每个块设备都会被赋予两个设备号,分别是主、次设备号
主设备号用在驱动程序中,用来区分设备类型
而次设备号则是用来给多个同类设备编号



通用块层

跟虚拟文件系统VFS类似,为了减小不同块设备的差异带来的影响,Linux通过一个统一的通用块层,来管理各种不同的块设备

通用块层,其实是处在文件系统和磁盘驱动中间的一个块设备抽象层
它主要有两个功能:

  1. 第一个功能跟虚拟文件系统的功能类似
    向上,为文件系统和应用程序,提供访问块设备的标准接口
    向下,把各种异构的磁盘设备抽象为统一的块设备,并提供统一框架来管理这些设备的驱动程序
  2. 第二个功能,通用块层还会给文件系统和应用程序发来的I/O请求排队
    并通过重新排序、请求合并等方式,提高磁盘读写的效率

其中,对I/O请求排序的过程,就是I/O调度,事实上,Linux内核支持四种I/O调度算法

  1. NONE更确切来说,并不能算I/O调度算法
    因为它完全不使用任何I/O调度器,对文件系统和应用程序的I/O其实不做任何处理,常用在虚拟机中(此时磁盘I/O调度完全由物理机负责)
  2. NOOP 是最简单的一种I/O调度算法
    它实际上是一个先入先出的队列,只做一些最基本的请求合并,常用于SSD磁盘
  3. CFQ也被称为完全公平调度器
    是现在很多发行版的默认I/O调度器,它为每个进程维护了一个I/O调度队列
    并按照时间片来均匀分布每个进程的I/O请求
    类似于进程CPU调度,CFQ还支持进程I/O的优先级调度,
    所以它适用于运行大量进程的系统,像是桌面环境、多媒体应用等
  4. DeadLine调度算法
    分别为读、写请求创建了不同的I/O队列,可以提高机械磁盘的吞吐量,并确保达到最终期限的请求被优先处理
    DeadLine调度算法,多用在I/O压力比较重的场景,比如数据库等


I/O栈

可以把Linux存储系统的I/O栈,由上到下分为三个层次
分别是文件系统层、通用块层和设备层
这三个I/O层的关系如下图所示,这其实也是Linux存储系统的I/O栈全景图
image-20211217102722562

根据这张I/O栈的全景图,可以更清楚地理解存储系统I/O的工作原理

  1. 文件系统层,包括虚拟文件系统和其他各种文件系统的具体实现
    它为上层的应用程序,提供标准的文件访问接口
    对下会通过通用块层,来存储和管理磁盘数据
  2. 通用块层,包括块设备I/O队列和I/O调度器
    它会对文件系统的I/O请求进行排队, 再通过重新排序和请求合并,然后才要发送给下一级的设备层
  3. 设备层,包括存储设备和相应的驱动程序,负责最终物理设备的I/O操作

存储系统的I/O ,通常是整个系统中最慢的一环。所以,Linux通过多种缓存机制来优化I/O效率

比方说,为了优化文件访问的性能,会使用页缓存、索引节点缓存、目录项缓存等多种缓存机制,以减少对下层块设备的直接调用

同样,为了优化块设备的访问效率,会使用缓冲区,来缓存块设备的数据



小结

由文件系统层、通用块层和设备层构成的Linux存储系统I/O栈

通用块层是Linux磁盘I/O的核心,向上,它为文件系统和应用程序,提供访问了块设备的标准接口
向下,把各种异构的磁盘设备,抽象为统一的块设备
并会对文件系统和应用程序发来的I/O请求进行重新排序、请求合并等,提高了磁盘访问的效率


posted @ 2021-12-17 10:42  李成果  阅读(181)  评论(0编辑  收藏  举报