SPDK概述
SPDK是一个专为高性能存储设备开发的开源C库,它提供了一套高效的API和工具,用于直接访问诸如NVMe SSD等存储设备。SPDK通过在用户空间运行,避免了传统存储访问中的内核空间切换开销,从而实现低延迟和高吞吐量。
环境库 (env)
SPDK依赖名为env的环境库来处理内存分配和PCI设备操作。在使用NVMe驱动之前,需要初始化env环境。
- 内存分配: env库提供了高效的内存分配机制,例如利用大页面(Hugepages)来减少TLB (Translation Lookaside Buffer) 缺失,从而提升访问效率。
- PCI 设备操作: 提供了发现、访问、管理PCI设备的方法,包括枚举、资源分配、直接内存访问(DMA)配置等。
初始化env环境
以下是初始化SPDK环境并枚举NVMe设备的基本流程示例:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 
 | #include "spdk/stdinc.h"#include "spdk/env.h"
 #include "spdk/nvme.h"
 
 
 static bool probe_cb(void *cb_ctx, const struct spdk_nvme_transport_id *trid, struct spdk_nvme_ctrlr_opts *opts) {
 printf("发现NVMe设备:%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) {
 printf("已连接NVMe设备:%s\n", spdk_nvme_ctrlr_get_serial_number(ctrlr));
 }
 
 int main(int argc, char **argv) {
 struct spdk_env_opts opts;
 
 spdk_env_opts_init(&opts);
 opts.name = "hello_nvme";
 if (spdk_env_init(&opts) < 0) {
 fprintf(stderr, "无法初始化SPDK环境\n");
 return -1;
 }
 
 if (spdk_nvme_probe(NULL, NULL, probe_cb, attach_cb, NULL) != 0) {
 fprintf(stderr, "NVMe设备枚举失败\n");
 return -1;
 }
 
 
 
 spdk_env_fini();
 return 0;
 }
 
 | 
- probe_cb函数说明:- 
- 此回调函数用于检测每个NVMe控制器。返回true表示选择该控制器加入SPDK管理。
- 可以根据控制器的PCIe地址或其他属性决定是否管理该设备。
 
- attach_cb函数说明:- 
- 当probe_cb返回true时,会触发此回调函数,表示设备已被SPDK接管。
- ctrlr参数代表NVMe控制器,可用于进一步的设备管理和操作。
- 常见用途包括将控制器和命名空间信息存储于全局结构中,以便于应用程序中的管理和访问。
 
数据读写流程
在完成环境初始化和NVMe设备绑定之后,可以开始进行数据读写操作。这个过程主要涉及以下几个关键步骤:
1. 分配I/O队列
每个NVMe控制器支持多个I/O队列对。在进行数据传输之前,需要为应用程序至少创建一个队列对,包含一个提交队列和一个完成队列,用于命令的发送和响应的接收。
| 12
 3
 4
 5
 
 | struct spdk_nvme_qpair *qpair = spdk_nvme_ctrlr_alloc_io_qpair(ctrlr, NULL, 0);if (qpair == NULL) {
 fprintf(stderr, "创建I/O队列对失败\n");
 
 }
 
 | 
2. 分配空间
使用spdk_zmalloc或spdk_nvme_ctrlr_map_cmb等API分配DMA内存,以存储从主机到设备以及从设备到主机的数据。
3. 执行读写操作
控制器和I/O队列对准备就绪后,通过spdk_nvme_ns_cmd_read和spdk_nvme_ns_cmd_write函数执行数据读写。这些操作是异步的,需要指定回调函数来处理完成通知。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 
 | void write_complete(void *arg, const struct spdk_nvme_cpl *completion) {
 int *finish = arg;
 if (spdk_nvme_cpl_is_error(completion)) {
 
 }
 finish = 1;
 
 }
 
 
 int rc = spdk_nvme_ns_cmd_write(ns, qpair, buffer, lba,
 num_blocks,
 write_complete, NULL, 0);
 if (rc != 0) {
 fprintf(stderr, "写入命令提交失败\n");
 
 }
 
 | 
4. 轮询完成队列
由于NVMe操作是异步进行的,需要在应用的主循环中轮询I/O队列对,以处理完成的命令。
| 12
 3
 4
 
 | while (!finish) {spdk_nvme_qpair_process_completions(qpair, 0);
 
 }
 
 | 
5. 资源清理
应用程序结束时,及时释放分配的资源,包括I/O队列对、控制器资源,以及SPDK环境,以避免资源泄露。
| 12
 3
 
 | spdk_nvme_ctrlr_free_io_qpair(qpair);
 spdk_env_fini();
 
 | 
示例
一个完整的读写流程示例:hello_world.c