SPDK示例代码分析

一 SPDK简介

SPDK是intel公司为NVME ssd硬件开发的一套用于加速硬件性能的一款开发套件,支持不同层面的lib库,包括nvme ssd driver、ioat、bdev等。

二 源码目录

  • app
    • app/iscsi_tgt: iscsi target
    • app/nvmf_tgt: NVMe-oF target
    • app/iscsi_top:iscsi top工具 类似于linux top,用来监控iscsi
    • app/trace:iscsi target和nvme-of target trace工具
    • app/vhost:将virtio控制器呈现给基于qemu的虚拟机,并对IO进行处理
  • build
  • doc:spdk 下的doc文件
  • dpdk:spdk调用了dpdk的很多基础库
  • etc:各类型使用方式的基本配置
  • examples:示例代码
  • lib:开发库
  • mk:makefile文件
  • scripts:脚本及环境配置相关
  • test:各模块功能性能测试

    三 示例代码

    本文主要从hello_world代码开始,分析应用如何使用nvme ssd设备,hello_world代码利用ssd存储数据并读出。

int main(int argc, char **argv){
    int rc;
    struct spdk_env_opts opts;

    spdk_env_opts_init(&opts);
    opts.name = "hello_world";
    opts.shm_id = 0;
    if (spdk_env_init(&opts) < 0) {//前半部分代码主要都是用来初始化spdk环境及配置基本项
        fprintf(stderr, "Unable to initialize SPDK env\n");
        return 1;
    }

    printf("Initializing NVMe Controllers\n");

    rc = spdk_nvme_probe(NULL, NULL, probe_cb, attach_cb, NULL);//调用接口将机器内的nvme ssd加入列表
    if (rc != 0) {
        fprintf(stderr, "spdk_nvme_probe() failed\n");
        cleanup();
        return 1;
    }

    if (g_controllers == NULL) {
        fprintf(stderr, "no NVMe controllers found\n");
        cleanup();
        return 1;
    }

    printf("Initialization complete.\n");
    hello_world();//读写操作
    cleanup();//停止attach 对应的nvme ssd
    return 0;
}

主要流程:

  1. 初始化spdk环境
  2. 扫描nvme ssd并加载
  3. 读写操作
  4. 卸载nvme ssd
    其中,加载nvme ssd如何做到?众多的ssd又如何管理?
static bool
probe_cb(void *cb_ctx, const struct spdk_nvme_transport_id *trid,
     struct spdk_nvme_ctrlr_opts *opts)
{
    printf("Attaching to %s\n", trid->traddr);

    return true;
}

static void
attach_cb(void *cb_ctx, const struct spdk_nvme_transport_id *trid,
      struct spdk_nvme_ctrlr *ctrlr, const struct spdk_nvme_ctrlr_opts *opts)
{
    int nsid, num_ns;
    struct ctrlr_entry *entry;
    struct spdk_nvme_ns *ns;
    const struct spdk_nvme_ctrlr_data *cdata = spdk_nvme_ctrlr_get_data(ctrlr);

    entry = malloc(sizeof(struct ctrlr_entry));
    if (entry == NULL) {
        perror("ctrlr_entry malloc");
        exit(1);
    }

    printf("Attached to %s\n", trid->traddr);

    snprintf(entry->name, sizeof(entry->name), "%-20.20s (%-20.20s)", cdata->mn, cdata->sn);

    entry->ctrlr = ctrlr;
    entry->next = g_controllers;
    g_controllers = entry;

    //namespace==scis lun,不同的ssd支持的namespace数量不一致
    num_ns = spdk_nvme_ctrlr_get_num_ns(ctrlr);
    printf("Using controller %s with %d namespaces.\n", entry->name, num_ns);
    for (nsid = 1; nsid <= num_ns; nsid++) {
        ns = spdk_nvme_ctrlr_get_ns(ctrlr, nsid);
        if (ns == NULL) {
            continue;
        }
        register_ns(ctrlr, ns);
    }
}

不难发现,probe_cb在扫描对应的nvme ssd设备后进行回调,attach_cb在被加载后回调。由于nvme ssd有众多的namespace,相当于机械硬盘的LUN,因此需要进行管理,这里利用了两个数据结构。

