如果你想知道SSD为什么使用多队列,可以看看这篇文章:https://kernel.dk/blk-mq.pdf

1. 多块层

以下关于多队列层的总结来自 The Multi-Queue Interface ArticleLinux kernel git 展示了如何转换为blk-mq。

blk_mq 的API实现了两级块层设计,该设计使用两组独立的请求队列。

  1. 软件暂存队列,按CPU分配;

  2. 硬件调度队列,其数量通常与blcok设备支持的实际硬件队列数量匹配。

如上图所示,软件暂存队列硬件调度队列之间的映射队列数不同。

现在,我们考虑两个队列放置不同的情况。

假设这里有3种情况:

  • 软件暂存队列 > 硬件调度队列

在这种情况下,两个或多个软件暂存队列被分配到一个硬件上下文中。而在硬件上下文将从所有关联的软件队列中拉入请求的同时,进行一次调度。

  • 软件暂存队列 < 硬件调度队列

在这种情况下,软件暂存队列硬件调度队列之间的映射是有顺序的。

  • 软件暂存队列 == 硬件调度队列

在这种情况下,这是最简单的情况,即执行直接1:1映射。

2. 多队列块层中的主要数据结构

2.1 基本结构

2.2 blk_mq_reg(在内核4.5中)

根据 The Multi-Queue Interface Article,blk_mq_reg 结构包含了一个新的块设备向块层注册时需要的所有重要信息。

这个数据结构包括指向 blk_mq_ops 数据结构的指针,用于跟踪多队列块层与设备驱动交互的具体例程。

blk_mq_reg 结构还保存了需要初始化的硬件队列数量等。

但是,blk_mq_reg 已经不复存在。我们需要通过 blk_mq_ops 来了解块层和块设备之间的操作

因此你可以在 kernel 3.15 中找到以下数据结构:

struct blk_mq_reg {
struct blk_mq_ops *ops;
unsigned int nr_hw_queues;
unsigned int queue_depth;
unsigned int reserved_tags;
unsigned int cmd_size; /* per-request extra data */
int numa_node;
unsigned int timeout;
unsigned int flags; /* BLK_MQ_F_* */
};

但是,在内核4.5中,你已经无法找到。

我认为在内核4.5中,此数据结构已更改为 struct blk_mq_tag_set *set

struct blk_mq_tag_set {
struct blk_mq_ops *ops;
unsigned int nr_hw_queues;
unsigned int queue_depth; /* max hw supported */
unsigned int reserved_tags;
unsigned int cmd_size; /* per-request extra data */
int numa_node;
unsigned int timeout;
unsigned int flags; /* BLK_MQ_F_* */
void *driver_data; struct blk_mq_tags **tags; struct mutex tag_list_lock;
struct list_head tag_list;
};

因为内核3.15中的函数 function(struct request_queue * blk_mq_init_queue(struct blk_mq_reg * reg,void * driver_data)) ,在内核4.5中已经更改为 function(struct request_queue * blk_mq_init_queue(struct blk_mq_tag_set * set))

2.3 blk_mq_ops 结构(在内核4.5中)

如上文中所述,此数据结构用于多队列块层与块设备层进行通信。

在此数据结构中,执行 blk_mq_hw_ctxblk_mq_ctx 之间上下文映射的函数存储在 map_queue 字段中。

struct blk_mq_ops {
/*
* Queue request
*/
queue_rq_fn *queue_rq; // this part /*
* Map to specific hardware queue
*/
map_queue_fn *map_queue; // this part /*
* Called on request timeout
*/
timeout_fn *timeout; /*
* Called to poll for completion of a specific tag.
*/
poll_fn *poll; softirq_done_fn *complete; /*
* Called when the block layer side of a hardware queue has been
* set up, allowing the driver to allocate/init matching structures.
* Ditto for exit/teardown.
*/
init_hctx_fn *init_hctx;
exit_hctx_fn *exit_hctx; /*
* Called for every command allocated by the block layer to allow
* the driver to set up driver specific data.
*
* Tag greater than or equal to queue_depth is for setting up
* flush request.
*
* Ditto for exit/teardown.
*/
init_request_fn *init_request;
exit_request_fn *exit_request;
};

