Linux NVMe Driver学习笔记之6:Admin Queue与Blk-mq初始化
這篇文章緊接上回分解,在nvme_probe函數(shù)的最后一步調(diào)用nvme_reset_work進(jìn)行reset操作,nvme_reset_work的主要工作可以概括如下幾個(gè)步驟:
進(jìn)入nvme_reset_work函數(shù)后先檢查NVME_CTRL_RESETTING標(biāo)志,來(lái)確保nvme_reset_work不會(huì)被重復(fù)進(jìn)入。
調(diào)用nvme_pci_enable
調(diào)用nvme_configure_admin_queue
調(diào)用nvme_init_queue
調(diào)用nvme_alloc_admin_tags
調(diào)用nvme_init_identify
調(diào)用nvme_setup_io_queues
調(diào)用nvme_start_queues/nvme_dev_add之后,接著調(diào)用nvme_queue_scan
上篇文章中,我們解析了nvme_configure_admin_queue的內(nèi)容,本文我們接著介紹nvme_reset_work中的其他函數(shù)。
1. 先來(lái)看看nvme_init_queue:
static void?nvme_init_queue(struct nvme_queue *nvmeq, u16 qid)
{
struct nvme_dev *dev = nvmeq->dev;
spin_lock_irq(&nvmeq->q_lock);
nvmeq->sq_tail?= 0;
nvmeq->cq_head?= 0;
nvmeq->cq_phase?= 1;
nvmeq->q_db?= &dev->dbs[qid * 2 * dev->db_stride];
memset((void *)nvmeq->cqes, 0, CQ_SIZE(nvmeq->q_depth));
dev->online_queues++;
spin_unlock_irq(&nvmeq->q_lock);
}
nvme_init_queue做的事情比較簡(jiǎn)單,就是對(duì)之前nvme_configure_admin_queue函數(shù)中申請(qǐng)的queue進(jìn)行初始化操作。在這個(gè)過(guò)程中,對(duì)SQ Tail, CQ Head以及CQ phase變量進(jìn)行初始化賦值,然后通過(guò)q_db指向Doorbell寄存器。
有關(guān)SQ、CQ、Phase、Doorbell的詳細(xì)解釋請(qǐng)參考:
NVMe系列專題之二:隊(duì)列(Queue)管理
2.?看完nvme_init_queue, 我們?cè)俳又虺?/strong>nvme_alloc_admin_tags:?
static int?nvme_alloc_admin_tags(struct nvme_dev *dev)
{
if (!dev->ctrl.admin_q) {
dev->admin_tagset.ops = &nvme_mq_admin_ops;
dev->admin_tagset.nr_hw_queues = 1;
dev->admin_tagset.queue_depth = NVME_AQ_BLKMQ_DEPTH - 1;
dev->admin_tagset.timeout = ADMIN_TIMEOUT;
dev->admin_tagset.numa_node = dev_to_node(dev->dev);
dev->admin_tagset.cmd_size = nvme_cmd_size(dev);
dev->admin_tagset.driver_data = dev;
if (blk_mq_alloc_tag_set(&dev->admin_tagset))
return -ENOMEM;
dev->ctrl.admin_q =?blk_mq_init_queue(&dev->admin_tagset);
if (IS_ERR(dev->ctrl.admin_q)) {
blk_mq_free_tag_set(&dev->admin_tagset);
return -ENOMEM;
}
if (!blk_get_queue(dev->ctrl.admin_q)) {
nvme_dev_remove_admin(dev);
dev->ctrl.admin_q = NULL;
return -ENODEV;
}
} else
blk_mq_start_stopped_hw_queues(dev->ctrl.admin_q, true);
return 0;
}
這個(gè)函數(shù)是NVMe設(shè)備采用Multi-Queue(MQ)的核心函數(shù),所以在展開(kāi)解析這個(gè)函數(shù)之前,我們先聊聊Linux Multi-Queue Block Layer.?
如之前NVME文章(NVMe系列專題之二:隊(duì)列(Queue)管理)中介紹,多隊(duì)列、原生異步、無(wú)鎖是NVMe的最大特色,這些為高性能而生的設(shè)計(jì)迫使Linux Kernel在3.19拋棄了老的單隊(duì)列Block Layer而轉(zhuǎn)向Multi-Queue Block Layer. 這個(gè)Multi-Queue Block Layer的架構(gòu)直接對(duì)應(yīng)于NVMe的多隊(duì)列設(shè)計(jì),如下圖:
所謂的Multi-Queue機(jī)制就是在多核CPU的情況下,將不同的block層提交隊(duì)列分配到不同的CPU核上,以更好的平衡IO的工作負(fù)載,大幅提高SSD等存儲(chǔ)設(shè)備的IO效率。Multi-Queue Block Layer長(zhǎng)啥樣子呢?畫了個(gè)圖,看一下:
-
Multi-Queue Block Layer分為兩層,Software Queues和Hardware Dispatch Queues.?
-
Softeware Queues是per core的,Queue的數(shù)目與協(xié)議有關(guān)系,比如NVMe協(xié)議,可以有最多64K對(duì) IO SQ/CQ。Software Queues層做的事情如上圖標(biāo)識(shí)部分。
-
Hardware Queues數(shù)目由底層設(shè)備驅(qū)動(dòng)決定,可以1個(gè)或者多個(gè)。最大支持?jǐn)?shù)目一般會(huì)與MSI-X中斷最大數(shù)目一樣,支持2K。設(shè)備驅(qū)動(dòng)通過(guò)map_queue維護(hù)Software Queues和Hardware Queues之間的對(duì)接關(guān)系。
-
需要強(qiáng)調(diào)一點(diǎn),Hardware Queues與Software Queues的數(shù)目不一定相等,上圖1:1 Mapping的情況屬于最理想的情況。
到這里,Multi-Queue Block Layer基本理論我們就算回顧完畢了,我回過(guò)頭來(lái)在看看nvme_alloc_admin_tags這個(gè)函數(shù)。
從上面的代碼來(lái)看,主要分為三步:
-
對(duì)admin_tagset結(jié)構(gòu)體初始化,在這個(gè)過(guò)程中特別提一下ops的賦值(后續(xù)會(huì)用到)。
static struct blk_mq_ops?nvme_mq_admin_ops?= {
.queue_rq = nvme_queue_rq,
.complete = nvme_complete_rq,
.init_hctx =?nvme_admin_init_hctx,
.exit_hctx ? ? ?= nvme_admin_exit_hctx,
.init_request = nvme_admin_init_request,
.timeout = nvme_timeout,
};
-
接著調(diào)用blk_mq_alloc_tag_set分配tag set并與request queue關(guān)聯(lián),
-
然后調(diào)用blk_mq_init_allocated_queue對(duì)hardware queue和software queues進(jìn)行初始化,并配置兩者之間的mapping關(guān)系,最后將返回值傳遞給dev->ctrl.admin_q。
blk_mq_init_allocated_queue調(diào)用blk_mq_realloc_hw_ctxs,然調(diào)用blk_mq_init_hctx,最后調(diào)用set->ops->init_hctx,也就是nvme_admin_init_hctx。
也就是說(shuō),blk_mq_init_allocated_queue初始化最終調(diào)用的是nvme_admin_init_hctx:
static int?nvme_admin_init_hctx(struct blk_mq_hw_ctx *hctx, void *data,
unsigned int hctx_idx)
{
struct nvme_dev *dev = data;
struct nvme_queue *nvmeq = dev->queues[0];
WARN_ON(hctx_idx != 0);
WARN_ON(dev->admin_tagset.tags[0] != hctx->tags);
WARN_ON(nvmeq->tags);
hctx->driver_data =?nvmeq;
nvmeq->tags = &dev->admin_tagset.tags[0];
return 0;
}
從上面的code,可以發(fā)現(xiàn),Hardware Queue初始化時(shí),會(huì)將nvme_configure_admin_queue函數(shù)中申請(qǐng)的NVMe Queue(nvmeq)賦值給Hardware Queue的driver_data. 由此可知,NVMe Queue與Hardware Queue是一一對(duì)應(yīng)的關(guān)系,這也是NVMe與Linux Multi-Queue Block Layer默契配合的關(guān)鍵之處。
總結(jié)
以上是生活随笔為你收集整理的Linux NVMe Driver学习笔记之6:Admin Queue与Blk-mq初始化的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 快播案:程序正义、盗版和色情
- 下一篇: 快播(Qvod)也开始耍流氓了