多佇列塊 IO 排隊機制 (blk-mq)¶
多佇列塊 IO 排隊機制是一個 API,它使快速儲存裝置能夠透過排隊並將 IO 請求同時提交到塊裝置來實現巨大的每秒輸入/輸出運算元 (IOPS),從而受益於現代儲存裝置提供的並行性。
簡介¶
背景¶
從核心開發之初,磁碟驅動器一直是事實上的標準。塊 IO 子系統旨在為隨機訪問時具有高代價的那些裝置實現儘可能好的效能,並且瓶頸是機械移動部件,比儲存堆疊上的任何層都慢得多。這種最佳化技術的一個例子包括根據磁碟磁頭的當前位置對讀/寫請求進行排序。
然而,隨著固態驅動器和非易失性儲存器的發展,它們沒有機械部件,也沒有隨機訪問懲罰,並且能夠執行高並行訪問,堆疊的瓶頸已從儲存裝置轉移到作業系統。為了利用這些裝置設計中的並行性,引入了多佇列機制。
以前的設計有一個單佇列來儲存塊 IO 請求,只有一個鎖。由於快取中的髒資料以及多個處理器只有一個鎖的瓶頸,這在 SMP 系統中擴充套件性不好。當不同的程序(或同一程序,移動到不同的 CPU)想要執行塊 IO 時,此設定也會遇到擁塞。與此相反,blk-mq API 生成多個佇列,這些佇列具有 CPU 本地的各個入口點,從而無需鎖。有關其工作原理的更深入的解釋將在以下部分(操作)中介紹。
操作¶
當用戶空間對塊裝置執行 IO(例如,讀取或寫入檔案)時,blk-mq 會採取行動:它將儲存和管理對塊裝置的 IO 請求,充當使用者空間(以及檔案系統,如果存在)和塊裝置驅動程式之間的中介軟體。
blk-mq 有兩組佇列:軟體暫存佇列和硬體排程佇列。當請求到達塊層時,它將嘗試儘可能短的路徑:直接將其傳送到硬體佇列。但是,在兩種情況下可能無法這樣做:如果該層附加了 IO 排程程式,或者我們想要嘗試合併請求。在這兩種情況下,請求都將傳送到軟體佇列。
然後,在請求由軟體佇列處理後,它們將被放置在硬體佇列中,這是硬體可以直接訪問以處理這些請求的第二階段佇列。但是,如果硬體沒有足夠的資源來接受更多請求,則 blk-mq 會將請求放置在臨時佇列中,以便在硬體能夠處理時將來發送。
軟體暫存佇列¶
如果塊 IO 子系統未將請求直接傳送到驅動程式,則會將請求新增到軟體暫存佇列(由 struct blk_mq_ctx 表示)中。一個請求是一個或多個 BIO。它們透過資料結構 struct bio 到達塊層。然後,塊層將從中構建一個新結構,即 struct request,該結構將用於與裝置驅動程式進行通訊。每個佇列都有自己的鎖,佇列的數量由每個 CPU 或每個節點定義。
暫存佇列可用於合併相鄰扇區的請求。例如,扇區 3-6、6-7、7-9 的請求可以變成 3-9 的一個請求。即使隨機訪問 SSD 和 NVM 與順序訪問相比具有相同的響應時間,但順序訪問的組合請求也會減少單個請求的數量。這種合併請求的技術稱為外掛。
除此之外,還可以透過 IO 排程程式重新排序請求,以確保系統資源的公平性(例如,確保沒有應用程式遭受飢餓)和/或提高 IO 效能。
IO 排程程式¶
塊層實現了多個排程程式,每個排程程式都遵循一種啟發式方法來提高 IO 效能。它們是“可插拔的”(如即插即用),這意味著可以使用 sysfs 在執行時選擇它們。您可以在此處閱讀有關 Linux 的 IO 排程程式的更多資訊。排程僅發生在同一佇列中的請求之間,因此不可能合併來自不同佇列的請求,否則會導致快取垃圾回收,並且需要為每個佇列設定一個鎖。排程後,請求有資格傳送到硬體。可以選擇的可能排程程式之一是 NONE 排程程式,這是最直接的排程程式。它只會將請求放置在程序執行的任何軟體佇列中,而不會進行任何重新排序。當裝置開始處理硬體佇列中的請求(也稱為執行硬體佇列)時,對映到該硬體佇列的軟體佇列將根據其對映依次耗盡。
硬體排程佇列¶
硬體佇列(由 struct blk_mq_hw_ctx 表示)是裝置驅動程式用來對映裝置提交佇列(或裝置 DMA 環形緩衝區)的結構,並且是低階裝置驅動程式獲取請求的所有權之前的塊層提交程式碼的最後一步。要執行此佇列,塊層會從關聯的軟體佇列中刪除請求,並嘗試排程到硬體。
如果無法將請求直接傳送到硬體,它們將被新增到請求的連結列表 (hctx->dispatch) 中。然後,下次塊層執行佇列時,它將首先發送位於 dispatch 列表中的請求,以確保與那些已準備好首先發送的請求公平排程。硬體佇列的數量取決於硬體及其裝置驅動程式支援的硬體上下文的數量,但不會超過系統核心的數量。在此階段沒有重新排序,並且每個軟體佇列都有一組硬體佇列來發送請求。
注意
塊層和裝置協議都不保證請求的完成順序。這必須由更高層處理,例如檔案系統。
基於標記的完成¶
為了指示哪個請求已完成,每個請求都由一個整數標識,範圍從 0 到排程佇列大小。此標記由塊層生成,稍後由裝置驅動程式重用,從而無需建立冗餘識別符號。當請求在驅動程式中完成時,標記會發送回塊層以通知其最終完成。這消除了執行線性搜尋以找出已完成的 IO 的需要。
進一步閱讀¶
原始碼文件¶
-
enum blk_eh_timer_return¶
超時處理程式應如何進行
常量
BLK_EH_DONE塊驅動程式已完成命令或將在稍後完成。
BLK_EH_RESET_TIMER重置請求計時器並繼續等待請求完成。
-
struct blk_mq_hw_ctx¶
面向硬體塊裝置的硬體佇列的狀態
定義:
struct blk_mq_hw_ctx {
struct {
spinlock_t lock;
struct list_head dispatch;
unsigned long state;
};
struct delayed_work run_work;
cpumask_var_t cpumask;
int next_cpu;
int next_cpu_batch;
unsigned long flags;
void *sched_data;
struct request_queue *queue;
struct blk_flush_queue *fq;
void *driver_data;
struct sbitmap ctx_map;
struct blk_mq_ctx *dispatch_from;
unsigned int dispatch_busy;
unsigned short type;
unsigned short nr_ctx;
struct blk_mq_ctx **ctxs;
spinlock_t dispatch_wait_lock;
wait_queue_entry_t dispatch_wait;
atomic_t wait_index;
struct blk_mq_tags *tags;
struct blk_mq_tags *sched_tags;
unsigned int numa_node;
unsigned int queue_num;
atomic_t nr_active;
struct hlist_node cpuhp_online;
struct hlist_node cpuhp_dead;
struct kobject kobj;
#ifdef CONFIG_BLK_DEBUG_FS;
struct dentry *debugfs_dir;
struct dentry *sched_debugfs_dir;
#endif;
struct list_head hctx_list;
};
成員
{未命名結構}匿名
鎖保護排程列表。
排程用於已準備好排程到硬體但由於某些原因(例如,缺乏資源)無法傳送到硬體的請求。一旦驅動程式可以傳送新請求,此列表中的請求將首先發送以進行更公平的排程。
狀態BLK_MQ_S_* 標誌。定義硬體佇列的狀態(活動、計劃重新啟動、已停止)。
run_work用於稍後排程硬體佇列執行。
cpumask此 hctx 可以執行的可用 CPU 的對映。
next_cpu由 blk_mq_hctx_next_cpu() 用於從 cpumask 中進行迴圈 CPU 選擇。
next_cpu_batch在更改為下一個 CPU 之前,批處理中剩餘的工作數計數器。
標誌BLK_MQ_F_* 標誌。定義佇列的行為。
sched_data指向附加到請求佇列的 IO 排程程式擁有的資料的指標。如何使用此指標取決於 IO 排程程式。
佇列指向擁有此硬體上下文的請求佇列的指標。
fq需要執行重新整理操作的請求佇列。
driver_data指向建立此 hctx 的塊驅動程式擁有的資料的指標。
ctx_map每個軟體佇列的點陣圖。如果位為開啟,則該軟體佇列中存在待處理請求。
dispatch_from當未選擇排程程式時要使用的軟體佇列。
dispatch_busyblk_mq_update_dispatch_busy() 使用的數字,用於決定硬體佇列是否正忙於使用指數加權移動平均演算法。
型別HCTX_TYPE_* 標誌。硬體佇列的型別。
nr_ctx軟體佇列的數量。
ctxs軟體佇列陣列。
dispatch_wait_lockdispatch_wait 佇列的鎖。
dispatch_wait當目前沒有可用的標記時,將請求放入其中的等待佇列,以便將來等待另一次嘗試。
wait_index下一個可用的 dispatch_wait 佇列的索引,用於插入請求。
標記塊驅動程式擁有的標記。只有在從硬體佇列排程請求時才分配此集中的標記。
sched_tagsI/O 排程程式擁有的標記。如果請求佇列有關聯的 I/O 排程程式,則在分配該請求時會分配一個標記。否則,不使用此成員。
numa_node儲存介面卡已連線到的 NUMA 節點。
queue_num此硬體佇列的索引。
nr_active活動請求的數量。僅當標記集在請求佇列之間共享時才使用。
cpuhp_online如果 CPU 即將死亡,則用於儲存請求的列表。
cpuhp_dead用於儲存某些 CPU 死亡時的請求的列表。
kobjsysfs 的核心物件。
debugfs_dir此硬體佇列的 debugfs 目錄。命名為 cpu<cpu_number>。
sched_debugfs_dir排程程式的 debugfs 目錄。
hctx_list如果此 hctx 未使用,則這是 q->unused_hctx_list 中的一個條目。
-
struct blk_mq_queue_map¶
將軟體佇列對映到硬體佇列
定義:
struct blk_mq_queue_map {
unsigned int *mq_map;
unsigned int nr_queues;
unsigned int queue_offset;
};
成員
mq_mapCPU ID 到硬體佇列索引對映。這是一個具有 nr_cpu_ids 個元素的陣列。每個元素的值都在 [queue_offset, queue_offset + nr_queues) 範圍內。
nr_queues要將 CPU ID 對映到的硬體佇列的數量。
queue_offset要對映到的第一個硬體佇列。由 PCIe NVMe 驅動程式用於將每個硬體佇列型別(
enum hctx_type)對映到一組不同的硬體佇列。
-
enum hctx_type¶
硬體佇列的型別
常量
HCTX_TYPE_DEFAULT所有未另行說明的 I/O。
HCTX_TYPE_READ僅用於讀取 I/O。
HCTX_TYPE_POLL任何型別的輪詢 I/O。
HCTX_MAX_TYPEShctx 的型別數。
-
struct blk_mq_tag_set¶
可以在請求佇列之間共享的標記集
定義:
struct blk_mq_tag_set {
const struct blk_mq_ops *ops;
struct blk_mq_queue_map map[HCTX_MAX_TYPES];
unsigned int nr_maps;
unsigned int nr_hw_queues;
unsigned int queue_depth;
unsigned int reserved_tags;
unsigned int cmd_size;
int numa_node;
unsigned int timeout;
unsigned int flags;
void *driver_data;
struct blk_mq_tags **tags;
struct blk_mq_tags *shared_tags;
struct mutex tag_list_lock;
struct list_head tag_list;
struct srcu_struct *srcu;
struct rw_semaphore update_nr_hwq_lock;
};
成員
操作指向實現塊驅動程式行為的函式的指標。
對映一個或多個 ctx -> hctx 對映。每個硬體佇列型別(
enum hctx_type)都存在一個對映,驅動程式希望支援該型別。對對映的大小沒有限制,並且在型別之間共享對映是完全合法的。nr_mapsmap 陣列中的元素數。範圍為 [1, HCTX_MAX_TYPES] 的數字。
nr_hw_queues此資料結構所屬的塊驅動程式支援的硬體佇列的數量。
queue_depth每個硬體佇列的標記數,包括保留標記。
reserved_tags為 BLK_MQ_REQ_RESERVED 標記分配保留的標記數。
cmd_size每個請求要分配的額外位元組數。塊驅動程式擁有這些額外位元組。
numa_node儲存介面卡已連線到的 NUMA 節點。
超時請求處理超時(以節拍為單位)。
標誌零個或多個 BLK_MQ_F_* 標誌。
driver_data指向建立此標記集的塊驅動程式擁有的資料的指標。
標記標記集。每個硬體佇列一個標記集。具有 nr_hw_queues 個元素。
shared_tags共享的標記集。具有 nr_hw_queues 個元素。如果設定,則由所有 tags 共享。
tag_list_lock序列化 tag_list 訪問。
tag_list使用此標記集的請求佇列的列表。另請參見 request_queue.tag_set_list。
srcu當請求佇列的型別為阻塞 (BLK_MQ_F_BLOCKING) 時用作鎖。
update_nr_hwq_lock同步更新 nr_hw_queues 與新增/刪除磁碟和切換電梯。
-
struct blk_mq_queue_data¶
有關插入佇列的請求的資料
定義:
struct blk_mq_queue_data {
struct request *rq;
bool last;
};
成員
rq請求指標。
last如果它是佇列中的最後一個請求。
-
struct blk_mq_ops¶
實現塊驅動程式行為的回撥函式。
定義:
struct blk_mq_ops {
blk_status_t (*queue_rq)(struct blk_mq_hw_ctx *, const struct blk_mq_queue_data *);
void (*commit_rqs)(struct blk_mq_hw_ctx *);
void (*queue_rqs)(struct rq_list *rqlist);
int (*get_budget)(struct request_queue *);
void (*put_budget)(struct request_queue *, int);
void (*set_rq_budget_token)(struct request *, int);
int (*get_rq_budget_token)(struct request *);
enum blk_eh_timer_return (*timeout)(struct request *);
int (*poll)(struct blk_mq_hw_ctx *, struct io_comp_batch *);
void (*complete)(struct request *);
int (*init_hctx)(struct blk_mq_hw_ctx *, void *, unsigned int);
void (*exit_hctx)(struct blk_mq_hw_ctx *, unsigned int);
int (*init_request)(struct blk_mq_tag_set *set, struct request *, unsigned int, unsigned int);
void (*exit_request)(struct blk_mq_tag_set *set, struct request *, unsigned int);
void (*cleanup_rq)(struct request *);
bool (*busy)(struct request_queue *);
void (*map_queues)(struct blk_mq_tag_set *set);
#ifdef CONFIG_BLK_DEBUG_FS;
void (*show_rq)(struct seq_file *m, struct request *rq);
#endif;
};
成員
queue_rq從塊 IO 佇列新請求。
commit_rqs如果驅動程式使用 bd->last 來判斷何時將請求提交到硬體,則必須定義此函式。如果出現錯誤導致我們停止發出進一步的請求,則此掛鉤用於啟動硬體(否則最後一個請求將這樣做)。
queue_rqs佇列新請求列表。驅動程式保證每個請求都屬於同一佇列。如果驅動程式沒有完全清空 rqlist,那麼其餘的將由塊層在返回時單獨排隊。
get_budget在佇列請求之前保留預算,一旦執行 .queue_rq,驅動程式有責任釋放保留的預算。此外,我們必須處理 .get_budget 的故障情況,以避免 I/O 死鎖。
put_budget釋放保留的預算。
set_rq_budget_token儲存 rq 的預算令牌
get_rq_budget_token檢索 rq 的預算令牌
超時在請求超時時呼叫。
輪詢呼叫以輪詢特定標記的完成。
complete將請求標記為完成。
init_hctx在硬體佇列的塊層端設定完畢時呼叫,允許驅動程式分配/初始化匹配結構。
exit_hctxDitto 用於退出/拆卸。
init_request為塊層分配的每個命令呼叫,允許驅動程式設定驅動程式特定資料。
大於或等於 queue_depth 的標記用於設定重新整理請求。
exit_requestDitto 用於退出/拆卸。
cleanup_rq在釋放尚未完成的一個請求之前呼叫,通常用於釋放驅動程式私有資料。
忙如果設定,則返回此隊列當前是否忙。
map_queues這允許驅動程式透過覆蓋構建 mq_map 的設定時函式來指定自己的佇列對映。
show_rq由 debugfs 實現用於顯示有關請求的驅動程式特定資訊。
-
enum mq_rq_state blk_mq_rq_state(struct request *rq)¶
讀取請求的當前 MQ_RQ_* 狀態
引數
struct request *rq目標請求。
-
bool blk_mq_add_to_batch(struct request *req, struct io_comp_batch *iob, bool is_error, void (*complete)(struct io_comp_batch*))¶
將請求新增到完成批處理
引數
struct request *req要新增到批處理的請求
struct io_comp_batch *iob要新增請求的批處理
bool is_error如果請求失敗並出現錯誤,則指定 true
void (*complete)(struct io_comp_batch *)請求的 completaion 處理程式
描述
批處理完成僅在沒有 I/O 錯誤且沒有特殊的 ->end_io 處理程式時有效。
返回
當請求已新增到批處理時為 true,否則為 false
-
struct request *blk_mq_rq_from_pdu(void *pdu)¶
將 PDU 強制轉換為請求
引數
void *pdu要強制轉換的 PDU(協議資料單元)
返回
請求
描述
驅動程式命令資料緊接在請求之後。因此,減去請求大小以返回到原始請求。
-
void *blk_mq_rq_to_pdu(struct request *rq)¶
將請求強制轉換為 PDU
引數
struct request *rq要強制轉換的請求
返回
指向 PDU 的指標
描述
驅動程式命令資料緊接在請求之後。因此,新增請求以獲取 PDU。
-
void blk_mq_wait_quiesce_done(struct blk_mq_tag_set *set)¶
等待直到正在進行的靜默完成
引數
struct blk_mq_tag_set *set要等待的 tag_set
注意
驅動程式有責任確保已在 tag_set 的一個或多個 request_queue 上啟動靜默。此函式僅等待那些使用 blk_mq_quiesce_queue_nowait 設定了靜默標誌的 request_queue 上的靜默。
-
void blk_mq_quiesce_queue(struct request_queue *q)¶
等待直到所有正在進行的排程都已完成
引數
struct request_queue *q請求佇列。
注意
此函式不會阻止呼叫 struct request end_io() 回撥函式。一旦返回此函式,我們確保在透過 blk_mq_unquiesce_queue() 取消靜默佇列之前,不會發生任何排程。
-
bool blk_update_request(struct request *req, blk_status_t error, unsigned int nr_bytes)¶
在不完成請求的情況下完成多個位元組
引數
struct request *req正在處理的請求
blk_status_t error塊狀態程式碼
unsigned int nr_bytes要為 req 完成的位元組數
描述
結束附加到 req 的多個位元組的 I/O,但即使 req 沒有剩餘位元組,也不會完成請求結構。如果 req 有剩餘位元組,則將其設定為下一個段範圍。
將 blk_rq_bytes() 的結果作為 nr_bytes 傳遞保證此函式返回
false。
注意
除了此函式末尾的一致性檢查之外,故意忽略 RQF_SPECIAL_PAYLOAD 標誌。
返回
false- 此請求沒有更多資料true- 此請求有更多資料
-
void blk_mq_complete_request(struct request *rq)¶
結束請求上的 I/O
引數
struct request *rq正在處理的請求
描述
透過排程 ->complete_rq 操作來完成請求。
-
void blk_mq_start_request(struct request *rq)¶
開始處理請求
引數
struct request *rq指向要啟動的請求的指標
描述
裝置驅動程式使用的函式,用於通知塊層即將處理請求,因此 blk 層可以執行適當的初始化,例如啟動超時計時器。
-
void blk_execute_rq_nowait(struct request *rq, bool at_head)¶
插入一個請求到 I/O 排程器以供執行
引數
struct request *rq要插入的請求
bool at_head在佇列的頭部或尾部插入請求
描述
將一個完全準備好的請求插入到 I/O 排程器佇列的末尾以供執行。不要等待完成。
注意
如果佇列已死,此函式將直接呼叫 done。
-
blk_status_t blk_execute_rq(struct request *rq, bool at_head)¶
插入一個請求到佇列中以供執行
引數
struct request *rq要插入的請求
bool at_head在佇列的頭部或尾部插入請求
描述
將一個完全準備好的請求插入到 I/O 排程器佇列的末尾以供執行,並等待完成。
返回
提供給 blk_mq_end_request() 的 blk_status_t 結果。
-
void blk_mq_delay_run_hw_queue(struct blk_mq_hw_ctx *hctx, unsigned long msecs)¶
非同步執行硬體佇列。
引數
struct blk_mq_hw_ctx *hctx指向要執行的硬體佇列的指標。
unsigned long msecs執行佇列前要等待的延遲毫秒數。
描述
非同步執行硬體佇列,延遲 msecs 毫秒。
-
void blk_mq_run_hw_queue(struct blk_mq_hw_ctx *hctx, bool async)¶
開始執行硬體佇列。
引數
struct blk_mq_hw_ctx *hctx指向要執行的硬體佇列的指標。
bool async如果我們想要非同步執行佇列。
描述
檢查請求佇列是否不在靜止狀態,並且是否有待處理的請求要傳送。如果為真,則執行佇列以將請求傳送到硬體。
-
void blk_mq_run_hw_queues(struct request_queue *q, bool async)¶
執行請求佇列中的所有硬體佇列。
引數
struct request_queue *q指向要執行的請求佇列的指標。
bool async如果我們想要非同步執行佇列。
-
void blk_mq_delay_run_hw_queues(struct request_queue *q, unsigned long msecs)¶
非同步執行所有硬體佇列。
引數
struct request_queue *q指向要執行的請求佇列的指標。
unsigned long msecs執行佇列前要等待的延遲毫秒數。
-
void blk_mq_request_bypass_insert(struct request *rq, blk_insert_t flags)¶
在分派列表處插入請求。
引數
struct request *rq指向要插入的請求的指標。
blk_insert_t flagsBLK_MQ_INSERT_*
描述
應該小心使用,當呼叫者知道我們想要繞過目標裝置上的潛在 IO 排程器時。
-
void blk_mq_try_issue_directly(struct blk_mq_hw_ctx *hctx, struct request *rq)¶
嘗試直接將請求傳送到裝置驅動程式。
引數
struct blk_mq_hw_ctx *hctx關聯硬體佇列的指標。
struct request *rq指向要傳送的請求的指標。
描述
如果裝置有足夠的資源現在接受新請求,則直接將請求傳送到裝置驅動程式。否則,插入到 hctx->dispatch 佇列,以便我們可以在將來再次嘗試傳送它。插入此佇列的請求具有更高的優先順序。
引數
struct bio *bioBio 指標。
描述
從 q 和 bio 構建請求結構併發送到裝置。如果發生以下情況,請求可能不會直接排隊到硬體:* 此請求可以與另一個請求合併 * 我們想將請求放置在外掛佇列中以供將來可能合併 * 此佇列中有一個活動的 IO 排程器
如果 bio 出現錯誤,或者在請求建立時出現錯誤,則它不會對請求進行排隊。
-
blk_status_t blk_insert_cloned_request(struct request *rq)¶
用於堆疊驅動程式以提交請求的助手
引數
struct request *rq正在排隊的請求
-
void blk_rq_unprep_clone(struct request *rq)¶
用於釋放克隆請求中所有 bios 的輔助函式
引數
struct request *rq要清理的克隆請求
描述
釋放 rq 中克隆請求的所有 bios。
-
int blk_rq_prep_clone(struct request *rq, struct request *rq_src, struct bio_set *bs, gfp_t gfp_mask, int (*bio_ctr)(struct bio*, struct bio*, void*), void *data)¶
用於設定克隆請求的輔助函式
引數
struct request *rq要設定的請求
struct request *rq_src要克隆的原始請求
struct bio_set *bsbio_set,克隆的 bios 從中分配
gfp_t gfp_maskbio 的記憶體分配掩碼
int (*bio_ctr)(struct bio *, struct bio *, void *)為每個克隆 bio 呼叫的設定函式。成功返回
0,失敗返回非0。void *data要傳遞給 bio_ctr 的私有資料
描述
將 rq_src 中的 bios 克隆到 rq,並將 rq_src 的屬性複製到 rq。此外,原始 bios 指向的頁面不會被複制,克隆的 bios 只是指向相同的頁面。因此,克隆的 bios 必須在原始 bios 之前完成,這意味著呼叫者必須在 rq_src 之前完成 rq。
-
void blk_mq_destroy_queue(struct request_queue *q)¶
關閉請求佇列
引數
struct request_queue *q要關閉的請求佇列
描述
這將關閉 blk_mq_alloc_queue() 分配的請求佇列。所有未來的請求都將失敗,並返回 -ENODEV。呼叫者負責透過呼叫 blk_put_queue() 來刪除 blk_mq_alloc_queue() 的引用。
上下文
可以睡眠