2.4 blk_mq_hw_ctx 结构(在内核4.5中)

blk_mq_hw_ctx 结构表示与 request_queue 关联的硬件上下文。

这个对应的结构是内核4.5中的 blk_mq_ctx 结构。

struct blk_mq_hw_ctx {
struct {
spinlock_t lock;
struct list_head dispatch;
} ____cacheline_aligned_in_smp; unsigned long state; /* BLK_MQ_S_* flags */
struct delayed_work run_work;
struct delayed_work delay_work;
cpumask_var_t cpumask;
int next_cpu;
int next_cpu_batch; unsigned long flags; /* BLK_MQ_F_* flags */ struct request_queue *queue;
struct blk_flush_queue *fq; void *driver_data; struct blk_mq_ctxmap ctx_map; unsigned int nr_ctx;
struct blk_mq_ctx **ctxs; atomic_t wait_index; struct blk_mq_tags *tags; unsigned long queued;
unsigned long run;
#define BLK_MQ_MAX_DISPATCH_ORDER 10
unsigned long dispatched[BLK_MQ_MAX_DISPATCH_ORDER]; unsigned int numa_node;
unsigned int queue_num; atomic_t nr_active; struct blk_mq_cpu_notifier cpu_notifier;
struct kobject kobj; unsigned long poll_invoked;
unsigned long poll_success;
};

2.5 blk_mq_ctx 结构(在内核4.5中)

如上文所述,blk_mq_ctx 作为软件暂存队列已分配给每个CPU。

struct blk_mq_ctx {
struct {
spinlock_t lock;
struct list_head rq_list;
} ____cacheline_aligned_in_smp; unsigned int cpu;
unsigned int index_hw; unsigned int last_tag ____cacheline_aligned_in_smp; /* incremented at dispatch time */
unsigned long rq_dispatched[2];
unsigned long rq_merged; /* incremented at completion time */
unsigned long ____cacheline_aligned_in_smp rq_completed[2]; struct request_queue *queue;
struct kobject kobj;
} ____cacheline_aligned_in_smp;

2.6 request_queue 结构(在内核4.5中)

blk_mq_hw_ctxblk_mq_ctx 之间的上下文映射是建立在blk_mq_ops 结构的 map_queue 字段上的。在内核4.5中,这个映射仍然是 mq_map 字段,在与块设备相关的 request_queue 数据结构中。

