1
概述
本文基于SPDK v22.09(点击蓝字,阅读往期文章),从hello_world程序来剖析SDPK NVMe用户态驱动,对NVMe盘的初始化、管理和读写操作。
spdk/examples/nvme/hello_world.c整体结构代码:
图1显示了NVMe子系统和CPU以及内存设备之间的结构,这对于我们理解spdk[MX1] 的IO过程很有帮助。Qpair一般创建于内存中,nvme子系统作为一个pCIe endpoint通过pCIe总线挂接。接下来我们关注的是nvme子系统如何基于nvme协议,通过pCIe总线实现和HOST的通信以及它们之间进行通信的细节。本文将重点关注于设备的查找和绑定以及IO详细处理过程。
图1. NVMe子系统体系结构
2
设备查找
我们的重点部分是扫描设备并将设备和控制器绑定以及数据的读写操作。下面进行具体分析怎么扫描设备并和驱动绑定的。两个重要的回调函数probe_cb和attach_cb。
probe_cb:找到NVMe controller之后进行回调。
attach_cb:一旦NVMe控制器已连接到用户空间驱动程序后调用。
图2展示了spdk_nvme_probe函数的调用过程。
图2. 设备查找流程
在spdk_nvme_probe函数指明了transport id。调用spdk_nvme_probe_async,这是一个异步函数,函数中我们重点关注对于spdk_nvme_probe_async函数的调用。
spdk_nvme_probe_async函数实现了对于在nvme_driver_init函数中对全局变量g_spdk_nvme_driver进行初始化,由于是对全局变量进行操作,故要进行加锁操作。我们看一下在锁之间进行了哪些事情。首先进行主进程判断,只有主进程中的一个线程可以进行驱动的初始化,并维护一个共享内存。其他进程将会查找已存在的共享内存。在对g_spdk_nvme_driver初始化之后就可以使用g_spdk_nvme_driver内部持有的锁而将前面持有的锁释放掉。
g_spdk_nvme_driver是全局变量可以被多个进程访问,内部维护了一个多进程共享的队列,队列维护了已经和驱动绑定的控制器。
然后我们进行到nvme_probe_internal(probe_ctx, false),在这个函数里进行具体的设备查找工作。在nvme_transport_ctrlr_scan(probe_ctx, direct_connect)函数中,会在return时调用nvme_pcie_ctrlr_scan(probe_ctx, direct_connect),在nvme_pcie_ctrlr_scan函数中调用了函数spdk_pci_enumerate(spdk_pci_nvme_get_driver(),pcie_nvme_enum_cb, &enum_ctx),实现按照给定的驱动进行设备的枚举,并尝试将没有绑定驱动的设备绑定到驱动上。这里传入了一个回调函数,pcie_n