磁盘I/O子系统
在进程解码和执行指令之前,要把数据从盘片的扇区中恢复到进程的缓存和寄存器中。程序执行结果又被写回到磁盘中。
本节中我们会了解Linux的I/O子系统,这也是一个影响系统性能的重要部分。
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),各个应用都使用到了缓存。
存储器层次
下图展示了缓存等级的概念,由于CPU寄存器和磁盘之间的访问速度差异很大,CPU会花很多时间等待磁盘中的数据,这导致CPU的高性能无用武之地。存储器层次结构通过L1 cache、L2 cache、RAM和其它在CPU和磁盘之间的缓存来消除这种影响。这减少了进程访问较慢存储器和磁盘的机会。离处理器比较近的缓存拥有更高的CPU访问速度和较小的空间。
这项技术还带来了局部引用(Locality of reference)的好处,越高的的缓存命中率和越快的内存,就能越快的获取到数据。
局部性引用(Locality of reference)
如上文所说,高缓存命中率是提升性能的关键。为了获得高缓存命中率,使用”局部性引用“的技术。这个技术基于如下的原则:
- 最近使用过的数据即将被使用的可能性很高(时间局部性,temporal locality)。
- 使用过数据的附近数据被使用的可能性很高(空间局部性,spatial locality)。
Linux在很多组件中用到了这个原则,例如页缓存、文件对象缓存(i-node缓存、目录条目缓存等等)、预读缓冲区等。
刷新脏缓冲区(Flushing a dirty buffer)
在进程从磁盘中读数据时,数据被复制到内存中。该进程和其它进程都可以在内存缓存中读取同样的数据副本。当进程尝试改变数据,进程首先修改内存中的数据,这时候,磁盘和内存中的数据就不一致了,内存中的数据就叫做脏缓冲(dirty buffer)。脏缓冲应该尽快同步到磁盘上,否则,如果突然崩溃,内存中的数据会丢失。
同步脏缓冲的进程叫做flush,在Linux内核2.6中,pdflush内核线程负责把数据写入到磁盘上。数据会定时刷新(kupdate),或者当内存中的脏缓冲到了阀值的比例的时候(bdflush)。这个阀值在/proc/sys/vm/dirty_background_ratio文件中。更多信息,请参考”设置内核swap和pdflush行为“。
块层
块层处理所有和块设备相关的操作。块层中的关键数据结构是bio。bio结构是文件系统层和块层之间的一个接口。
在执行写操作时,文件系统层尝试写入由块缓冲组成的页缓存。通过把相邻的块放在一起,组成bio结构,然后把bio发送给块层。
块层处理bio请求,并且把请求链接到I/O请求队列中。这个链接操作叫做I/O elevator。在Linux内核2.6中,有四种类型的I/O elevator算法。
块大小
驱动器上可以读出和写入的最小数据量会对服务器性能有直接影响。作为参考,如果服务器要处理很多小文件,设置较小的块大小比较好。如果服务是需要处理大文件的,较大的块大小可能会提高性能。不能改变运行中的文件系统的块大小,只有重新格式化才能修改当前的块大小。
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卡。
注意:在内核2.6.18中,可以为每个磁盘子系统选择不同的I/O elevator,而不必要对整个系统做统一的设置。
I/O设备驱动
Linux内核使用设备驱动控制设备。设备驱动通常是一个独立的内核模块,为各个设备或者一组设备提供Linux操作系统支持。一旦设备驱动被载入,就作为Linux内核的一部分运行,并且完全控制设备。这里我们会讨论SCSI设备驱动。
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仿真。
如果某一指定功能专为该款设备而设立,那么它应该在设备固件和低级别驱动程序中实现功能。所支持的功能由你所使用的硬件和设备驱动版本决定。设备本身应该也支持所需求的功能才行。指定功能通常由设备驱动参数进行优化。你可以尝试在/etc/modules.conf中做一些性能调优。参考设备和设备驱动器文档来获得调优指导。
RAID和存储系统
存储系统的选择和配置,以及RAID类型都是影响系统性能的重要因素。Linux支持软RAID,但是这部分的细节不在本文的讨论范围之类。我们在“安装Linux之前的硬件考虑”一章中涉及了一些调优的方法。
部分IBM存储的解决方法,如下:- IBM System x Servers 性能调优,SG24-5287。
- IBM System Storage操作手册,SG24-5250。
- SAN(存储区域网络,Storage Area Networks)介绍,SG24-5470。