struct request_queue {
/*
* Together with queue_head for cacheline sharing
*/
struct list_head queue_head;
struct request *last_merge;
struct elevator_queue *elevator;
int nr_rqs[2]; /* # allocated [a]sync rqs */
int nr_rqs_elvpriv; /* # allocated rqs w/ elvpriv */ /*
* If blkcg is not used, @q->root_rl serves all requests. If blkcg
* is used, root blkg allocates from @q->root_rl and all other
* blkgs from their own blkg->rl. Which one to use should be
* determined using bio_request_list().
*/
struct request_list root_rl; request_fn_proc *request_fn;
make_request_fn *make_request_fn;
prep_rq_fn *prep_rq_fn;
unprep_rq_fn *unprep_rq_fn;
softirq_done_fn *softirq_done_fn;
rq_timed_out_fn *rq_timed_out_fn;
dma_drain_needed_fn *dma_drain_needed;
lld_busy_fn *lld_busy_fn; struct blk_mq_ops *mq_ops; unsigned int *mq_map; /* sw queues */
struct blk_mq_ctx __percpu *queue_ctx;
unsigned int nr_queues; /* hw dispatch queues */
struct blk_mq_hw_ctx **queue_hw_ctx;
unsigned int nr_hw_queues; /*
* Dispatch queue sorting
*/
sector_t end_sector;
struct request *boundary_rq; /*
* Delayed queue handling
*/
struct delayed_work delay_work; struct backing_dev_info backing_dev_info; /*
* The queue owner gets to use this for whatever they like.
* ll_rw_blk doesn't touch it.
*/
void *queuedata; /*
* various queue flags, see QUEUE_* below
*/
unsigned long queue_flags; /*
* ida allocated id for this queue. Used to index queues from
* ioctx.
*/
int id; /*
* queue needs bounce pages for pages above this limit
*/
gfp_t bounce_gfp; /*
* protects queue structures from reentrancy. ->__queue_lock should
* _never_ be used directly, it is queue private. always use
* ->queue_lock.
*/
spinlock_t __queue_lock;
spinlock_t *queue_lock; /*
* queue kobject
*/
struct kobject kobj; /*
* mq queue kobject
*/
struct kobject mq_kobj; #ifdef CONFIG_BLK_DEV_INTEGRITY
struct blk_integrity integrity;
#endif /* CONFIG_BLK_DEV_INTEGRITY */ #ifdef CONFIG_PM
struct device *dev;
int rpm_status;
unsigned int nr_pending;
#endif /*
* queue settings
*/
unsigned long nr_requests; /* Max # of requests */
unsigned int nr_congestion_on;
unsigned int nr_congestion_off;
unsigned int nr_batching; unsigned int dma_drain_size;
void *dma_drain_buffer;
unsigned int dma_pad_mask;
unsigned int dma_alignment; struct blk_queue_tag *queue_tags;
struct list_head tag_busy_list; unsigned int nr_sorted;
unsigned int in_flight[2];
/*
* Number of active block driver functions for which blk_drain_queue()
* must wait. Must be incremented around functions that unlock the
* queue_lock internally, e.g. scsi_request_fn().
*/
unsigned int request_fn_active; unsigned int rq_timeout;
struct timer_list timeout;
struct work_struct timeout_work;
struct list_head timeout_list; struct list_head icq_list;
#ifdef CONFIG_BLK_CGROUP
DECLARE_BITMAP (blkcg_pols, BLKCG_MAX_POLS);
struct blkcg_gq *root_blkg;
struct list_head blkg_list;
#endif struct queue_limits limits; /*
* sg stuff
*/
unsigned int sg_timeout;
unsigned int sg_reserved_size;
int node;
#ifdef CONFIG_BLK_DEV_IO_TRACE
struct blk_trace *blk_trace;
#endif
/*
* for flush operations
*/
unsigned int flush_flags;
unsigned int flush_not_queueable:1;
struct blk_flush_queue *fq; struct list_head requeue_list;
spinlock_t requeue_lock;
struct work_struct requeue_work; struct mutex sysfs_lock; int bypass_depth;
atomic_t mq_freeze_depth; #if defined(CONFIG_BLK_DEV_BSG)
bsg_job_fn *bsg_job_fn;
int bsg_job_size;
struct bsg_class_device bsg_dev;
#endif #ifdef CONFIG_BLK_DEV_THROTTLING
/* Throttle data */
struct throtl_data *td;
#endif
struct rcu_head rcu_head;
wait_queue_head_t mq_freeze_wq;
struct percpu_ref q_usage_counter;
struct list_head all_q_node; struct blk_mq_tag_set *tag_set;
struct list_head tag_set_list;
struct bio_set *bio_split; bool mq_sysfs_init_done;
};

3. 队列初始化

当一个新的使用多队列 API 的设备驱动被加载时,它会创建并初始化一个新的 blk_mq_ops 结构,并将一个新的 blk_mq_reg 的相关指针设置为它的地址。

更详细的说,除了下面的结构,其他的操作现在都是严格要求的。

但是,为了在上下文分配或 I/O 请求完成时执行特定的操作,可以指定其他操作。

作为必要的数据,驱动程序必须初始化它所支持的提交队列的数量,以及它们的大小。

其他数据也是必须的,以确定驱动所支持的命令大小,以及必须暴露给块层的特定标志。

但是,在内核4.5版本中,struct blk_mq_tag_set 很重要,上面的工作就是在这个 struct blk_mq_tag_set 中实现的。

3.1 queue_fn

必须将其设置为负责处理命令的功能,例如,通过将命令传递给低级驱动程序。

3.2 map_queue

执行硬件和软件上下文之间的映射。

3.3 blk_mq_init_queue 函数(在内核4.5中)

在为设备相关的 gendisk 和 request_queue 做好准备后,驱动调用 blk_mq_init_queue 函数。(在内核4.5中)

这个函数初始化硬件和软件上下文,并执行它们之间的映射。

这个初始化例程还设置了一个备用的 make_request 函数,代替了传统的请求提交路径,其中包括函数 blk_make_request() (在内核4.5中)的多队列提交路径(其中包括 blk_mq_make_request() 函数)。

