一、前言
前面我们谈了的内容如下:
这一节我们将谈下Linux磁盘I/O子系统
二、概览
在进程解码和执行指令之前,要把数据从盘片的扇区中恢复到进程的缓存和寄存器中。程序执行结果又被写回到磁盘中。可以说,现在的服务器大部分都是I/O出现了瓶颈。
下图展示了基本的I/O子系统架构:
我们以把数据写入磁盘为例。当执行向磁盘写入数据操作的时候,会发生如下的一系列基本操作。假设文件数据存在于磁盘扇区上,并且已经被读入到页缓存中。
- 进程使用write()系统调用写入文件。
- 内核更新映射到文件的page cache。
- 内核线程pdflush负责把页缓存刷入到磁盘中。
- 文件系统层把各个块缓存放入一个bio结构,并且提交一个写入到块设备层的请求。
- 块设备层从上层获得请求,执行I/O elevator操作,把请求放入到I/O请求队列中。
- 磁盘驱动,例如SCSI或其它特定驱动将会负责写操作。
- 磁盘驱动固件执行硬件操作,例如寻址、旋转、数据传送到磁盘的扇区。
以上就是程序把数据写到硬盘的大概过程。
三、缓存
在过去的20年里,处理器的性能提升要大于其它计算机组件(例如处理器缓存、总线、RAM 和磁盘等)。因为存储器和磁盘的速度限制了整个系统性能,所以系统的整体性能并没有因 为处理器速度的提升而提升。但是,通过把常用数据放入到更快速度的内存中,以缓存机制 可以解决这个问题。它减少了访问比较慢的存储器的次数。现代计算机系统在几乎所有的I/O 组件中都使用了这项技术,例如硬盘驱动缓存(hard disk drive cache)、磁盘控制器缓存 (disk controller cache)和文件系统缓存(file system cache),各个应用都使用到了缓存。
3.1 存储器层次
上展示了缓存等级的概念,由于CPU寄存器和磁盘之间的访问速度差异很大,CPU会花很 多时间等待磁盘中的数据,这导致CPU的高性能无用武之地。存储器层次结构通过L1 cache、L2 cache、RAM和其它在CPU和磁盘之间的缓存来消除这种影响。这减少了进程访 问较慢存储器和磁盘的机会。离处理器比较近的缓存拥有更高的CPU访问速度和较小的空间。
3.2 局部性引用(Locality of reference)
如上文所说,高缓存命中率是提升性能的关键。为了获得高缓存命中率,使用”局部性引用“的 技术。这个技术基于如下的原则:
- 最近使用过的数据即将被使用的可能性很高(时间局部性,temporal locality)。
- 使用过数据的附近数据被使用的可能性很高(空间局部性,spatial locality)。
Linux在很多组件中用到了这个原则,例如页缓存、文件对象缓存(i-node缓存、目录条 目缓存等等)、预读缓冲区等。如下图所示:
3.3 刷新脏缓冲区(Flushing a dirty buffer)
在进程从磁盘中读数据时,数据被复制到内存中。该进程和其它进程都可以在内存缓存中读 取同样的数据副本。当进程尝试改变数据,进程首先修改内存中的数据,这时候,磁盘和内 存中的数据就不一致了,内存中的数据就叫做脏缓冲(dirty buffer)。脏缓冲应该尽快同步到 磁盘上,否则,如果突然崩溃,内存中的数据会丢失。
同步脏缓冲的进程叫做flush,在Linux内核2.6中,pdflush内核线程负责把数据写入到磁盘
上。数据会定时刷新(kupdate),或者当内存中的脏缓冲到了阀值的比例的时候
(bdflush)。这个阀值在/proc/sys/vm/dirty_background_ratio文件中。
下图展示刷新脏缓冲区的过程:
四、块层
块层处理所有和块设备相关的操作。块层中的关键数据结构是bio。bio结构是文件系统层和块 层之间的一个接口。
在执行写操作时,文件系统层尝试写入由块缓冲组成的页缓存。通过把相邻的块放在一起, 组成bio结构,然后把bio发送给块层。
块层处理bio请求,并且把请求链接到I/O请求队列中。这个链接操作叫做I/O elevator。在 Linux内核2.6中,有四种类型的I/O elevator算法。
4.1 块大小
驱动器上可以读出和写入的最小数据量会对服务器性能有直接影响。作为参考,如果服务器 要处理很多小文件,设置较小的块大小比较好。如果服务是需要处理大文件的,较大的块大 小可能会提高性能。不能改变运行中的文件系统的块大小,只有重新格式化才能修改当前的 块大小。
这里的块大小就是文件系统在格式化时指定的存储数据的block大小,现在的服务器因为硬盘容量巨大,所以一般设置block为4K大小。
4.2 I/O elevator
Linux2.6 内核使用了一种新的I/O elevator模型。Linux2.4提供的是一种通用目标的I/O elevator,2.6则提供了四个可用的elevator。因为Linux操作系统有各种广泛的用途,在不同场 景下,I/O和负载都有很大的差异。一台Linux笔记本要满足的的I/O需求可能比一个10000用户 的数据库系统还多样。为了满足多样性,有四种可选的I/O elevator。
-
预期(Anticipatory) 预期 I/O elevator是基于假设一个块设备只有一个物理寻道指针(例如一块SATA盘)。预期 elevator使用期限(deadline)机制,并且加上了启发式的期限。正如名字所表明的,预期I/O elevator尝试往磁盘中写入一个大的流,而不是很多非常小的随机磁盘访问。启发式预期可能 会导致潜在的写I/O。它一般适用于的高吞吐的通用操作系统,例如pc。在内核2.6.18中, elevator是标准的I/O调度算法,但是,大多数企业Linux发行版还是默认使用CFQ elevator。
-
完全公平排队(CFQ,Complete Fair Queuing) 通过为每个进程维护I/O队列,CFQ为进程实现QoS(服务质量,Quality of Service)策略。 CFQ elevator适用于拥有很多进程竞争的极多用户的系统。它努力的避免进程饿死,具有低 延时的特点。从内核2.6.18开始,加强版的CFQ elevator是默认的I/O调度器。 基于系统设置和负载模式,CFQ调度器可能拖慢一个主进程的运行,例如,一个使用公平算 法的大型数据库系统。根据默认配置,会完全基于竞争处理进程组。一个单一的数据库,所 有的通过页缓存(所有的pdflush实例都在一个pgroup)的写都被CFQ认为是能够和其它很多 后台进程竞争的一个进程。在这个场景下,尝试I/O调度器子配置或deadline调度器可能更加 有用。
-
期限(Deadline) 期限elevator是使用dealine算法的循环elevator(轮询,round robin),提供接近实时行为的 I/O子系统。在维护不错吞吐量磁盘的时候,期限elevator具有优良的请求延迟。期限算法确保 不会发生进程饥饿的状况。
-
NOOP NOOP表示没有操作(No Operation),顾名思义。NOOP elevator很精简,它是简单的FIFO 队列,不做任何数据排序。NOOP把相邻的数据请求做简单的合并,对磁盘I/O来说,它增加 了非常小的处理器开销。NOOP elevator假设块设备拥有自己的elevator算法,例如SCSI的 TCQ,或者没有寻道延迟,例如flash卡。
五、I/O设备驱动
Linux内核使用设备驱动控制设备。设备驱动通常是一个独立的内核模块,为各个设备或者一 组设备提供Linux操作系统支持。一旦设备驱动被载入,就作为Linux内核的一部分运行,并且完全控制设备。这里我们会讨论SCSI设备驱动。
5.1 SISC
小型计算机系统接口(Small Computer System Interface,SCSI)是最常用的I/O设备和技 术,尤其在服务器环境。在Linux内核中,SCSI设备也受设备驱动模块控制。它由如下几个类 型的模块组成。
-
上层驱动程序(upper level drivers):sd_mod,sr_mode(SCSI-CDROM), st(SCSI Tape),sq(SCSI generic device)等等。它们提供了各种类型SCSI设备的 驱动功能,例如SCSI CD-ROM。
-
中间层驱动:scsi_mod 。实现SCSI协议和通用的SCSI功能。
-
低级别驱动程序,提供到各设备的低级别接入。底层驱动程序,基本上是特定于硬件设 备,并且提供给每个设备。例如,ips是IBM ServerRAID控制器,ql2300专门为Qlogic HBA,mptscsih是LSI Logic SCSI的驱动器,等等。
-
伪驱动程序:ide-scsi。用作IDE-SCSI仿真。
下图展示了SCSI驱动的架构:
六、RAID和存储系统
存储系统的选择和配置,以及RAID类型都是影响系统性能的重要因素。
磁盘阵列(Redundant Arrays of Independent Drives,RAID),有“独立磁盘构成的具有冗余能力的阵列”之意。通俗点说,磁盘阵列是由很多价格较便宜的磁盘,组合成一个容量巨大的磁盘组,利用个别磁盘提供数据所产生加成效果提升整个磁盘系统效能。利用这项技术,将数据切割成许多区段,分别存放在各个硬盘上。
有关RAID的详尽知识参考百度百科:磁盘阵列
在选择硬盘上,SSD > 高转速机械硬盘 > 低转速机械硬盘
七、下一节是???
谈完Linux的磁盘I/O子系统,下一节将会谈一下Linux的网络子系统