服务器高性能程序 磁盘I/O篇

Linux IO系统的架构图

一.设备 ——– 影响磁盘性能的因素

硬盘的转速影响硬盘的整体性能。一般情况下转速越大,性能会越好。

硬盘的性能因素主要包括两个:

  • 平均访问时间
  • 传输速率

平均访问时间包括两方面因素:

  • 平均寻道时间(Average Seek Time)是指硬盘的磁头移动到盘面指定磁道所需的时间。一般在3ms至15ms之间。

  • 平均旋转等待时间(Latency)是指磁头已处于要访问的磁道,等待所要访问的扇区旋转至磁头下方的时间。一般在2ms至6ms之间。

传输速率(Data Transfer Rate) 硬盘的数据传输率是指硬盘读写数据的速度,单位为兆字节每秒(MB/s)。磁盘每秒能传输80M~320M字节。

传输速率包括内部传输速率和外部传输速率。

  • 内部传输率(Internal Transfer Rate) 也称为持续传输率(Sustained Transfer Rate),它反映了硬盘缓冲区未用时的性能。内部传输率主要依赖于硬盘的旋转速度。

  • 外部传输率(External Transfer Rate)也称为突发数据传输率(Burst Data Transfer Rate)或接口传输率,它标称的是系统总线与硬盘缓冲区之间的数据传输率,外部数据传输率与硬盘接口类型和硬盘缓存的大小有关。STAT2 的传输速率在300MB/s级别。

因此在硬件级上,提高磁盘性能的关键主要是降低平均访问时间。

二.设备驱动

内存到硬盘的传输方式:poll,中断,DMA

DMA:当 CPU 初始化这个传输动作,传输动作本身是由 DMA 控制器 来实行和完成。

DMA控制器获得总线控制权后,CPU即刻挂起或只执行内部操作,由DMA控制器输出读写命令,直接控制RAM与I/O接口进行DMA传输。DMA每次传送的是磁盘上相邻的扇区。Scatter-gather DMA允许传送不相邻的扇区。

CPU性能与硬盘与内存的数据传输速率关系不大。

设备驱动内有一个结构管理着IO的请求队列

structrequest_queue(include/linux/Blkdev.h)

这里不仅仅有读写请求的数据块,还有用于IO调度的回调函数结构。每次需要传输的时候,就从队列中选出一个数据块交给DMA进行传输。

所以IO调度的回调函数这是降低平均访问的时间的关键。

三.OS

IO调度器

Linux kernel提供了四个调度器供用户选择。他们是noop,cfq,deadline,as。 可以在系统启动时设置内核参数elevator=<name>来指定默认的调度器。 也可以在运行时为某个块设备设置IO调度程序。

下面来简要介绍这四个调度器的电梯调度算法。

  • Noop:最简单的调度算法。新的请求总是被添加到队头或者队尾,然后总是从队头中选出将要被处理的请求。

  • CFQ:(Complete FarinessQueueing)它的目标是在所有请求的进程中平均分配IO的带宽。 因此,它会根据进程创建自己的请求队列,然后将IO请求放入相应的队列中。在使用轮转法从每个非空的队列中取出IO请求。

  • Deadline:使用了四个队列,两个以磁盘块序号排序的读写队列,两个以最后期限时间排序的读写队列。 算法首先确定下一个读写的方向,读的优先级高于写。 然后检查被选方向的最后期限队列:如果最后期限时间的队列中有超时的请求,则将刚才的请求移动至队尾,然后在磁盘号排序队列中从超时请求开始处理。 当处理完一个方向的请求后,在处理另一个方向的请求。(读请求的超时时间是500ms,写请求的超时时间是5s)

  • Anticipatory:它是最复杂的IO调度算法。 和deadline算法一样有四个队列。还附带了一些启发式策略。它会从当前的磁头位置后的磁盘号中选择请求。 在调度了一个由P进程的IO请求后,会检查下一个请求,如果还是P进程的请求,则立即调度,如果不是,同时预测P进程很快会发出请求,则还延长大约7ms的时间等待P进程的IO请求。

Write/Read函数

以ext3的write为例:

系统调用write()的作用就是修改页高速缓存内的一些页的内容,如果页高速缓存内没有所要的页则分配并追加这些页。