换句话说,备用的 make_request 函数是用 blk_queue_make_reqeust() 设置的。

4. 提交请求

设备初始化用 blk_mq_make_request() 代替了传统的块 I/O 提交函数(在内核4.5中),让多队列结构从上层的角度来使用。

多队列块层使用的 make_request (在内核4.5中不存在) 函数包含了从进程阻塞中获益的可能性,但只适用于支持单个硬件队列或异步请求的驱动。

如果请求是同步的,并且驱动主动使用多队列接口,则不会阻塞。

如果允许阻塞,make_request 函数也会执行请求合并,先在任务的阻塞列表里面搜索一个候选者。

最后在映射到当前 CPU 的软件队列中,提交路径不涉及任何 I/O 调度相关的回调。

最后,make_request 会立即将任何同步请求发送到相关硬件队列中,而在 async 或 flush 请求的情况下,它会延迟这个过渡,以便后续的合并和更高效的调度。

5. 请求调度

如果一个 I/O 请求是同步的(因此不允许在多队列块层中阻塞),它对设备驱动程序的调度是在同一请求的上下文中进行的。

如果请求是 async 或 flush,则存在任务阻塞。调度的顺序如下:

  1. 在提交另一个I/O请求给与同一硬件队列相关联的软件队列时。
  2. 在reqeust提交过程中安排的延迟工作被执行时。

多队列块层主要的 排队等候 函数是 blk_mq_run_hw_queue() (在内核4.5代码中),它基本依赖于另一个由其 blk_mq_ops 结构的 queue_rq (在内核4.5中)字段指向的驱动专用例程。

重要:我们必须检查 blk_mq_run_hw_queuerequest_qu 之间的关系!

重要:这个函数可以延迟队列的任何运行,同时它可以立即向驱动发送一个同步请求。

内部函数 __blk_mq_run_hw_queue() (在内核4.5中),在 reqeust 是同步的情况下被 blk_mq_run_hw_queue() (在内核4.5代码中)调用,首先加入与当前服务的硬件队列相关联的任何软件队列,然后它将结果列表与已经在调度列表中的任何条目加入。

在收集了所有待服务的条目之后,函数 __blk_mq_run_hw_queue() (在内核4.5中)处理它们(这些条目),启动每个 reqeust,并通过它的 queue_rq 函数将其传递给驱动。

该函数最后通过重排或删除相关请求处理可能出现的错误。

原文:https://hyunyoung2.github.io/2016/09/14/Multi_Queue/

Linux内核中块层上的多队列的更多相关文章

  1. Linux内核中Makefile、Kconfig和.config的关系(转)

    我们在编译Linux内核时,往往在Linux内核的顶层目录会执行一些命令,这里我以RK3288举例,比如:make firefly-rk3288-linux_defconfig.make menuco ...

  2. [转] Linux 内核中的 Device Mapper 机制

    本文结合具体代码对 Linux 内核中的 device mapper 映射机制进行了介绍.Device mapper 是 Linux 2.6 内核中提供的一种从逻辑设备到物理设备的映射框架机制,在该机 ...

  3. Linux 内核中的 Device Mapper 机制

    本文结合具体代码对 Linux 内核中的 device mapper 映射机制进行了介绍.Device mapper 是 Linux 2.6 内核中提供的一种从逻辑设备到物理设备的映射框架机制,在该机 ...

  4. KSM剖析——Linux 内核中的内存去耦合

    简介: 作为一个系统管理程序(hypervisor),Linux® 有几个创新,2.6.32 内核中一个有趣的变化是 KSM(Kernel Samepage Merging)  允许这个系统管理程序通 ...

  5. Linux内核中流量控制

    linux内核中提供了流量控制的相关处理功能,相关代码在net/sched目录下:而应用层上的控制是通过iproute2软件包中的tc来实现, tc和sched的关系就好象iptables和netfi ...

  6. Linux内核中SPI/I2c子系统剖析

    Linux内核中,SPI和I2C两个子系统的软件架构是一致的,且Linux内核的驱动模型都以bus,driver,device三种抽象对象为基本元素构建起来.下文的分析将主要用这三种抽象对象的创建过程 ...

  7. Linux内核中SPI总线驱动分析

    本文主要有两个大的模块:一个是SPI总线驱动的分析 (研究了具体实现的过程): 另一个是SPI总线驱动的编写(不用研究具体的实现过程). 1 SPI概述 SPI是英语Serial Peripheral ...

  8. [翻译] Linux 内核中的位数组和位操作

    目录 Linux 内核里的数据结构 原文链接与说明 Linux 内核中的位数组和位操作 位数组声明 体系结构特定的位操作 通用位操作 链接 Linux 内核里的数据结构 原文链接与说明 https:/ ...

  9. Linux内核中常见内存分配函数【转】

    转自:http://blog.csdn.net/wzhwho/article/details/4996510 1.      原理说明 Linux内核中采用了一种同时适用于32位和64位系统的内存分页 ...

  10. linux内核中socket的创建过程源码分析(总结性质)

    在漫长地分析完socket的创建源码后,发现一片浆糊,所以特此总结,我的博客中同时有另外一篇详细的源码分析,内核版本为3.9,建议在阅读本文后若还有兴趣再去看另外一篇博文.绝对不要单独看另外一篇. 一 ...

