spdk_nvme_helloworld分析

2023-03-15  本文已影响0人  itsenlin

概述

本文基于SPDK v23.1版本的hello_world示例来说明SPDK的nvme命令处理流程,代码架构如下:

example\nvme\hello_world.c

int main(int argc, char **argv)
{
    spdk_env_opts_init(&opts);
    rc = parse_args(argc, argv, &opts);  // 参数解析

    opts.name = "hello_world";
    if (spdk_env_init(&opts) < 0) {  // spdk环境初始化,最终调用的是dpdk的环境初始化
    }

    // 扫描设备,并将驱动和设备绑定,调用用户提供的回调`probe_cb`和`attach_cb`
    rc = spdk_nvme_probe(&g_trid, NULL, probe_cb, attach_cb, NULL); 

    hello_world(); // IO qpair创建、nvme的读写
    cleanup();     // 资源释放

    return rc;
}

标准的NVMe处理涉及到NVMe子系统、HOST CPU、HOST 内存三方面,下图展示这三者之间的关系:

初始化SPDK环境

接口:int spdk_env_init(const struct spdk_env_opts *opts)

这里需要的参数opts可以通过接口spdk_env_opts_init(&opts)来设置,以及通过当前程序提供的参数来修改parse_args(argc, argv, &opts)

默认的opts参数配置如下:

[ DPDK EAL parameters: hello_world --no-shconf -c 0x1 --huge-unlink --log-level=lib.eal:6 --log-level=lib.cryptodev:5 --log-level=user1:6 --iova-mode=pa --base-virtaddr=0x200000000000 --match-allocations --file-prefix=spdk_pid76450 ]

最终调用DPDK的接口rte_eal_init来完成SPDK环境的初始化,为后面的操作做好准备工作

设备查找

设备的注册

SPDK对设备的管理类似Linux的设备驱动模型:包含busdevicedriver三个部分。如下:

/**
 * Structure describing the PCI bus
 */
struct rte_pci_bus {
    struct rte_bus bus;               /**< Inherit the generic class */
    RTE_TAILQ_HEAD(, rte_pci_device) device_list; /**< List of PCI devices */
    RTE_TAILQ_HEAD(, rte_pci_driver) driver_list; /**< List of PCI drivers */
};

另外,SPDK对于传输使用的协议或者总线虚拟化成一个transport,主要包含PCIETCPFabricRDMA等类型。本文是基于example\nvme\hello_world来说明,此示例使用的是PCIE类型的transport

