美文网首页
初谈spdk

初谈spdk

作者: 九楼记 | 来源:发表于2022-03-26 23:45 被阅读0次

    关键词

    DPDK:全称Intel Data Plane Development Kit,是intel提供的数据平面开发工具集,为Intel architecture(IA)处理器架构下用户空间高效的数据包处理提供库函数和驱动的支持。通俗地说,就是一个用来进行包数据处理加速的软件库。

    CBDMA :全称Crystal Beach DMA是一个CPU里面的DMA引擎,有了它之后就能够在不中断CPU的情况下执行高效的内存拷贝。

    NVMe SSD:有一个SSD Controller,同时存储空间划分成若干个独立的逻辑空间,每个空间逻辑块地址(LBA)范围是0到N-1 (N是逻辑空间大小),这样划分出来的每一个逻辑空间我们就叫做NameSpace。

    为什么产生

    用户使用现在的固态设备,吞吐量和延迟性能比传统的磁盘好太多,现在总的处理时间中,存储软件占用了更大的比例。

    硬件推动软件革新需求。

    什么是spdk

    SPDK(全称Storage Performance Development Kit),Intel发布的,提供了一整套工具和库,以实现高性能、扩展性强、全用户态的存储应用程序。它是继DPDK之后,intel在存储领域推出的又一项颠覆性技术,旨在大幅缩减存储IO栈的软件开销,从而提升存储性能,可以说它就是为了存储性能而生。

    spdk的优势

    相对于传统IO方式,SPDK主要运用了两项关键技术:UIO和pooling,使用SPDK的存储系统能轻松达到数百万IOPS。

    首先,将设备驱动代码运行在用户态避免内核上下文切换和中断将会节省大量的处理开销,允许更多的时钟周期被用来做实际的数据存储。无论存储算法(去冗,加密,压缩,空白块存储)多么复杂,浪费更少的时钟周期总是意味着更好的性能和时延。

    其次,采用轮询模式改变了传统I/O的基本模型。在传统的I/O模型中,应用程序提交读写请求后进入睡眠状态,一旦I/O完成,中断就会将其唤醒。轮询的工作方式则不同,应用程序提交读写请求后继续执行其他工作,以一定的时间间隔回头检查I/O是否已经完成。这种方式避免了中断带来的延迟和开销,并使得应用程序提高了I/O效率。

    概括:要在用户态实施一套基于用户态软件驱动的完整I/O栈。

    spdk的组件构成

    image

    SPDK中大概有三类子组件:网络前端、处理框架、后端。

    网络前端子组件:包括DPDK网卡驱动和用户态网络服务UNS(这是一个Linux内核TCP/IP协议栈的替代品,能够突破通用TCP/IP协议栈的种种性能限制瓶颈)。DPDK在网卡侧提供了一个高性能的发包收包处理框架,在数据从网卡到操作系统用户态之间提供了一条快速通道。UNS代码则继续这一部分处理,稍加处理TCP/IP数据包的标准处理方式,并形成iSCSI命令。

    处理框架:包括iSCSI Target和Customer Storage App,“处理框架”部分拿到了数据包内容,将iSCSI命令转换为SCSI块级命令。然而,在它将这些命令发到“后端”驱动之前,SPDK提供了一套API框架,让厂商能够插入自己定义的处理逻辑(架构图中绿色的方框)。通过这种机制,存储厂商可在这里实现例如缓存、去重、压缩、加密、RAID计算,或擦除码(Erasure Coding)计算等功能,使这些功能包含在SPDK的处理流程中。

    后端:数据到达了“后端”驱动层,在这里SPDK和物理块设备交互(读和写操作)。这一系列后端设备驱动涵盖了不同性能的存储分层,保证SPDK几乎与每种存储应用形成关联。

    典型使用场景

    • 提供块设备接口的后端存储应用,诸如iSCSI Target和 NVMe-oF Target;
    • 对虚拟机中I/O (virtio) 的加速,主要支持Linux系统下的QEMU/KVM作为hypervisor 管理虚拟机的场景,使用vhost交互协议实现基于共享内存通道的高效vhost用户态target (例如vhost SCSI/blk/NVMe target)
    • SPDK加速数据库存储引擎。通过实现了RocksDB中的抽象文件类,SPDK的blobfs/blobstore目前可以和Rocksdb集成,用于在NVMe SSD上加速实现RocksDB引擎的使用。该操作的实质是bypass kernel文件系统将完全使用基于SPDK的用户态I/O stack。

    spdk例子

    hello-world.c

    重要概念

    让我们先搞清楚两个问题:

    • 问题1: 每一块NVMe固态硬盘里都一个控制器(Controller), 那么发现的所有NVMe固态硬盘(也就是NVMe Controllers)以什么方式组织在一起?

    • 问题2: 每一块NVMe固态硬盘都可以划分为多个NameSpace (类似逻辑分区的概念), 那么这些NameSpace以什么方式组织在一起?

    链表

    代码主要流程

    spdk_env_opts_init(&opts);
    spdk_env_init(&opts);
    rc = spdk_nvme_probe(NULL, NULL, probe_cb, attach_cb, NULL);
    hello_world();
    cleanup();
    
    
    • 1-2,初始化spdk环境及其配置基本项

    • 3,调用函数spdk_nvme_probe()主动发现NVMe SSDs设备。 关键函数是spdk_nvme_probe()。

    • 4,调用函数hello_world()做简单的读写操作

    • 5,调用函数cleanup()以释放内存资源,卸载NVMe SSD设备等。

    probe_cb和attach_cb是两个callback函数, (其实还有remove_cb)

    • probe_cb: 当扫描NVMe设备后被调用

    • attach_cb: 当一个NVMe设备已经被挂接到一个用户态的NVMe 驱动的时候被调用

    继续看代码:

    static void
    hello_world(void)
    {
        struct ns_entry         *ns_entry;
        struct hello_world_sequence sequence;
        int             rc;
        size_t              sz;
    
        TAILQ_FOREACH(ns_entry, &g_namespaces, link) {
            /*
             * Allocate an I/O qpair that we can use to submit read/write requests
             *  to namespaces on the controller.  NVMe controllers typically support
             *  many qpairs per controller.  Any I/O qpair allocated for a controller
             *  can submit I/O to any namespace on that controller.
             *
             * The SPDK NVMe driver provides no synchronization for qpair accesses -
             *  the application must ensure only a single thread submits I/O to a
             *  qpair, and that same thread must also check for completions on that
             *  qpair.  This enables extremely efficient I/O processing by making all
             *  I/O operations completely lockless.
             */
            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;
            }
            ……
    
    

    spdk_nvme_ctrlr_alloc_io_qpair:分配一个qpair,一个是submit queue,一个是complete queue,进行读写操作。一般都是一个sq和cq对应。创建好qpair后就可以通过qpair下发IO操作了。

    image
    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);
    
    

    spdk_nvme_ns_cmd_write:提交一个写请求到ns和qpair

    static void
    write_complete(void *arg, const struct spdk_nvme_cpl *completion)
    {
        ……
        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);
        ……
    }
    
    
    static void
    read_complete(void *arg, const struct spdk_nvme_cpl *completion)
    {
        struct hello_world_sequence *sequence = arg;
        printf("%s", sequence->buf);
    }
    
    

    sdpk推荐文档:https://spdk.io/doc/

    相关文章

      网友评论

          本文标题:初谈spdk

          本文链接:https://www.haomeiwen.com/subject/gzjejrtx.html