struct ctrlr_entry {
    struct spdk_nvme_ctrlr  *ctrlr;
    struct ctrlr_entry  *next;
    char            name[1024];
};

struct ns_entry {
    struct spdk_nvme_ctrlr  *ctrlr;
    struct spdk_nvme_ns *ns;
    struct ns_entry     *next;
    struct spdk_nvme_qpair  *qpair;
};

static struct ctrlr_entry *g_controllers = NULL;//管理所有的nvme ssd
static struct ns_entry *g_namespaces = NULL;//管理所有ssd的namespace

在所有的ssd以及对应的namespace都注册管理完成后,进行数据读写操作。


static void
hello_world(void)
{
    struct ns_entry         *ns_entry;
    struct hello_world_sequence sequence;
    int             rc;

    ns_entry = g_namespaces;
    while (ns_entry != NULL) {

         //每个SSD控制器下支持多namespace,同时支持多个qpair,但应用需要确保多线程进行qpair操作时保证同时只能一个线程对同一个qpair操作
        ns_entry->qpair = spdk_nvme_ctrlr_alloc_io_qpair(ns_entry->ctrlr, NULL, 0);
        if (ns_entry->qpair == NULL) {
            printf("ERROR: spdk_nvme_ctrlr_alloc_io_qpair() failed\n");
            return;
        }

        sequence.buf = spdk_dma_zmalloc(0x1000, 0x1000, NULL);//调用spdk库进行内存分配,底层依赖于dpdk实现
        sequence.is_completed = 0;
        sequence.ns_entry = ns_entry;

        snprintf(sequence.buf, 0x1000, "%s", "Hello world!\n");

         //将sequence.buf内容写入lba启示地址为0的位置,写入完成后回调write_complete
        rc = spdk_nvme_ns_cmd_write(ns_entry->ns, ns_entry->qpair, sequence.buf,
                        0, /* LBA start */
                        1, /* number of LBAs */
                        write_complete, &sequence, 0);
        if (rc != 0) {
            fprintf(stderr, "starting write I/O failed\n");
            exit(1);
        }
        //write_complete的回调需要通过轮询的方式判断io是否完成进行
        while (!sequence.is_completed) {
            spdk_nvme_qpair_process_completions(ns_entry->qpair, 0);
        }

        spdk_nvme_ctrlr_free_io_qpair(ns_entry->qpair);//释放namespace,切换到下一个ns
        ns_entry = ns_entry->next;
    }
}

static void
write_complete(void *arg, const struct spdk_nvme_cpl *completion)
{
    struct hello_world_sequence *sequence = arg;
    struct ns_entry         *ns_entry = sequence->ns_entry;
    int             rc;


    spdk_dma_free(sequence->buf);//写IO完成后,释放响应buf
    sequence->buf = spdk_dma_zmalloc(0x1000, 0x1000, NULL);
    //读取刚写入的内容
    rc = spdk_nvme_ns_cmd_read(ns_entry->ns, ns_entry->qpair, sequence->buf,
                   0, /* LBA start */
                   1, /* number of LBAs */
                   read_complete, (void *)sequence, 0);
    if (rc != 0) {
        fprintf(stderr, "starting read I/O failed\n");
        exit(1);
    }
}


static void
read_complete(void *arg, const struct spdk_nvme_cpl *completion)
{
    struct hello_world_sequence *sequence = arg;

    printf("%s", sequence->buf);
    spdk_dma_free(sequence->buf);
    sequence->is_completed = 1;
}

本示例代码主要偏向于如何应用spdk库,至于底层的spdk实现需要深入spdk代码研究。

参考

