• Linux NVMe Driver学习笔记之6:Admin Queue与Blk-mq初始化


    这篇文章紧接上回分解,在nvme_probe函数的最后一步调用nvme_reset_work进行reset操作,nvme_reset_work的主要工作可以概括如下几个步骤:

    1. 进入nvme_reset_work函数后先检查NVME_CTRL_RESETTING标志,来确保nvme_reset_work不会被重复进入。

    2. 调用nvme_pci_enable

    3. 调用nvme_configure_admin_queue

    4. 调用nvme_init_queue

    5. 调用nvme_alloc_admin_tags

    6. 调用nvme_init_identify

    7. 调用nvme_setup_io_queues

    8. 调用nvme_start_queues/nvme_dev_add之后,接着调用nvme_queue_scan

    上篇文章中,我们解析了nvme_configure_admin_queue的内容,本文我们接着介绍nvme_reset_work中的其他函数。

    1. 先来看看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做的事情比较简单,就是对之前nvme_configure_admin_queue函数中申请的queue进行初始化操作。在这个过程中,对SQ Tail, CQ Head以及CQ phase变量进行初始化赋值,然后通过q_db指向Doorbell寄存器。

    有关SQ、CQ、Phase、Doorbell的详细解释请参考:

    NVMe系列专题之二:队列(Queue)管理

    2. 看完nvme_init_queue, 我们再接着瞅瞅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;

    }

    这个函数是NVMe设备采用Multi-Queue(MQ)的核心函数,所以在展开解析这个函数之前,我们先聊聊Linux Multi-Queue Block Layer. 

    如之前NVME文章(NVMe系列专题之二:队列(Queue)管理)中介绍,多队列、原生异步、无锁是NVMe的最大特色,这些为高性能而生的设计迫使Linux Kernel在3.19抛弃了老的单队列Block Layer而转向Multi-Queue Block Layer. 这个Multi-Queue Block Layer的架构直接对应于NVMe的多队列设计,如下图:

    所谓的Multi-Queue机制就是在多核CPU的情况下,将不同的block层提交队列分配到不同的CPU核上,以更好的平衡IO的工作负载,大幅提高SSD等存储设备的IO效率。Multi-Queue Block Layer长啥样子呢?画了个图,看一下:

    • Multi-Queue Block Layer分为两层,Software Queues和Hardware Dispatch Queues. 

    • Softeware Queues是per core的,Queue的数目与协议有关系,比如NVMe协议,可以有最多64K对 IO SQ/CQ。Software Queues层做的事情如上图标识部分。

    • Hardware Queues数目由底层设备驱动决定,可以1个或者多个。最大支持数目一般会与MSI-X中断最大数目一样,支持2K。设备驱动通过map_queue维护Software Queues和Hardware Queues之间的对接关系。

    • 需要强调一点,Hardware Queues与Software Queues的数目不一定相等,上图1:1 Mapping的情况属于最理想的情况。

    到这里,Multi-Queue Block Layer基本理论我们就算回顾完毕了,我回过头来在看看nvme_alloc_admin_tags这个函数。

    从上面的代码来看,主要分为三步:

    • 对admin_tagset结构体初始化,在这个过程中特别提一下ops的赋值(后续会用到)。

      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,

      };

    • 接着调用blk_mq_alloc_tag_set分配tag set并与request queue关联,

    • 然后调用blk_mq_init_allocated_queue对hardware queue和software queues进行初始化,并配置两者之间的mapping关系,最后将返回值传递给dev->ctrl.admin_q。

    blk_mq_init_allocated_queue调用blk_mq_realloc_hw_ctxs,然调用blk_mq_init_hctx,最后调用set->ops->init_hctx,也就是nvme_admin_init_hctx。

    也就是说,blk_mq_init_allocated_queue初始化最终调用的是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,可以发现,Hardware Queue初始化时,会将nvme_configure_admin_queue函数中申请的NVMe Queue(nvmeq)赋值给Hardware Queue的driver_data. 由此可知,NVMe Queue与Hardware Queue是一一对应的关系,这也是NVMe与Linux Multi-Queue Block Layer默契配合的关键之处。

  • 相关阅读:
    【矩阵论】1. 准备知识(上)
    【GDC】玩法设计
    Faiss:选择合适的索引Index
    客服系统Golang源码
    毛里智慧小学宿舍楼工程量清单编制
    微机----------------中断技术
    主流音视频芯片交叉编译工具链版本
    提高篇(五):使用Processing创作互动艺术:从灵感到实现
    G1D30-NLP(Tokenizer)&DP(交叠子问题)
    adb 获取当前界面元素
  • 原文地址:https://blog.csdn.net/zhuzongpeng/article/details/127604393