jiayuqicz 2020-05-05
现代计算机体系中,磁盘的速度和CPU的速度差距太大了,如果简单的将系统的IO请求按照请求的顺序进行顺序处理的话,系统的IO开销将导致系统的效率十分的低下,因此就需要将IO请求进行合理的安排,Linux系统在这一方面主要通过两种机制实现其一是各种层次的缓存,然后就是IO调度。IO调度的算法和磁盘的寻址原理无法分离,所以先简单看一下磁盘寻址原理,这个百度一下就可以找到很多这里就是简单说下。
常见的硬盘都是由许多个盘片组成重叠同心圆盘,每一个磁盘由磁盘、磁轴和读写头组成,要在硬盘上找到一个准确的地址需要磁柱(cylinder)、磁头(head)和扇区(sector)三个坐标这就是CHS寻址。而且现代的磁盘这部分的工作是由硬盘的固件完成的,硬盘固件接受逻辑块号由固件将逻辑块到chs进行一一映射,而操作系统访问采用块编号访问磁盘的特定地址,这就是逻辑块寻址(LBA),并且逻辑块的编号到硬盘CHS的映射往往也是顺序的。
所以起初的IO调度程序主要的任务就是减少磁头移动的距离,就如同一个快递员在城市中派送快递一样,他会安排一个合理的顺序去派件,同一个小区的快件会在一次派件时一起完成。反应在IO调度程序中就是合并和排序。合并就是将一个小区的快递从中转站全部集中来;排序就是对派送顺序的优化安排,就是尽量的减少派送总距离从而提高自己的派送效率。这个原理同电梯的调度相同,所以最早的IO调度算法本称为“电梯算法”。其原理就是保证磁头的线性移动,就像电梯会是 1、3、4、5、8、9或9、7、5、3、2、1 这样的顺序停止,虽然可能3楼的人后按了楼层按键。这个算法有个明显的问题就是如果不停的有新的物理顺序靠前的IO请求到来将导致靠后的请求被长时间的等待。然后就有了新的IO调度程序来解决以上问题以优化性能。
同电梯算法比,此机制保留了IO请求的排序后的IO请求队列,额外的引进了两个队列均按请求时间的前后排序,一个为读请求,一个为写请求。一个新的请求到来时会按顺序的先放到标准队列中合适的位置,而再根据操作类型不同放到新增读写队列其中一个的末尾,读和写队列有着不同的IO请求deadline时间限制,一般读为500ms,写为5秒。然后此时IO调度程序平时行为就如同前面的调度算法一样从标准队列中一个个的按顺序处理,不同的是当读或写队列中任何一个请求deadline时间到来时,就会转而服务这个到期的请求,从而解决电梯算法的“饿着”某些请求的问题。
此调度算法也是针对Deadline IO调度算法的一种优化,因为在Linux下读取操作是同步且同步化的,只有前一个读取请求返回了才可以送出下一个读请求,所以deadlineIO调度会有一个糟糕情况就是,当由一系列的物理相关的读请求出现时(从应用分析这种场景还比较常见)就会出现续读写头来回运动的情况,因为当第一个读请求送出并到期IO调度磁头需要先服务于这请求,然后回去继续普通的IO请求,此时与之前的读相邻的第二个读请求又来了,但是此时读写头已经运动回正常队列的请求地址了,然后等这个读请求到期就会发生和第一个读请求一样的情况了,所以如果正常队列的IO请求与当前请求的物理位置差的很远的话,这就是deadline算法的最坏情况,所以就有了Anticipatory IO调度算法,他的机制也很简单,在服务完一个到期的读时进行一个合适的时间等待,如果新提交的读请求来到就可以快速完成读,正如其名是一个预测性质的算法是概率学优化。
称为完全公平调度算法,它为每个进程维护了一个IO请求队列,IO调度程序会以轮询的方式服务每个进程队列的请求,直到时间片用完或者无IO请求,对于后者情况IO调度将等待一个合适的时间(10ms)再无请求到来时进行下一个队列的服务,并且它会使读取请求优先执行,因为进程地址空间对应到物理空间的局部性,这个算法在避免“饿着”某一个IO请求的前提下保证了整体性能。
这是一个针对非机械硬盘寻址机制出现的IO调度算法,比如固态硬盘,因为没有的机械动作的缘故,所以noop IO调度算法仅仅进行IO请求的合并而不进行排序,这种算法的效率简单且高效,但他依赖于物理设备。
IO调度算法的选择可以由内核启动参数传递设置,也可以通过/sys/block/device/queue/scheduler 获取当前IO调度算法和设置选择IO调度算法。除此之外还可以在用户空间优化IO的性能,这一部分就和应用的实现息息相关了,不过也常常就是三种方式,按路径,按inode,按物理块地址,因为常见文件系统会将同一路径的文件保存在物理地址相邻的地址中,Inode和物理块等都是一样的原理,就是将物理地址相邻的请求合并在一起交给内核,从而优化IO性能。