【SPDK入门】

本文介绍了SPDK(存储性能开发套件)的基本概念及其核心思想,SPDK是一个由Intel发起的开源项目,旨在通过用户态、异步、轮询方式的NVMe驱动来加速NVMe SSD作为后端存储的应用软件。文章还深入探讨了SPDK的框架结构,包括资源管理、线程模型、线程间通信及无锁化的IO处理机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


前言

Storage Performance Development Kit 存储性能开发套件

一、SPDK是什么?

1. SPDK概念

SPDK (Storage performance development kit, http://spdk.io) 是由Intel发起、用于加速使用NVMe SSD作为后端存储的应用软件加速库。Intel对SPDK的定义是利用用户态、异步、轮询方式的NVMe驱动,用于加速NVMe SSD作为后端存储使用的应用软件的加速库

在这里插入图片描述

传统磁盘IO操作是经过内核处理的,采用中断的方式进行DMA将数据从内核态拷贝到用户态,再交给用户程序处理,而这些操作必然会消耗性能,内核主要完成两件事:

  1. 支持虚拟文件系统、各个存储子系统以及各个驱动的管理;
  2. 用户程序和文件系统之间的系统调用

2. SPDK核心思想

正常情况下,普通机械硬盘在通过内核进行IO操作;但是当前基于NVMe协议的SSD固态硬盘,拥有更快的读写速度,在IO请求频繁的场景,内核和用户态频繁切换,则会降低性能,而SPDK提供的就是绕过内核(kernel bypass)的解决方案,可以大幅度降低NVMe command的延迟 (Latency) ,同时提高单CPU核的IOPS(相对的,读写一定大小的数据量,bypass方案占用的CPU则是更低的),其核心思想主要为以下几点:

  • 用户态(user space):将所有必要的驱动程序移动到用户空间,从而避免 syscalls,并允许从应用程序中访问零拷贝。基于UIO或VFIO的支持直接将存储设备的的地址空间映射到应用空间的方式,并利用NVMe规范来初始化NVMe SSD设备,实现基本的I/O操作,从而构筑用户态驱动。
  • 异步(asynchronous):不需要等下发任务处理完即返回
  • 轮询(polled-mode):对磁盘进行CPU轮询(周期执行),一旦查询到IO操作,立马触发回调函数,而不是依赖中断(上下文切换,带来不稳定的性能,延时更高),这降低了总延迟和延迟方差;轮询速度快,中断消耗时间
  • 无锁(lockless):spdk设计的主要目标之一就随着使用硬件(e.g. SSD,NIC,CPU)的增多而获得性能的线性提升,为了达到这目的,spdk的设计者就必须消除使用更多的系统资源带来的overhead,如:更多的线程、进程间通信,访问更多的存储硬件、网卡带来的性能损耗。避免 I/O 路径中的所有锁(避免锁带来的性能损耗),而是依靠消息传递。依赖的dpdk的实现,其本质是使用cas(compare and swap)实现了多生产者多消费者FIFO队列

在这里插入图片描述

SPDK整体架构分为四层,自上而下,最上层的应用协议层指代SPDK对外支持的协议以及相关的存储应用,包含有网络存储NVMe-oF,iSCSI Target以及虚拟化vhost-blk/scsi Target等;第二层为存储服务层,他提供了对块或者文件的抽象,用来支持更多的存储业务,例如提供了Blobstore;第三层抽象了通用的块存储设备bdev,用来支持后端不同的存储方式,例如NVMe,NVMe-oF,Ceph RBD等,并支持自定义的存储设备;底层则是驱动层,在这一层上,SPDK实现了用户态驱动用来加速各类存储应用。右侧例举了一些SPDK可集成的服务以及应用场景。

spdk编程最基本的准则,就是避免在spdk核上出现进程上下文切换。其会打破spdk高性能框架,造成性能降低甚至不能工作。进程上下文切换会因为很多原因导致,大致列举如下:

  • cpu时间片耗尽
  • 进程在系统资源不足(比如内存不足)时,要等到资源满足后才可以运行,这个时候进程也会被挂起,并由系统调度其他进程运行。
  • 进程主动调用sleep等函数让出cpu使用权。
  • 当有优先级更高的进程运行时,为了保证高优先级进程的运行,当前进程会被挂起,由高优先级进程来运行。
  • 硬件中断会导致CPU上的进程被挂起,转而执行内核的中断服务程序。

3. 相关名词解释

PCIe:从PCI发展过来的,“e” 是 “express”的简称,是“快”的意思,PCI使用并口数据传输,PCIe使用串口传输,但是PCIe的速度更快。

NVMe:面向PCI-E固态硬盘的接口协议

用户空间文件系统:Filesystem in Userspace,简称FUCE,完全在用户态实现的系统,可以使用存储性能开发工具包spdk,所有操作在用户空间,无需内核操作,数据零拷贝,降低系统延时。性能比较好的用户空间文件系统有libpfs和libpmfs。spdk提供的用户态文件系统为blobfs

热插拔:带电插拔,在不断电的情况下,将硬件模块拔出系统,不影响正常工作,提高系统的稳定性,抗灾害能力。

DMA:直接内存访问(Direct Memory Access),在进行IO设备和内存的数据传输时,数据搬运工作全部交给DMA控制器,CPU不参与数据拷贝的事情(拷贝数据不经过CPU,但是整个过程还是会涉及CPU,决定拷贝什么数据,从哪里传输到哪里)

零拷贝:如何把地址同步给其他使用者,减少上下文切换和数据拷贝的次数,如mmap。mmap() 系统调用函数会直接把内核缓冲区里的数据「映射」到用户空间,这样,操作系统内核与用户空间就不需要再进行任何的数据拷贝操作。
在这里插入图片描述

PageCache:磁盘高速缓存,缓存最近被访问的数据,预读功能;对于大文件,可以提高文件读写的性能

UIO(Userspace I/O):是运行在用户空间的I/O技术,Linux系统中一般的驱动设备都是运行在内核空间,而在用户空间用应用程序调用即可,而UIO则是将驱动的很少一部分运行在内核空间,而在用户空间实现驱动的绝大多数功能!使用UIO可以避免设备的驱动程序需要随着内核的更新而更新的问题。
在这里插入图片描述

用户空间下的驱动程序比运行在内核空间的驱动要多得多,UIO框架下运行在内核空间的驱动程序所做的工作很简单,常做的只有两个:分配和记录设备需要的资源和注册uio设备和必须在内核空间实现的小部分中断应答函数。

RDMA:远程直接内存访问,远程直接内存访问两个或者多个计算机进行通讯的时候使用DMA, 从一个主机的内存直接访问另一个主机的内存。
在这里插入图片描述

RDMA是一种host-offload, host-bypass技术,允许应用程序(包括存储)在它们的内存空间之间直接做数据传输。具有RDMA引擎的以太网卡(RNIC)–而不是host–负责管理源和目标之间的可靠连接。使用RNIC的应用程序之间使用专注的QP和CQ进行通讯。

二、SPDK框架

1. 资源管理

SPDK利用CPU的亲和性,将线程和CPU核做绑定,设计了线程模型,应用程序从收到这个核的I/O操作到运行结束,都在该核上完成,这样可以更高效的利用缓存,同时也避免多核之间的内存同步问题。与此同时,在单核上的内存资源的管理,利用了大页存储来加速。需要说明的是,SPDK借助了DPDK对基础内存资源的管理,比如大页分配(大页配置说明: https://www.kernel.org/doc/Documentation/vm/hugetlbpage.txt),因此需要DPDK相关库作为依赖,另外DPDK也为SPDK提供了一些基础数据管理结构,例如内存池,无锁队列等等。

2.线程模型

在SPDK中的线程模型架构上,每一个CPU核中拥有一个内核线程,会初始化一个reactor。每一个reactor下可持有零到多个SPDK抽象出的轻量用户态线程spdk_thread。为了提高在reactor间通信与同步的效率,SPDK放弃了传统加锁的方式,而是通过向每个reactor的spdk_ring来发送消息。在抽象得到的spdk_thread下拥有poller,用来注册用户函数。基本的框架图如下:
在这里插入图片描述

SPDK一大宗旨是使用最少的CPU核和线程来完成最多的任务。为此,SPDK在初始化程序时(目前调用spdk_app_start函数)限定使用绑定CPU的哪些核,可以在配置文件或命名行中配置,例如在命令行中使用-c 0x5是指使用core0 和core2来启动程序。通过CPU核绑定函数的亲和性可以限制住CPU的使用,并且在每个核上运行一个thread,该thread在SPDK中被称为Reactor (如Figure 1所示)。目前SPDK的环境库 (ENV) 缺省仍旧使用了DPDK的EAL库来进行管理。总而言之,Reactor thread执行一个函数 (_spdk_reactor_run), 该函数的主体包含一个while (1) {} 功能的函数,直到Reactor的state被改变,例如受到 (spdk_app_stop 的调用)。为了高效,上述循环中也会有一些相应的机制让出CPU资源 (诸如sleep)。这样的机制大多时候会导致CPU使用100%的情况,这点和DPDK比较类似。
换言之,假设一个使用SPDK编程框架的应用运用了两个CPU core,那么每个core上就会启动一个Reactor thread。如此一来,用户怎么执行自己的函数呢?为了解决该问题,SPDK提供了一个Poller的机制,即用户定义函数的分装。SPDK提供的Poller分为两种:(1) 基于定时器的Poller;(2) 非定时器的Poller。SPDK的Reactor thread对应的数据结构(struct spdk_reactor) 有相应的列表来维护Poller的机制。例如,一个链表维护定时器的Poller,一个链表维护非定时器的Poller,并且提供Poller的注册和销毁函数。在Reactor的while循环中,它会不停的check这些Poller的状态,进行相应的调用,用户的函数也因此可以进行相应的调用。由于单个CPU上只有一个Reactor thread,所以同一个Reactor thread 中不需要一些锁的机制来保护资源。当然,位于不同CPU的core上的thread还是需要通信必要。为了解决该问题,SPDK封装了线程间异步传递消息 (Async Messaging Passing) 的方式。
在这里插入图片描述

3. 线程间通信

SPDK放弃使用传统的加锁方式来进行线程间的通信,因为这种方案比较低效。为了使同一个thread只执行自己所管理的资源,SPDK提供了Event (事件调用) 机制。该机制的本质是每个Reactor对应的数据结构 (struct spdk_reactor) 维护了一个Event事件的ring (环)。这个环是多生产者和单消费者 (MPSC: Multiple producer Single Consumer) 的模型,即每个Reactor thread可以接收来自任何其他Reactor thread (包括当前的Reactor Thread) 的事件消息进行处理。目前SPDK中Event ring的缺省实现依赖于DPDK的机制,应该有线性锁的机制,但是相较于线程间采用锁的机制进行同步要高效得多。毫无疑问,Event ring处理的同时也在进行Reactor的函数 (_spdk_reactor_run) 处理。每个Event事件的数据结构 (struct spdk_event) 其实包括了需要执行的函数、加上相应的参数以及要执行的core。简单而言,一个Reactor A 向另外一个Reactor B通信,其实就是需要Reactor B执行函数F(X) (X是相应的参数)。基于上述机制,SPDK就实现了一套比较高效的线程间通信机制。具体例子可以参照SPDK NVMe-oF target内部的一些实现,主要代码位于 (lib/nvmf) 目录。

4. IO处理无锁化

SPDK主要的I/O 处理模型是Run-to-completion,指运行直到全部完成。上述内容中提及,使用SPDK应用框架时,一个CPU core只拥有一个thread,该thread可以执行很多Poller (包括定时和非定时器)。Run-to-completion的宗旨是让一个线程最好执行完所有的任务。显而易见,SPDK的编程框架满足了该需要。如果不使用SPDK应用编程框架,则需要编程者自己注意这个事项。例如,使用SPDK用户态NVMe驱动访问相应的I/O QPair进行读写操作,SPDK 提供了异步读写的函数 (spdk_nvme_ns_cmd_read),同时检查是否完成的函数 (spdk_nvme_qpair_process_completions)。这些函数的调用应由一个线程完成,不应该跨线程处理。
SPDK 的I/O 路径也采用无锁化机制。当多个thread操作同意SPDK 用户态block device (bdev) 时,SPDK会提供一个I/O channel的概念 (即thread和device的一个mapping关系)。不同的thread 操作同一个device应该拥有不同的I/O channel,每个I/O channel在I/O路径上使用自己独立的资源就可以避免资源竞争,从而去除锁的机制

总结

参考文档
零拷贝技术
初识SPDK
PCIE学习
RDMA概述
项目地址: https://github.com/spdk/spdk
Development: https://spdk.io/development/

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 P2P问题 87 第七章 驱动程序 88 7.1 NVMe驱动程序*** 88 7.1.1 介绍 88 7.1.2 例子 88 7.1.3 公共接口 89 7.1.4 NVMe驱动程序设计 89 7.1.5 NVMe over Fabrics主机支持 91 7.1.6 NVMe多进程 91 7.1.7 NVMe Hotplug 92 7.2 I/OAT驱动程序 93 7.2.1 公共接口 93 7.2.2 关键功能 93 7.3 Virtio驱动程序 93 7.3.1 介绍 93 7.3.2 2MB大页面 93 第八章 工具 94 8.1 SPDK CLI 94 8.1.1 安装所需的依赖项 94 8.1.2 运行SPDK应用程序实例 94 8.1.3 运行SPDK CLI 94 8.1.4 可选 - 创建Python虚拟环境 94 8.2 nvme-CLI 95 8.2.1 nvme-cli with SPDK入门指南 95 8.2.2 使用场景 95 第九章 性能测试报告(略) 96 第十章NVMe-oF Target跟踪点*** 96 10.1 介绍 96 10.2 启用跟踪点 97 10.3 捕获事件的快照 97 10.4 捕获足够的跟踪事件 98 10.5 添加新的跟踪点 99
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

自动驾驶小哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值