当脏页达到一定数量或者超时后,将脏页刷回硬盘。也可以执行相关系统调用。

为什么要达到一定数量,是因为延迟写能在一定层度上提高系统的性能,这也使得块设备的平均读请求会多于写请求。

在程序中调用write函数,将进入系统调用f_op->write。 这个函数将调用ext3的do_sync_write。这个函数将参数封装后调用generic_file_aio_write。由参数名可以看出同步写变成了异步写。 如果没有标记O_DIRECT,将调用函数generic_file_buffered_write将写的内容写进kernel的高速页缓存中。Buffer是以page为单位即4k。 之后当调用cond_resched()进行进程的调度,DMA会将buffer中的内容写进硬盘。

所以当每次以4k为单位写入硬盘时效率会达到最高。下面是UNIX环境高级编程的实验结果:

下图是linux 的块设备的数据操作层次:

Sector扇区:是设备驱动和IO调度程序处理数据粒度。

Block块:是VFS和文件系统处理数据的粒度。其大小不唯一,可以是512,1024,2048,4096字节。内核操作的块大小是4096字节。

Segment段:是DMA传送的单位。每一个段包含了相邻的扇区,它能使DMA传送不相邻的扇区。

四.用户程序

根据以上的分析,我们的write buffer一般设置为4K的倍数。

在程序中有意识的延迟写。这个是os的策略,当然也可以应用到程序的设计中。 当然也会有缺点:1.如果硬件错误或掉电,则会丢失内容(做额外的备份)2.需要额外的内存空间。(牺牲内存来提高IO的效率)

我们还需根据系统的IO调度器的调度策略,设计出不同的IO策略。尽量降低磁盘的平均访问时间,降低请求队列,提高数据传输的速率。

五.监控硬盘的工具和指标

Iostat–x –k 1

-x : 显示更多的消息 
-k : 数据以KB为单位 
1 : 每秒显示一次输出显示的信息

Iowait:cpu等待未完成的IO请求而空闲的时间的比例。

Idle:cpu空闲且无IO请求的比例。

rrqm/s:每秒这个设备相关的读取请求有多少被Merge了。

wrqm/s:每秒这个设备相关的写入请求有多少被Merge了。

rsec/s:每秒读取的扇区数;

wsec/:每秒写入的扇区数。

r/s:每秒完成的读 I/O 设备次数。即 delta(rio)/s

w/s:每秒完成的写 I/O 设备次数。即 delta(wio)/s

await:每一个IO请求的处理的平均时间(单位是毫秒)。包括加入请求队列和服务的时间。

svctm:平均每次设备I/O操作的服务时间。

avgrq-sz: 平均每次设备I/O操作的数据大小 (扇区)。即 delta(rsect+wsect)/delta(rio+wio)

avgqu-sz: 平均I/O队列长度。即 delta(aveq)/s/1000 (因为aveq的单位为毫秒)。

%util:在统计时间内所有处理IO时间,除以总共统计时间。
例如,如果统计间隔1秒,该设备有0.8秒在处理IO,而0.2秒闲置,那么该设备的%util = 0.8/1 = 80%,所以该参数暗示了设备的繁忙程度。
一般地,如果该参数是100%表示设备已经接近满负荷运行了(当然如果是多磁盘,即使%util是100%,因为磁盘的并发能力,所以磁盘使用未必就到了瓶颈)。

下面我们做一个实验来分析一下

我们使用命令

time dd if=/dev/zero of=/home/zhouyuan/mytest bs=1M count=3000

向mytest写入数据,写入3G。

截取部分的状态监控:

如图2,当两条数据 iowait 达到了 99% 以上,写入的数据是0,这是因为DMA将内存的中的数据传输给设备。结合图1的前两条数据,利用率达到了99%+却没有写入的磁盘块。

如图3,iowait下降,说明cpu开始执行相关程序,而此时块设备开始写入的数据。这两个操作是异步进行的。

Vmstat–k –n 1

Swap

    si: 从磁盘交换到内存的交换页数量,单位:KB/秒

    so: 从内存交换到磁盘的交换页数量,单位:KB/秒

IO

    bi: 从块设备接受的块数,单位:块/秒

    bo: 发送到块设备的块数,单位:块/秒

从图中我们可以看出系统的延迟写。

Tags: 服务端技术