随机推荐

  1. 在SOUI4中工作线程如果与UI线程交互

    在SOUI4中工作线程如果与UI线程交互 很多时候程序的耗时过程需要在工作线程执行,执行过程中可能需要通过UI线程来展示运行状态及结果,这就涉及到工作线程与UI线程交互的问题. SOUI的UI框架本身 ...

  2. 如何在WPS和Word/Excel中直接使用DeepSeek功能

    以下是将DeepSeek功能集成到WPS中的详细步骤,无需本地部署模型,直接通过官网连接使用:1. 下载并安装OfficeAI插件 (1)访问OfficeAI插件下载地址:https://www.of ...

  3. 在 PyTorch 中理解词向量,将单词转换为有用的向量表示

    你要是想构建一个大型语言模型,首先得掌握词向量的概念.幸运的是,这个概念很简单,也是本系列文章的一个完美起点. 那么,假设你有一堆单词,它可以只是一个简单的字符串数组. animals = [&quo ...

  4. Codeforces 1110D Jongmah 题解 [ 蓝 ] [ 线性 dp ] [ 观察 ]

    Jongmah:小清新麻将 dp 题. 观察 首先观察这两个操作的性质,不难发现我们出掉的所有的顺子只要累计出了三次,这三次顺子就一定可以化作出三次相同的单牌. 而我们只需要最大化操作次数,显然这三次 ...

  5. NolanPro 详细部署教程

    自己想办法去弄授权,只提供部署教程 服务器搭建教程 1 诺兰群里找 @NolanNarkbot 点击start 2 群里发送 /check@NolanNarkbot 再找 @NolanNarkbot ...

  6. Kubernetes - [04] 常用命令

    kubectl 语法 kubectl [command] [TYPE] [NAME] [flags] command:指定在一个或多个资源商要执行的操作.例如:create.get.describe. ...

  7. Powershell实现圆缩小放大 (实时刷新窗口)

    使用Powershell,创建实时刷新的窗口,绘制图形,这里以圆作为例子,做缩小放大动画. [分析] Powershell是windows内置的自动部署平台,功能强大在于可以调取.net框架,因此,即 ...

  8. Arduino LED流水灯·基础实验

    Arduino初学IO控制,流水灯实验是很好的学习对象.分两个进程学习. 一.假流水灯,即基础效果实现 二.真流水灯,即采用PWM模拟真实流水渐变效果 我们设立5盏灯,正极分别连接数字口(Digita ...

  9. 大数据之路Week10_day05 (JavaAPI 操作Redis 第一阶段)

    刚开始学习JavaAPI的时候,主要是对redis中的字符串,字节位图,列表,集合,有序集合进行操作,并能够完成简单的需求. package com.wyh.redis; import org.jun ...

  10. 2024.11.12随笔&联考总结

    前言 心情不好,因为考试时 T2T3 全看错题了,导致 T2 没做出来,T3 一份没得.然后下午打球眼镜架子坏了,回机房才发现被高二的盒了. 但还是稍微写一下总结吧. 总结 感觉我今天做题状态还行,思 ...