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设备的基本流程示例:
1 2 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队列对。在进行数据传输之前,需要为应用程序至少创建一个队列对,包含一个提交队列和一个完成队列,用于命令的发送和响应的接收。
1 2 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
函数执行数据读写。这些操作是异步的,需要指定回调函数来处理完成通知。
1 2 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队列对,以处理完成的命令。
1 2 3 4
| while (!finish) { spdk_nvme_qpair_process_completions(qpair, 0); }
|
5. 资源清理
应用程序结束时,及时释放分配的资源,包括I/O队列对、控制器资源,以及SPDK环境,以避免资源泄露。
1 2 3
| spdk_nvme_ctrlr_free_io_qpair(qpair);
spdk_env_fini();
|
示例
一个完整的读写流程示例:hello_world.c