initiator端命令
获取target信息
这个实现是在nvme-cli模块内部实现的,跟一个discovery控制器连接,获取可以连接的所有的nvme子系统的信息,可能会有多个。如下的示例中,只有一个nvme subsystem可以连接:nqn.2016-06.io.spdk:cnode1。
具体命令如下:
root@cc:/home/cc# nvme discover -t rdma -a 192.168.50.242 -s 4420
Discovery Log Number of Records 1, Generation counter 1
=====Discovery Log Entry 0======
trtype: rdma
adrfam: ipv4
subtype: nvme subsystem
treq: not required
portid: 0
trsvcid: 4420
subnqn: nqn.2016-06.io.spdk:cnode1
traddr: 192.168.50.242
eflags: none
rdma_prtype: not specified
rdma_qptype: connected
rdma_cms: rdma-cm
rdma_pkey: 0x0000
跟target建立连接
root@cc:/home/cc# nvme connect -t rdma -n "nqn.2016-06.io.spdk:cnode1" -a 192.168.50.242 -s 4420
这个流程的实现是nvme-cli模块往,nvme-fabrics内核模块创建的/dev/nvme-fabric字符设备写入命令字符串,然后由nvme-fabrics内核模块来实现。
nvme-fabrics内核模块的实现,主要在drivers\nvme\host\fabrics.c文件中。
connect流程
nvme-fabrics内核模块的初始化:static int __init nvmf_init(void)
nvme-fabrics内核模块初始化创建的/dev/nvme-fabrics字符文件,接收nvme connect命令,建立和target的连接,创建一个nvme虚拟设备。具体流程为nvmf_dev_write回调函数,调用nvmf_create_ctrl创建一个struct nvme_ctrl实体。nvmf_create_ctrl函数的流程如下:
nvme-rdma模块的初始化函数为static int __init nvme_rdma_init_module(void),主要做两件事:
-
注册一个ib设备的client:
static struct ib_client nvme_rdma_ib_client = {
.name = "nvme_rdma",
.remove = nvme_rdma_remove_one
};
ib_register_client(&nvme_rdma_ib_client);
-
向nvme-fabric模块注册一个rdma的传输层:nvmf_register_transport(&nvme_rdma_transport);
static struct nvmf_transport_ops nvme_rdma_transport = {
.name = "rdma",
.module = THIS_MODULE,
.required_opts = NVMF_OPT_TRADDR,
.allowed_opts = NVMF_OPT_TRSVCID | NVMF_OPT_RECONNECT_DELAY |
NVMF_OPT_HOST_TRADDR | NVMF_OPT_CTRL_LOSS_TMO |
NVMF_OPT_NR_WRITE_QUEUES | NVMF_OPT_NR_POLL_QUEUES |
NVMF_OPT_TOS,
.create_ctrl = nvme_rdma_create_ctrl,
};
nvmf_register_transport(&nvme_rdma_transport);
核心函数为:
static struct nvme_ctrl *nvme_rdma_create_ctrl(struct device *dev,
struct nvmf_ctrl_options *opts)
其处理流程如下:
int nvme_rdma_setup_ctrl(struct nvme_rdma_ctrl *ctrl, bool new)流程如下
int nvme_rdma_configure_admin_queue(struct nvme_rdma_ctrl *ctrl, bool new)流程如下:
int nvme_rdma_alloc_queue(struct nvme_rdma_ctrl *ctrl, int idx, size_t queue_size)
初始化nvme_rdma_queue 的一些信息,建立一个ib的连接,并等待连接建立完成(3秒钟没有建立完成,认为超时失败)
struct nvme_rdma_queue {
struct nvme_rdma_qe *rsp_ring;
int queue_size; //管理队列固定32,io队列根据获取的信息确定,软件目前最大设置为128
//admin队列,固定64字节;
//io队列,根据Identify Controller Data Structure中的
//I/O Queue Command Capsule Supported Size (IOCCSZ)决定
size_t cmnd_capsule_len;
struct nvme_rdma_ctrl *ctrl; //反指向所属的nvme_rdma_ctrl
struct nvme_rdma_device *device; //自己创建的rdma device,
//和一个ib_device对应,有一个指向该ib_device的指针,
//和一个在该ib设备上创建的protect domian的指针
//以及在该ib设备上,执行单次rdma send操作所能携带的SG的最大个数
struct ib_cq *ib_cq; //qp的sq和rq对应的cq
struct ib_qp *qp; //rdma sq and rq
unsigned long flags; //3个bit状态:NVME_RDMA_Q_ALLOCATED、LIVE、TR_READY
struct rdma_cm_id *cm_id; //调用rdma_create_id创建的rdma的连接管理实体,
//会注册rdma connect manage的回调nvme_rdma_cm_handler,该函数会对
//各种rdma connect的连接状态,进行下一步的处理
int cm_error;
struct completion cm_done; //用于线程等待异步操作执行完成,主要是等待rdma建立连接
bool pi_support; //t10卡的特殊特性, protect information,暂不关心
int cq_size;
struct mutex queue_lock;
};
struct nvme_rdma_device {
struct ib_device *dev;
struct ib_pd *pd;
struct kref ref;
struct list_head entry;
unsigned int num_inline_segments;
};
struct nvme_rdma_request {
struct nvme_request req; //nvme core模块定义的request
struct ib_mr *mr; //找ib的qb获取mr,将nvme请求的data部分,注册到该mr
//req->mr = ib_mr_pool_get(queue->qp, &queue->qp->rdma_mrs)
//ib_map_mr_sg(req->mr, req->data_sgl.sg_table.sgl, count, NULL, SZ_4K);
struct nvme_rdma_qe sqe; //data指向一个nvme cmd数据缓存,这个是申请的一块内存
//sqe.data = kzalloc(sizeof(struct nvme_command), GFP_KERNEL);
//sqe.dma是将sqe.data映射为ib设备的一个地址
//sqe->cqe.done = nvme_rdma_send_done;
union nvme_result result;
__le16 status;
refcount_t ref;
//cmd采用ib sg的方式发送,如果cmd携带inline capule data,则会有多个ib sg,
//目前最多带4个inline capule data
struct ib_sge sge[1 + NVME_RDMA_MAX_INLINE_SEGMENTS];
u32 num_sge; //当前cmd的ib sg个数,如果不带inline capule data,则只有一个
struct ib_reg_wr reg_wr; //ib sq的请求,请求的内容是让硬件快速注册nvme data的mr
struct ib_cqe reg_cqe; //对应上面的reg_wr请求的完成回调
struct nvme_rdma_queue *queue;
struct nvme_rdma_sgl data_sgl; //nvme请求的data部分的sgl(linux lib)
//blk mq模块,会将block请求的data部分,转换成sgl:
//req->data_sgl.nents = blk_rq_map_sg(rq->q, rq, req->data_sgl.sg_table.sgl);
struct nvme_rdma_sgl *metadata_sgl;
bool use_sig_mr;
};
mq-blk->nvme rdma->ib verbs数据结构
ib提交请求send请求接口
static inline int ib_post_send(struct ib_qp *qp,
const struct ib_send_wr *send_wr,
const struct ib_send_wr **bad_send_wr)
-
struct ib_send_wr,可以串起来,一次发送多个请求;
-
对于每一个请求,除了要指定请求的类型(mr注册/send/read/write等等),请求的数据,还需要一个给定一个struct ib_cqe指针(业务方申请地址存放),这个里面要指定一个回调函数;send操作的最终结果,会通过这个回调通知业务方。
-
回调函数的入参有两个,一个是ib qb,一个是之前发送请求时指定的struct ib_cqe指针。业务方通过struct ib_cqe指针,找到自己的上下文信息。
In-capsule data的场景
只能是以下全满足的场景:
-
只能是IO write操作(管理队列不支持In-capsule data)
-
io请求的sgl数目不能超过ib一次请求可以携带的sg个数-1(cmd占用一个sg),另外nvme rdma软件侧,额外限制了最大sgl个数为4个。
-
data + cmd长度,不能超过target控制器支持的最大capsule长度(I/O Queue Command Capsule Supported Size (IOCCSZ))。这里要求target控制器的in-capsule data offset必须为0,没有考虑不为0的情况。(要支持in-capsule data offset不为0,则代码实现有点复杂度,ib接口要额外填充一块in-capsule data offset的数据,这块数据要额外申请内存)。