如果你想知道SSD為什么使用多隊列,可以看看這篇文章:https://kernel.dk/blk-mq.pdf
1. 多塊層
以下關於多隊列層的總結來自 The Multi-Queue Interface Article,Linux kernel git 展示了如何轉換為blk-mq。
blk_mq 的API實現了兩級塊層設計,該設計使用兩組獨立的請求隊列。
-
軟件暫存隊列,按CPU分配;
-
硬件調度隊列,其數量通常與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_ctx 和 blk_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_ctx 和 blk_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,則存在任務阻塞。調度的順序如下:
- 在提交另一個I/O請求給與同一硬件隊列相關聯的軟件隊列時。
- 在reqeust提交過程中安排的延遲工作被執行時。
多隊列塊層主要的 排隊等候 函數是 blk_mq_run_hw_queue() (在內核4.5代碼中),它基本依賴於另一個由其 blk_mq_ops 結構的 queue_rq (在內核4.5中)字段指向的驅動專用例程。
重要:我們必須檢查 blk_mq_run_hw_queue 和 request_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 函數將其傳遞給驅動。
該函數最后通過重排或刪除相關請求處理可能出現的錯誤。