已标记关键词 清除标记
SPDK(存储性能开发套件)官方文档中文版。 第一章 简介 1 1.1.什么是SPDK? 1 1.2.入门 1 1.3. Vagrant开发环境 3 1.4.更新日志(略) 6 第二章 概念 6 2.1. 用户空间驱动程序** 6 2.2. 来自用户空间的DMA** 7 2.3. 消息传递和并发** 9 2.4. NAND Flash SSD内部 13 2.5. 将I / O提交到NVMe设备** 15 2.5.1 NVMe规范 15 2.5.2 SPDK NVMe驱动程序I / O路径 15 2.6. 使用Vhost-user进行虚拟化I / O. 16 2.6.1 介绍 16 2.6.2 QEMU 17 2.6.3 设备初始化 18 2.6.4 I / O路径 19 2.6.5 SPDK优化 20 2.7. SPDK目录结构概述 20 2.8. SPDK移植指南 22 第三章 用户指南 22 3.1. 系统配置用户指南 22 3.1.1 IOMMU配置 22 3.2. SPDK应用程序概述 23 3.2.1 配置SPDK应用程序 23 3.3. iSCSI Target 26 3.3.1. iSCSI Target入门指南 26 3.3.2. 通过配置文件配置iSCSI Target 27 3.3.3. 通过RPC方法配置iSCSI Target 28 3.3.4. 配置iSCSI启动器 29 3.3.5. rpc配置示例*** 30 3.3.6. iSCSI 热插拔 32 3.4. NVMe over Fabrics Target 32 3.5. Vhost Target(略) 37 3.6 块设备用户指南 38 3.6.1 bdev介绍 38 3.6.2 通用RPC命令 38 3.6.3 Ceph RBD 39 3.6.4 压缩虚拟Bdev模块 40 3.6.5 加密虚拟Bdev模块 41 3.6.6 延迟vbdev模块 41 3.6.7 GPT(GUID分区表) 42 3.6.8 iSCSI bdev 43 3.6.9 Linux AIO bdev 43 3.6.10 OCF虚拟bdev 43 3.6.11 Malloc bdev 44 3.6.12 NULL bdev 44 3.6.13 NVMe bdev 44 3.6.14 逻辑卷Lvol 45 3.6.15 RAID 46 3.6.16 Passthru 46 3.6.17 Pmem 46 3.6.18 Virtio Block 47 3.6.19 Virtio SCSI 47 3.7 BlobFS(Blobstore文件系统) 48 3.7.1 RocksDB集成 48 3.7.2 FUSE插件 49 3.8 JSON-RPC方法(略) 49 第四章 程序员指南 49 4.1. Blobstore程序员指南 49 4.1.1 介绍 50 4.1.2 运作理论 50 4.1.3 设计注意事项 52 4.1.4 例子 54 4.1.5配置 54 4.1.6 组件细节 54 4.2. 块设备层编程指南 56 4.3 编写自定义块设备模块 58 4.3.1 介绍 58 4.3.2 创建一个新模块 59 4.3.3创建虚拟Bdev 60 4.4 NVMe over Fabrics目标编程指南 61 4.4.1 介绍 61 4.4.2 原语结构体 61 4.4.3 基础函数 62 4.4.4访问控制 62 4.4.5发现子系统 62 4.4.6 传输 63 4.4.7选择线程模型 63 4.4.8 跨CPU核心扩展 63 4.4.9 零拷贝支持 63 4.4.10 RDMA 63 4.5 Flash传输层 64 4.5.1 术语 64 4.5.2 使用方法 67 4.6 GDB宏用户指南 69 4.6.1 介绍 69 4.6.2 加载gdb宏 71 4.6.3 使用gdb数据目录 72 4.6.4 使用.gdbinit加载宏 72 4.6.5 为什么我们需要显式调用spdk_load_macros 72 4.6.6 以上可用的宏总结 73 4.6.7 添加新宏 73 4.7 SPDK “Reduce”块压缩算法 73 4.7.1 介绍 73 4.7.2 例子 74 4.8 通知库 78 第五章 基本信息 79 5.1 事件框架 79 5.1.1 事件框架设计注意事项 80 5.1.2 SPDK事件框架组件 80 5.1.3 应用框架 80 5.2 逻辑卷 81 5.2.1 术语 81 5.2.2 配置逻辑卷 84 5.3 矢量数据包处理(略) 86 第六章 杂项 86 6.1 介绍 86 6.2 NVMe的P2P API 86 6.3 确定设备支持 87 6.4 P2
相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页