main函数执行之前会进行设备的注册,SPDK中使用的是gnuattribute特性来实现

  1. bus注册

       RTE_REGISTER_BUS(pci, rte_pci_bus.bus);
    
       #define RTE_REGISTER_BUS(nm, bus) \
       RTE_INIT_PRIO(businitfn_ ##nm, BUS) \
       {\
           (bus).name = RTE_STR(nm);\
           rte_bus_register(&bus); \         // TAILQ_INSERT_TAIL(&rte_bus_list, bus, next);,将bus放到rte_bus_list链表中,pci对应的bus为rte_pci_bus.bus
       }
    
       #define RTE_INIT_PRIO(func, prio) \
       static void __attribute__((constructor(RTE_PRIO(prio)), used)) func(void)
    
  2. driver注册前半部分

       SPDK_PCI_DRIVER_REGISTER(nvme, nvme_pci_driver_id,
            SPDK_PCI_DRIVER_NEED_MAPPING | SPDK_PCI_DRIVER_WC_ACTIVATE);
    
       #define SPDK_PCI_DRIVER_REGISTER(name, id_table, flags) \
       __attribute__((constructor)) static void _spdk_pci_driver_register_##name(void) \
       { \
           spdk_pci_driver_register(#name, id_table, flags); \   // 会将driver加到链表g_pci_drivers中
       }      
    
  3. transport注册

       SPDK_NVME_TRANSPORT_REGISTER(pcie, &pcie_ops);
         .name = "PCIE",
         .type = SPDK_NVME_TRANSPORT_PCIE,
    

    注册使用的宏如下(黄色部分保证宏在main之前执行,这种用法在spdk中很多,用到时再记录):

       #define SPDK_NVME_TRANSPORT_REGISTER(name, transport_ops) \
       static void __attribute__((constructor)) _spdk_nvme_transport_register_##name(void) \
       { \
           spdk_nvme_transport_register(transport_ops); \
       }
       spdk_nvme_transport_register(transport_ops); //在g_spdk_transports中申请一个空间存放这种transport对应的ops,并且把申请的空间放到链表g_spdk_nvme_transports中
    ```c
    
    
  4. main函数执行过程中的spdk初始化中会做设备的注册以及driver的后半部注册,将设备和驱动都注册到bus中

spdk_env_init.png

两个回调接口

probe_cb: 找到NVMe controller之后进行回调,hello_world示例中只做了一条日志打印。
attach_cb: NVMe controller连接到用户空间驱动程序后调用,hello_world示例中做了两件事,一是将初始化好的controller连接到g_controllers中;二是将NS注册到controller中。

设备查找流程

总体入口为SPDK接口

int
spdk_nvme_probe(const struct spdk_nvme_transport_id *trid, void *cb_ctx,
        spdk_nvme_probe_cb probe_cb, spdk_nvme_attach_cb attach_cb,
        spdk_nvme_remove_cb remove_cb)

其中三个callback参数都可以用户自定义,在hello_world示例中使用了probe_cbattach_cb

整体调用关系如下图所示

image-20230314135128751.png

其中QPair是SPDK的一种结构如下图所示(Admin和IO是一样的结构):

qpair结构.png

IO处理

创建IO qpair

根据NVMe协议要求:先创建IO CQ再创建IO SQ,如下图所示

image-20230314141340176.png
  1. SPDK使用polling机制来检查CQ,而不是使用MSI/MSI-X等中断形式,在创建CQ时对DW11的IV/IEN字段都设置为0,只设置了PC字段为1(即使用连续地址)

    代码如下:

    int
    nvme_pcie_ctrlr_cmd_create_io_cq(struct spdk_nvme_ctrlr *ctrlr,
                  struct spdk_nvme_qpair *io_que, spdk_nvme_cmd_cb cb_fn,
                  void *cb_arg)
    {
     ......
     req = nvme_allocate_request_null(ctrlr->adminq, cb_fn, cb_arg);
     ......
     cmd->cdw11_bits.create_io_cq.pc = 1;
     cmd->dptr.prp.prp1 = pqpair->cpl_bus_addr;
    
     return nvme_ctrlr_submit_admin_request(ctrlr, req);
    }
    
    

    NVMe协议描述如下:

    image-20230315155753473.png
  1. 通过循环检查CQE中的Phase Tag(P)字段来确定哪些是新来的CQE

    代码如下:

    int32_t
    nvme_pcie_qpair_process_completions(struct spdk_nvme_qpair *qpair, uint32_t max_completions)
    {
     ......
     pqpair->stat->polls++;
    
     while (1) {
         cpl = &pqpair->cpl[pqpair->cq_head];
    
         ......
         next_cpl = &pqpair->cpl[next_cq_head];
         next_is_valid = (next_cpl->status.p == next_phase);
         if (next_is_valid) {
             __builtin_prefetch(&pqpair->tr[next_cpl->cid]);
         }
         ......
    
         tr = &pqpair->tr[cpl->cid];
         /* Prefetch the req's STAILQ_ENTRY since we'll need to access it
          * as part of putting the req back on the qpair's free list.
          */
         __builtin_prefetch(&tr->req->stailq);
         pqpair->sq_head = cpl->sqhd;
    
         if (tr->req) {
             nvme_pcie_qpair_complete_tracker(qpair, tr, cpl, true);
         } else {
             SPDK_ERRLOG("cpl does not map to outstanding cmd\n");
             spdk_nvme_qpair_print_completion(qpair, cpl);
             assert(0);
         }
    
         if (++num_completions == max_completions) {
             break;
         }
     }
    
     ......
    
     return num_completions;
    }
    

    NVMe协议对CQE中Phase Tag的描述如下

image-20230315161226333.png
  1. Create IO CQ命令提交时SPDK设置了一个回调接口,在收到此命令的CQE之后调用此回调函数,而在此回调函数中做了Create IO SQ的命令处理,命令处理过程与Create IO CQ类似

构造IO读写

SPDK中的命令处理流程为:一个命令执行之前会添加此命令执行完成之后的回调接口,也即当此命令执行完并且收到对应CQE时会调用回调接口。

所以Hello_world示例通过此特性来实现了先写再读的操作,在写命令时设置回调write_complete,而在write_complete里面执行nvme的读操作,操作流程与前面的create_io_cqcreate_io_sq类似

image-20230316093212680.png

参考

《NVMe用户态驱动》

《SPDK源码》

《剖析SPDK读写NVMe盘过程--从hello_world开始》

上一篇 下一篇

猜你喜欢

热点阅读