SPDK NVMe API

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"

// NVMe设备发现时的回调函数
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; // 继续处理该设备
}

// NVMe设备成功连接后的回调函数
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_zmallocspdk_nvme_ctrlr_map_cmb等API分配DMA内存,以存储从主机到设备以及从设备到主机的数据。

3. 执行读写操作

控制器和I/O队列对准备就绪后,通过spdk_nvme_ns_cmd_readspdk_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;
// 其他逻辑
}

// 示例:向NVMe设备写入数据
int rc = spdk_nvme_ns_cmd_write(ns, qpair, buffer, lba, // 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


SPDK NVMe API
https://ci-tz.github.io/2024/03/10/SPDK-NVMe-API/
作者
次天钊
发布于
2024年3月10日
许可协议