当前位置: 首页 > 科技观察

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

时间:2023-03-13 19:45:22 科技观察

当文件系统通过submit_bio提交IO时,请求进入通用块层。一般的块层都会对IO进行一些预处理动作,目的是为了保证请求能够更合理的发送到底层磁盘设备,尽量保证最佳的性能。这里比较重要的是IO调度模块。您可能听说过CFQ。之前还有DeadLine和Noop等,这些都是磁盘调度算法。其中CFQ调度算法用得最多。如果忽略堆栈结构和块设备的各种映射,简化后的结构大约有3层,如图1所示。这里的三层不全是软件,也包括硬件。通用的块层就不用多说了。这里主要完成IO合并、调度等操作。下面是驱动层,是一个硬件驱动,将IO请求转化为对硬件寄存器的操作(注意:不同的块设备有区别,iSCSI设备一定不能有寄存器操作)。驱动层的程序对于不同的物理设备是不同的。比如直接接SAS的磁盘,驱动层的程序就是SAS驱动,如果是FC-SAN接FC-HBA卡,那么驱动层就是FC驱动(比如Qlogic驱动).图1块设备层最底层是设备层,通常是硬件设备。这里有各种类型的硬件,比如SAS卡、SATA卡、FC-HBA卡或者iSCSI-HBA卡等等。但有时它可能不是硬件设备。例如对于iSCSI,这一层可能是软件模拟的设备层,它的请求通过网卡发送给目标端。主要数据结构和过程大多数程序由两部分组成:数据结构和算法。数据结构相当于程序的骨架,算法是程序的肌肉和血肉。算法将数据结构链接起来形成一个完整的整体。人类认识问题的规律是从具体到抽象,从简单到复杂,所以我们从数据结构入手。了解了数据的关键数据结构之后,我们就可以更容易地理解块设备IO的整个逻辑。块设备IO中最关键的数据结构是request_queue,也就是请求队列。数据结构的简化图如图2所示。数据结构本身非常复杂。我们这里简化了,只保留了一些关键成员。如图,有颜色的部分是两个函数指针,分别用来接收请求和处理请求。图2请求队列数据结构为了便于理解,这里举例说明。以NBD块设备为例,块设备初始化时,make_request_fn初始化为blk_queue_bio,request_fn初始化为do_nbd_request。对于SCSI块设备,request_fn将被初始化为scsi_request_fn。有了以上数据结构的知识和key成员初始化的结果,我们接下来就可以分析出块设备整个过程的细节了。块设备请求的入口是submit_bio。简单查看一下,调用上面的代码可以看到,IO处理的入口函数其实就是函数指针make_request_fn,我们知道这个指针其实就是函数blk_queue_bio。因此,块设备的请求会被blk_queue_bio函数处理。磁盘调度策略Linux内核在设计磁盘调度策略方面提供了极大的灵活性。磁盘调度策略作为插件注册在内核中,即用户可以自由选择磁盘调度策略。调度算法的思想其实很简单。主要通过排序、合并、批处理IO来优化diskseeks和requests的处理时间。这里值得说明的是,现在的调度算法其实更多的是针对机械盘的,因为机械盘的磁头定位占据了整个IO处理时间的很大比例。当然,对于SSD盘,调度算法也有帮助,这就需要具体看IO的特点了。图3调度策略结构磁盘调度策略的结构定义如图3所示,各个变量的含义也比较清楚,本文不再赘述。本文主要看elevator_ops类型的变量ops。该变量是调度策略的具体功能实现。任何调度算法都必须实现其中一些功能。调度策略的实现就是通过这些回调函数来完成的。为了理解调度策略的功能集具体是干什么的,本文整理了一张表格。让我们看一下每个功能作为一个整体的作用。对于调度策略,并不是这里的所有功能都必须实现。只有下表中带*的功能必须实现。简而言之,上述回调函数的作用就是判断请求是否可以合并,进行合并和请求投递等操作。上面提到的回调函数很多,使用场景也比较复杂。具体用法分散在调度器的很多进程中。因此,我们很难一下子介绍完所有的场景。为了更直观的理解上述回调函数的作用,我们以Deadline调度策略为例进行简单介绍。图4显示了由Deadline初始化的回调函数。从图中可以看出,这里并没有初始化所有的回调函数,而是16个回调函数中只有9个被初始化。图4Deadline回调函数下面详细分析一下该函数的调用场景。在上一篇文章中,我们介绍了elevator_merge_fn函数用于查询可以与bio合并的请求。如图5所示,展示了整个调用栈,入口为blk_queue_bio。这个函数我们之前介绍过,它是调度器的入口。该函数调用elv_merge判断是否有可以合并的请求,并返回。elv_merge函数调用的官方Deadline调度器提供的回调函数。判断完成后,函数会根据实际情况返回请求(或未找到,不返回)和可合并方向(如前向合并,后向合并等),后续处理具体执行合并操作。图5函数调用栈由于IO调度涉及的进程较多,本文篇幅有限,今天先介绍到这里。后面我们会更深入地介绍其他关于IO调度的内容。