SPDK源码剖析一hello_world程序

SPDK初识之hello_world程序分析

首先是hello_world程序整体框架分析
在这里插入图片描述

int main(int argc, char **argv)
{
    
    rc = parse_args(argc, argv, &opts);
     
    if (spdk_env_init(&opts) < 0) {  // spdk环境初始化,最终调用dpdk环境初始化
    }

    // 扫描设备,将驱动和设备绑定,调用回调函数`probe_cb`和`attach_cb`
    rc = spdk_nvme_probe(&g_trid, NULL, probe_cb, attach_cb, NULL); 

    hello_world(); // IO qpair创建、nvme的读写
    cleanup();  

    return rc;
}

初始化SPDK环境

int spdk_env_init(const struct spdk_env_opts *opts)

函数 spdk_env_init 用于初始化SPDK环境,它接受一个指向 spdk_env_opts 结构体的指针作为参数。这个结构体包含了SPDK环境配置的各种选项。在调用 spdk_env_init 之前,通常需要先通过 spdk_env_opts_init 函数来初始化这个结构体,设置一些基本的配置选项。
最终调用DPDK的接口rte_eal_init来完成SPDK环境的初始化。

扫描设备

SPDK对于传输使用的协议或者总线虚拟化成一个transport,主要包含PCIE、TCP、Fabric、RDMA等类型。
SPDK对设备的管理类似Linux的设备驱动模型:包含总线、设备、驱动三个部分。
SPDK先后注册总线、驱动和transport。
之后提供了两个回调接口。

两个回调接口

probe_cb:
probe_cb 是在SPDK发现新的NVMe控制器后调用的回调函数。
在hello_world示例中,当SPDK发现一个新的NVMe控制器时,它仅打印了一条日志消息来确认控制器的发现。

attach_cb:
attach_cb 是在NVMe控制器成功连接到用户空间驱动程序后调用的回调函数。
在hello_world示例中,attach_cb 做了两件事情:
将初始化好的NVMe控制器添加到全局控制器列表 g_controllers 中,以便SPDK可以跟踪和管理所有已发现的控制器。
将命名空间(Namespace,NS)注册到控制器中。在NVMe中,命名空间是逻辑存储单元,它包含存储设备上的数据。注册命名空间意味着SPDK可以开始对它进行操作。

Qpair

首先明白SPDK中的关键概念:提交队列(Submission Queue)和完成队列(Completion Queue)
在NVMe(非易失性内存表达)协议中,SQ和CQ是两个关键的概念,用于管理NVMe设备上的I/O(输入/输出)操作。

SQ: SQ是NVMe设备上的一个队列,用于存放待处理的I/O请求。 主机(HOST)将I/O请求打包成命令,并放入SQ中。 NVMe子系统会从SQ中读取命令,并处理这些I/O请求。

CQ: CQ是NVMe设备上的另一个队列,用于存放处理完成的I/O请求。当NVMe子系统完成一个I/O请求时,它会生成一个完成命令,并将其放入CQ中。 主机从CQ中读取完成命令,以确认I/O操作的成功或失败。

在NVMe协议中,SQ和CQ的个数并没有要求一一对应。也就是说,一个SQ可以对应多个CQ,或者一个CQ可以对应多个SQ。这种设计增加了系统的灵活性,可以根据具体需求配置SQ和CQ的数量。

然而,在SPDK中,SQ和CQ通常是一一对应的,并且都包含在一个名为“qpair”的结构中。这种做法简化了SPDK的编程模型,使得开发者可以更容易地管理和同步I/O请求的提交和完成

QPair是SPDK(Storage Performance Development Kit)中的一种结构,用于管理NVMe(非易失性内存表达)设备的I/O操作。如下图所示:
在这里插入图片描述
QPair包括两个主要部分:SQ(Submission Queue,提交队列)和CQ(Completion Queue,完成队列)。
为了更有效地管理请求对象,每个QPair都会包含一个free_req对象池,用于缓存nvme_request对象实例。同时,还会包含一个free_tr对象池,用于缓存nvme_tracker对象,每个对象都关联一个cmdId(命令标识),以跟踪每个请求的执行情况。当请求完成时,相应的回调会被触发。

nvme_request对象内部主要维护了spdk_nvme_cmd数据结构,由于SQ和CQ使用不同的物理内存空间,因此在提交命令时需要进行一次数据拷贝。

对于执行失败的请求,QPair不会直接丢弃它们,而是先加入到queued_req队列中,以便后续进行重试处理。当queued_req队列不为空时,新的请求会先提交到这个队列中,确保之前失败的请求先得到处理。

在SPDK中,每个QPair(队列对)会绑定两个Doorbell寄存器,每个寄存器占用4个字节的空间。这些寄存器用于通知NVMe控制器关于I/O操作的状态,并且基于基于MMIO(内存映射I/O)方式更新。具体来说:
第一个Doorbell寄存器用于告知NVMe控制器,提交队列(SQ)中新的I/O命令已准备好执行。
第二个Doorbell寄存器用于通知NVMe控制器,完成队列(CQ)中I/O命令的执行状态已更新。

IO处理

创建IO qpair

创建IO qpair时先创建CQ再创建SQ。

IO读写

SPDK的命令在执行前,每个命令都会附带一个完成后的回调函数。这意味着一旦命令完成并收到对应的完成队列条目(CQE),就会触发这个回调函数。
因此,Hello_world示例利用了这一特性,实现了先写入数据再读取的操作流程。在发送写命令时,它定义了一个回调函数write_complete,并在该函数内部执行了NVMe的读操作,在发送读命令时,定义一个回调函数read_complete,在该函数内部打印数据,并将sequence.is_completed标志设置为1。

在Hello_world函数里,主程序一个while死循环,在循环体内周期性地调用spdk_nvme_qpair_process_completions() 函数。
这个函数会检查NVMe设备的完成队列(CQ),以确定是否有新的完成事件(CQEs)到达。
如果CQ中有新的完成事件,函数会处理这些事件,并调用相应的回调函数。在Hello_world示例中回调函数将sequence.is_completed标志位设置为1,于是死循环退出

//轮询I/O队列对,等待写操作完成
		while (!sequence.is_completed) {
			spdk_nvme_qpair_process_completions(ns_entry->qpair, 0);
		}
  • 8
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Cider瞳

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

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

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

打赏作者

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

抵扣说明:

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

余额充值