zoukankan      html  css  js  c++  java
  • IO调度 | Linux块设备中的IO路径及调度策略

    当文件系统通过submit_bio提交IO之后,请求就进入了通用块层。通用块层会对IO进行一些预处理的动作,其目的是为了保证请求能够更加合理的发送到底层的磁盘设备,尽量保证性能最佳。这里面比较重要的就是IO调度模块。大家可能都听说过CFQ,除此之前还有DeadLine和Noop等,这些都是磁盘的调度算法。其中CFQ调度算法用的最多。

    如果忽略块设备的层叠结构和各种映射,简化的结构大概有3层,如图1所示。这里的3层并非都是软件,还包含硬件。通用块层就不用多说了,这里主要完成IO的合并和调度等操作。其下是驱动层,驱动层是硬件的驱动程序,用于将IO请求转换为对硬件寄存器的操作(注:不同的块设备又有差异,必然iSCSI设备是不会有寄存器操作的)。物理设备不同该驱动层的程序就不同,比如对于SAS直连的磁盘,该驱动层的程序就是SAS驱动,而如果是FC-HBA卡连接的FC-SAN,那么这个驱动层就是FC驱动(比如Qlogic的驱动)。

    Linux块设备中的IO路径及调度策略

    图1 块设备分层

    最下面一层是设备层,设备层通常是一个硬件设备。这里的硬件种类繁多,比如SAS卡、SATA卡、FC-HBA卡或者iSCSI-HBA卡等等。但有的时候又可能并不是硬件设备,比如对于iSCSI来说,该层可能是通过软件模拟的一个设备层,而其请求则是通过网卡发送到目标器端。

    主要数据结构及流程

    绝大多数程序都是由数据结构和算法2部分内容组成的,数据结构相当于程序的骨架,而算法则是程序的筋和肉。通过算法将数据结构关联起来,从而形成一个完整的整体。人类认识问题的规律是从具体到抽象,从简单到复杂,因此我们先从数据结构开始。理解了数据关键的数据结构,那我们就能更加容易的理解块设备IO的整个逻辑。

    在块设备IO中最为关键的数据结构是request_queue,也就是请求队列。该数据结构的简图如图2所示,这个数据结构本身非常复杂,我们这里进行了简化,只保留了部分关键的成员。如图彩色部分是2个函数指针,分别用于接收请求和处理请求。

    Linux块设备中的IO路径及调度策略

    图2 请求队列数据结构

    为了便于理解,我们这里举一个例子。以NBD块设备为例,在块设备初始化的时候make_request_fn被初始化为blk_queue_bio,request_fn被初始化为do_nbd_request。对于SCSI块设备而言,request_fn会被初始化为scsi_request_fn

    有了上面数据结构的知识及关键成员初始化的结果,接下来我们就可以分析一下块设备的整个流程的细节。块设备请求的入口是submit_bio,经过简单的检查后调用

    Linux块设备中的IO路径及调度策略

    由上述代码可以看出IO处理的入口函数其实是函数指针make_request_fn,而我们知道该指针实际上是函数blk_queue_bio。因此块设备的请求会由blk_queue_bio函数进行处理。

    磁盘调度策略

    Linux内核在设计磁盘的调度策略时提供了极大的灵活性。磁盘的调度策略以插件的注册到内核当中,也就是用户可以自由的选择磁盘的调度策略。

    调度算法的思想其实非常简单,主要是通过对IO的排序、合并和批量处理来优化磁盘寻道和请求的处理时间。这里值得说明的目前的调度算法其实更多的是针对机械磁盘,因为机械磁盘磁头定位耗时占整个IO处理时间的很大比例。当然对于SSD磁盘,调度算法也有一定的帮助,这就需要针对IO的特性具体来看了。

    Linux块设备中的IO路径及调度策略

    图3 调度策略结构体

    磁盘调度策略的结构体定义如图3所示,各个变量的含义也是比较明确,本文不再赘述。本文主要看一下 其中elevator_ops类型的变量ops,这个变量是调度策略具体的功能实现,任何调度算法都要实现其中某些函数。

    调度策略的实现就是通过这些回调函数完成的。为了理解调度策略的函数集具体做哪些事情,本文整理了一个表格,我们先从整体上看一下每个函数具体做了哪些事情。对于调度策略来说,这里的函数并非每个都要实现,下表中只有带*的才是必须要实现的函数。

    Linux块设备中的IO路径及调度策略

    简而言之,上述回调函数的功能就是判断请求是否可以被合并、执行合并和请求下发等等操作。上述回调函数比较多,而且使用场景也比较复杂,具体使用分散在调度器的很多流程中。因此,我们很难一下子介绍清楚所有的场景。为了更加直观的理解上述回调函数的作用,我们以Deadline调度策略为例进行简单的介绍。

    如图4是Deadline初始化的回调函数,从图中可以看出这里并没有初始化所有的回调函数,而只初始化了16个回调函数中的9个。

    Linux块设备中的IO路径及调度策略

    图4 Deadline回调函数

    我们具体分析一下函数的调用场景,前文我们介绍到elevator_merge_fn函数用于查询可以与bio合并的请求。如图5所示为整个调用栈,入口为blk_queue_bio,这个函数我们之前介绍过,它就是调度程序的入口。该函数调用elv_merge用于查找是否有可以合并的请求,并返回。而elv_merge函数调用的正式Deadline调度器提供的回调函数。完成判断后,该函数会根据实际情况返回请求(或者没有找到,不返回)和可合并的方向(例如向前合并,向后合并等),后续流程就是进行具体的合并操作了。

    Linux块设备中的IO路径及调度策略

    图5 函数调用栈

    由于IO调度涉及的流程比较多,限于本文篇幅,今天就先介绍到这里。后续我们再更加深入的介绍关于IO调度的其它内容。

  • 相关阅读:
    Codeforces 451A Game With Sticks
    POJ 3624 Charm Bracelet
    POJ 2127 Greatest Common Increasing Subsequence
    POJ 1458 Common Subsequence
    HDU 1087 Super Jumping! Jumping! Jumping!
    HDU 1698
    HDU 1754
    POJ 1724
    POJ 1201
    CSUOJ 1256
  • 原文地址:https://www.cnblogs.com/wyf0518/p/11461934.html
Copyright © 2011-2022 走看看