NVMe
简介
NVMe over Fabrics (NVMe-oF) 是 NVMe 网络协议对以太网和光纤通道的扩展,可在存储和服务器之间提供更快、更高效的连接,并降低应用程序主机服务器的 CPU 利用率
NVM Express over Fabrics 定义了一个通用架构,支持存储网络结构上的 NVMe 块存储协议的一系列存储网络结构。 这包括启用存储系统的前端接口、横向扩展至大量 NVMe 设备以及扩展数据中心内可访问 NVMe 设备和 NVMe 子系统的距离
NVMe over Fabrics 规范的制定工作于 2014 年开始,目标是将 NVMe 扩展到以太网、光纤通道和 InfiniBand® 等结构上。 NVMe over Fabrics 旨在与任何合适的存储结构技术配合使用。 该规范于 2016 年 6 月发布
Nvmf架构图:
本文基于Linux5.10.38及RDMA, OFED驱动
KO文件及流程图
依赖nvme-core, nvme-fabrics, nvme-rdma, nvmet可选
nvme初始化及nvme_cli连接nvmet_tgt流程图:
nvme_cli与spdk_tgt参考流程图:
源码分析
nvme_cli -> spdk_tgt discover流程
gdb --args nvme discover -t rdma -a 172.17.29.217 -s 4420 -> admin_passthru
gdb --args /usr/sbin/nvme discover -t rdma -s 4420 -a 172.17.29.217
nvme.c -> main -> int main(int argc, char **argv)
handle_plugin -> int handle_plugin
plugin->commands[i] -> COMMAND_LIST -> ENTRY("discover", "Discover NVMeoF subsystems", discover_cmd)
cmd->fn(argc, argv, cmd, plugin) -> static int discover_cmddiscover(desc, argc, argv, false) -> int discover(const char *desc, int argc, char **argv, bool connect)argconfig_parsebuild_options -> static int build_optionsstatic int do_discover(char *argstr, bool connect) -> nqn=nqn.2014-08.nvmexpress.discovery,transport=rdma,traddr=172.17.29.217,trsvcid=4420,hostnqn=nqn.2014-08.nvmexpress:uuid:e4a72828-9b7f-454f-ab8a-60b2c57e2439,hostid=cee6f16a-0183-4d00-ac0f-58add_ctrl -> static int add_ctrl(const char *argstr) -> 控制器设备: /dev/nvme0open(PATH_NVME_FABRICS, O_RDWR)write(fd, argstr, len) -> 写设备, 触发内核驱动处理(nvmf_dev_write) -> nqn=nqn.2014-08.nvmexpress.discovery,transport=rdma,traddr=175.17.53.73,trsvcid=4420,hostnqn=nqn.2014-08.nvmexpress:uuid:a8dce057-b5a2-492e-8da3-9cf328f401c7,hostid=a20d3ab6-2c0a-4335-8552-305 -> Jul 12 11:13:56 s63 kernel: nvme nvme0: new ctrl: NQN "nqn.2014-08.nvmexpress.discovery", addr 172.17.29.65:4420Jul 12 11:14:01 s63 systemd: Started Session 3337 of user root.read(fd, buf, BUF_SIZE)nvmf_get_log_page_discovery -> static int nvmf_get_log_page_discoverynvme_discovery_lognvme_get_lognvme_get_log13remove_ctrlnvmf_get_log_page_discovery -> static int nvmf_get_log_page_discovery -> /dev/nvme0nvme_discovery_log -> int nvme_get_log -> nvme_get_log13 -> .opcode = nvme_admin_get_log_page -> return ioctl(fd, ioctl_cmd, cmd) -> 管理命令: nvme_admin_get_log_page = 0x02enum nvme_admin_opcode nvme管理命令 -> linux/nvme.hnvme_submit_admin_passthruioctl(fd, NVME_IOCTL_ADMIN_CMD, cmd) -> 转到内核驱动处理 -> nvme_dev_ioctlremove_ctrlcase DISC_OKret = connect_ctrls(log, numrec)case DISC_NO_LOGprint_discovery_logconnect_ctrls转到内核处理:
...
文件系统fs:
.unlocked_ioctl = nvme_dev_ioctl
nvme_user_cmdcopy_from_usernvme_validate_passthru_nsidcmon.opcode = cmd.opcode;...nvme_cmd_allowedstatus = nvme_submit_user_cmdnvme_alloc_user_requestblk_mq_alloc_requestnvme_init_requestnvme_req(req)->flags |= NVME_REQ_USERCMDnvme_map_user_request -> nvme_alloc_request 需要大量参数。 将其分成两个函数以减少参数数量。 第一个保留名称 nvme_alloc_request, 而第二个名为 nvme_map_user_requestbio = req->bioctrl = nvme_req(req)->ctrlnvme_passthru_startnvme_command_effectsnvme_mpath_start_freezenvme_start_freezeet = nvme_execute_rq(req, false)status = blk_execute_rq(rq, at_head)blk_mq_insert_request -> ... -> nvme_rdma_queue_rqblk_mq_run_hw_queueblk_rq_is_poll -> false HCTX_TYPE_DEFAULTwait_for_completion_ioblk_rq_unmap_user(bio)blk_mq_free_request(req)nvme_passthru_end(ctrl, effects, cmd, ret)spdk_tgt处理查询日志页命令:#0 nvmf_ctrlr_get_log_page (req=0x2000070d4560) at ctrlr.c:2517
#1 0x00000000004d2dbc in nvmf_ctrlr_process_admin_cmd (req=0x2000070d4560) at ctrlr.c:3592
#2 0x00000000004d5365 in spdk_nvmf_request_exec (req=0x2000070d4560) at ctrlr.c:4566
#3 0x000000000050b800 in nvmf_rdma_request_process (rtransport=0xd9a440, rdma_req=0x2000070d4560) at rdma.c:2239
#4 0x000000000050ea12 in nvmf_rdma_qpair_process_pending (rtransport=0xd9a440, rqpair=0xda5ec0, drain=false) at rdma.c:3276
#5 0x00000000005120ab in nvmf_rdma_poller_poll (rtransport=0xd9a440, rpoller=0xda4e70) at rdma.c:4685
#6 0x0000000000512333 in nvmf_rdma_poll_group_poll (group=0xda4da0) at rdma.c:4767
#7 0x00000000004f4d04 in nvmf_transport_poll_group_poll (group=0xda4da0) at transport.c:715
#8 0x00000000004e6a93 in nvmf_poll_group_poll (ctx=0xd29060) at nvmf.c:70
#9 0x000000000059d238 in thread_execute_poller (thread=0xd82a90, poller=0xd82e20) at thread.c:946
#10 0x000000000059d7bb in thread_poll (thread=0xd82a90, max_msgs=0, now=514611841303047) at thread.c:1072
#11 0x000000000059da5b in spdk_thread_poll (thread=0xd82a90, max_msgs=0, now=514611841303047) at thread.c:1156
#12 0x000000000055f7de in _reactor_run (reactor=0xd29140) at reactor.c:914
#13 0x000000000055f8cd in reactor_run (arg=0xd29140) at reactor.c:952
#14 0x000000000055fd4b in spdk_reactors_start () at reactor.c:1068
#15 0x000000000055c20a in spdk_app_start (opts_user=0x7fffffffde70, start_fn=0x407c15 <nvmf_tgt_started>, arg1=0x0) at app.c:808
#16 0x0000000000407d1d in main (argc=3, argv=0x7fffffffe038) at nvmf_main.c:47
nvmf_rdma_qpair_process_pendingSTAILQ_FOREACH_SAFE nvmf_rdma_request_processspdk_nvmf_request_execnvmf_ctrlr_process_admin_cmdcase SPDK_NVME_OPC_GET_LOG_PAGEnvmf_ctrlr_get_log_pagespdk_nvmf_qpair_get_listen_trid.qpair_get_listen_trid = nvmf_rdma_qpair_get_listen_tridspdk_nvme_trid_populate_transportnvmf_get_discovery_log_pagenvmf_generate_discovery_logspdk_nvmf_subsystem_get_firstspdk_nvmf_subsystem_get_nextRB_NEXT(subsystem_tree, &tgt->subsystems, subsystem) -> 创建subsystem的时候将数据插入红黑树: RB_INSERT -> nvmf:使用 RB 树来跟踪 tgt 子系统 目前我们使用数组,这对于许多子系统来说计算成本很高,因为查找需要对子系统 nqns 进行 O(n) 字符串比较。 它的容量并不昂贵,因为它只是一个指针数组。 因此,将其从指针数组切换为 RB_HEAD,这样我们就可以将查找次数减少到 O(log n) 字符串比较。 请注意,我们仍然会为每个传输轮询组分配 spdk_nvmf_subsystem_poll_groups 数组,因为我们不想在 IO 路径中产生 RB_FIND 的额外成本nvmf_transport_listener_discovernvmf_rdma_discoverentry->trtype = SPDK_NVMF_TRTYPE_RDMA...entry->tsas.rdma.rdma_qptype = SPDK_NVMF_RDMA_QPTYPE_RELIABLE_CONNECTEDentry->tsas.rdma.rdma_cms = SPDK_NVMF_RDMA_CMS_RDMA_CMnvme_cli connect 连接SPDK_TGT流程, 创建队列与发现命令类似:
nvme_cli连接:
gdb --args nvme nvme connect -t rdma -n nvme-subsystem-name -a 172.17.29.65 -s 4421
gdb --args nvme connect -t rdma -n "nqn.2022-06.io.spdk:cnode216" -a 172.17.29.217 -s 4420
main -> handle_plugin... -> ENTRY("connect", "Connect to NVMeoF subsystem", connect_cmd)
connect_cmd -> int connectret = argconfig_parse(argc, argv, desc, command_line_options, &cfgbuild_optionsinstance = add_ctrl(argstr) -> static int add_ctrl -> 添加控制器fd = open(PATH_NVME_FABRICS, O_RDWR) -> PATH_NVME_FABRICS "/dev/nvme-fabrics"if (write(fd, argstr, len) -> nqn=nvme-subsystem-name,transport=rdma,traddr=172.17.29.65,trsvcid=4421,hostnqn=nqn.2014-08.nvmexpress:uuid:3195faad-fe20-44d5-8ae4-291d29629c89,hostid=a872f1d1-daae-4da0-be86-5a36131e640b -> [root@s63 ~]# bpftrace -e 'kprobe:nvmf_dev_write{ printf("%s\n", kstack); }' -> nvmf_dev_writelen = read(fd, buf, BUF_SIZE)case OPT_INSTANCE内核处理
static const struct file_operations nvmf_dev_fops = {.owner = THIS_MODULE,.write = nvmf_dev_write,.read = seq_read,.open = nvmf_dev_open,.release = nvmf_dev_release,
};
static ssize_t nvmf_dev_writebuf = memdup_user_nul(ubuf, count) -> 内存拷贝 -> duplicate memory region from user space and NUL-terminatenvmf_create_ctrl(struct device *dev, const char *buf) -> /sys/class/nvme-fabrics -> 参考, kernel-nvmf: nvmf_ctrl_options *opts -> 关键数据结构体 -> nvme-fabrics:添加通用 NVMe over Fabrics 库,NVMe over Fabrics 库为传输和 nvme 核心提供接口,以处理独立于底层传输的特定于结构的命令和属性,此外,fabrics 库添加了一个杂项设备 允许实际创建结构控制器的接口,因为我们不能像 PCI 情况那样自动发现它。 nvme-cli 实用程序已得到增强,可以使用此接口来支持结构连接和发现nvmf_parse_optionsrequest_module("nvme-%s", opts->transport) -> nvme-rdma.ko -> 当内核发现一个需要的module不在内核中时,会调用request_module去用户空间创建进程去加载这个缺失的module, Linux内核模块的自动加载及request_module系统调用: (&nvmf_transports_rwsem) -> nvme-fabrics:将 nvmf_transports_mutex 转换为 rwsem 互斥体可防止在创建控制器时更改传输列表,但使用普通的旧互斥体意味着它还会序列化控制器创建。 这不必要地减慢了创建多个控制器的速度 - 例如,对于 RDMA 传输,创建控制器涉及为每个 IO 队列建立一个连接,这涉及更多的网络/软件往返,因此延迟可能会变得很严重。解决此问题的最简单方法是 将互斥量更改为 rwsem,并仅在列表发生变化时保留它以进行写入。 由于我们可以在创建控制器时读取rwsem,因此我们可以并行创建多个控制器, rw_semaphore,对于无竞争的 rwsem,计数和所有者是任务在获取 rwsem 时需要接触的唯一字段。 因此,它们被放置在彼此旁边,以增加它们共享相同缓存行的机会。 在竞争的 rwsem 中,所有者可能是结构中最常访问的字段,因为持有 osq 锁的乐观等待者将在所有者上旋转。 对于嵌入式 rwsem,包含结构中的其他热字段应远离 rwsem,以减少它们共享相同缓存行而导致缓存行弹跳问题的机会, down_read()是读者用来得到读写信号量sem时调用的,如果该信号量在被写者所持有,则对该函数的调用会导致调用者的睡眠。通过该操作,多个读者可以获得读写信号量, .htmlnvmf_lookup_transporttry_module_get -> 首先判断模块module是否处于活动状态,然后通过local_inc()宏操作将模块module的引用计数加1up_read(&nvmf_transports_rwsem);nvmf_check_required_optsnvmf_check_allowed_opts -> 检查选项ctrl = ops->create_ctrl -> .create_ctrl = nvme_rdma_create_ctrl -> static struct nvme_ctrl *nvme_rdma_create_ctrl -> 创建控制器inet_pton_with_scope -> 将tgt地址和端口转为socket地址nvme_rdma_existing_controller -> 比较6元组: <Host NQN, Host ID, local address, remote address, remote port, SUBSYS NQN>nvmf_ip_options_matchINIT_DELAYED_WORK(&ctrl->reconnect_work nvme_rdma_reconnect_ctrl_work -> nvme-rdma:集中控制器设置序列,将控制器序列集中到单个例程,该例程在故障后正确清理,而不是在多个流程中具有多个外观(创建、重置、重新连接)。我们在这里还获得的一件事是理智/边界 连接回动态控制器时也会检查INIT_WORK(&ctrl->err_work, nvme_rdma_error_recovery_work)nvme_stop_keep_alive -> nvme-rdma:在错误恢复中不要完全停止控制器,通过在已经发生故障的控制器上调用 nvme_stop_ctrl 将等待扫描工作完成(仅通过识别超时到期时间,即 60 秒)。 当我们已经知道控制器发生故障时,这是不必要的cancel_delayed_work_sync(&ctrl->ka_work)flush_worknvme_rdma_teardown_io_queues(ctrl, false);nvme_unquiesce_io_queues(&ctrl->ctrl);nvme_rdma_teardown_admin_queue(ctrl, false);nvme_unquiesce_admin_queue(&ctrl->ctrl);nvme_auth_stop(&ctrl->ctrl);nvme_rdma_reconnect_or_remove -> 重连或移除queue_delayed_work(nvme_wq, &ctrl->reconnect_work -> nvme_rdma_reconnect_ctrl_worknvme_rdma_setup_ctrl...INIT_WORK(&ctrl->ctrl.reset_work, nvme_rdma_reset_ctrl_work)nvme_init_ctrl -> 初始化nvme控制器, 初始化 NVMe 控制器结构。 这需要在最早的初始化期间调用,以便我们在探测期间拥有初始化的结构, nvme:将 chardev 和 sysfs 接口移至通用代码为此,我们需要添加适当的控制器初始化例程和除 PCIe 控制器列表之外的所有控制器列表,该列表保留在 pci.c 中。 请注意,当对控制器的最后一个引用被删除时,我们会删除 sysfs 设备 - 旧代码会将其保留更长时间,这没有多大意义。这需要一个新的 ->reset_ctrl 操作来实现控制器重置,并需要一个新的 ->reset_ctrl 操作来实现控制器重置。 ->write_reg32 实现子系统重置所需的操作。 现在,我们还存储 NVMe 合规版本的缓存副本以及控制器是否连接到子系统或不在通用控制器结构中的标志INIT_LIST_HEAD(&ctrl->namespaces) -> 初始命名空间链表init_rwsem(&ctrl->namespaces_rwsem)INIT_WORK(&ctrl->scan_work, nvme_scan_work)nvme_init_non_mdts_limitsnvme_scan_ns_list -> 扫描命名空间列表: , 驱动架构分析: , nvme_reset_work, 这个函数初始化nvme盘的admin和io队列(struct nvme_queue),同时初始化nvme盘的管理队列和请求队列对应的硬件队列描述结构blk_mq_tag_set,注意:这里的请求队列结构是struct request_queue,并不是nvme盘收发命令的admin和io队列,每个nvme逻辑盘只有一个请求队列,一个该请求队列对应多个nvme盘硬件io队列。nvme逻辑盘用struct nvme_ns结构表示,该结构包含通用盘设备结构:struct gendisk, blk_mq_tag_set结构包含一个物理nvme盘硬件队列数、队列深度、io请求及处理等信息,该结构包含物理块设备所有描述信息,是块设备软件请求队列和硬件物理存储设备队列之间的纽带,建立了系统软件层面的io请求队列和物理存储设备硬件队列的映射关系。通过该结构文件系统读写操作发送的io请求最终到达物理存储设备INIT_WORK(&ctrl->async_event_work, nvme_async_event_work)INIT_WORK(&ctrl->fw_act_work, nvme_fw_act_work)INIT_WORK(&ctrl->delete_work, nvme_delete_ctrl_work)INIT_DELAYED_WORK(&ctrl->ka_work, nvme_keep_alive_work)INIT_DELAYED_WORK(&ctrl->failfast_work, nvme_failfast_work)ctrl->ka_cmdmon.opcode = nvme_admin_keep_aliveida_alloc -> idr, ida内核id机制: (&ctrl->ctrl_device)nvme:将控制器引用计数切换为使用结构设备而不是为字符设备句柄分配单独的结构设备,而是将其嵌入到结构 nvme_ctrl 中并将其用于主控制器引用计数。 这消除了双重引用计数,并为我们提供了字符设备操作的自动引用。 我们暂时将 ctrl->device 保留为指针,以避免到处更改 printks,但将来我们可以研究采用与其他子系统类似的控制器结构的消息打印助手。请注意,delete_ctrl 操作始终已经有一个引用(或者通过 sysfs 由于此更改,或者因为 /dev/nvme-fabrics 节点上的每个打开文件现在输入时都有一个引用,所以我们不需要在那里执行 except_zero 变体kobject_initdevice_pm_initctrl->device->devt = MKDEV(MAJOR(nvme_ctrl_base_chr_devt) -> 创建设备主编号, MKDEV 用于将给定的主设备号和次设备号的值组合成 dev_t 类型的设备号, 创建/dev中的设备, alloc_chrdev_region函数,来让内核自动给我们分配设备号dev_set_drvdata -> 函数用来设置device 的私有数据, 设置控制器指针dev_set_name -> 设置设备名, 如: /dev/nvme2nvme_get_ctrlcdev_init(&ctrl->cdev, &nvme_dev_fops) -> 函数操作表为: static const struct file_operations nvme_dev_fopscdev_device_add -> 添加字符设备device_addnvme_fault_inject_initnvme_mpath_init_ctrl -> 多路径, nvme-multipath:修复 ANA 状态 nvme_init_identify 的双重初始化,因此 nvme_mpath_init 可以被多次调用,因此不得覆盖可能已初始化或正在使用的字段。 当控制器初始化时,分离出一个用于基本初始化的助手,并确保 init_identify 路径不会盲目地更改正在使用的数据结构INIT_WORK(&ctrl->ana_work, nvme_ana_work)nvme_auth_init_ctrlchanged = nvme_change_ctrl_state NVME_CTRL_CONNECTINGret = nvme_rdma_setup_ctrl(ctrl, true) -> 变更: nvme-rdma:集中控制器设置序列将控制器序列集中到单个例程,该例程在故障后正确清理,而不是在多个流程中具有多个外观(创建、重置、重新连接)。 我们在这里还获得的一件事是在连接回动态控制器时进行健全性/边界检查nvme_rdma_configure_admin_queuenvme_rdma_alloc_queue(ctrl, 0, NVME_AQ_DEPTH) -> 队列深度32 -> ...queue->cm_id = rdma_create_id(&init_net, nvme_rdma_cm_handlerrdma_resolve_addr(queue->cm_id, src_addr -> 触发cm事件: RDMA_CM_EVENT_ADDR_RESOLVED -> cm_error = nvme_rdma_addr_resolved(queue)nvme_rdma_create_queue_ib -> nvme-rdma:使 nvme_rdma_[create|destroy]_queue_ib 对称,我们将引用放在销毁例程中的设备上,因此我们应该在创建例程中查找并获取引用queue->device = nvme_rdma_find_get_device(queue->cm_id)nvme_rdma_create_cq(ibdev, queue)ib_alloc_cqnvme_rdma_create_qp(queue, send_wr_factor)init_attr.event_handler = nvme_rdma_qp_eventinit_attr.sq_sig_type = IB_SIGNAL_REQ_WRinit_attr.qp_type = IB_QPT_RCrdma_create_qp(queue->cm_id, dev->pd, &init_attr)queue->rsp_ring = nvme_rdma_alloc_ring(ibdev, queue->queue_sizering = kcalloc(ib_queue_sizenvme_rdma_alloc_qeib_dma_map_single -> 将内核虚拟地址映射为 -> DMA地址dma_map_singlepages_per_mr = nvme_rdma_get_max_fr_pagesib_mr_pool_init ?NVME_RDMA_Q_TR_READYrdma_set_service_type -> 设置服务类型, Setting Type of Service (ToS), , nvme-rdma:为 rdma 传输添加 TOS 对于 RDMA 传输,TOS 是 IB QoS 的扩展,为客户端提供隔离不同类型数据的流量的能力。 RDMA CM 使用 rdma_set_service_type() 将其抽象为 ULP。 在内部,每个流量流都由一个连接来表示,该连接具有与普通连接一样的所有独立资源,并按服务类型进行区分。 换句话说,IP 对之间可以有多个 qp 连接,并且每个连接都支持唯一的服务类型。 TOS 用途之一是带宽管理,它允许为 QoS 类别设置带宽限制,例如 80% 带宽分配给 QoS 类别 A 的控制器,20% 分配给 QoS 类别 B 的控制器。 注意:除了 TOS 配置之外,还必须在目标(发送 RDMA 命令)和发起方的相关 HCA 上配置 QOS,以影响流量, 用法: nvme connect --tos=0 --transport=rdma --traddr=10.0.1.1 --nqn=test-nvmerdma_resolve_route -> 触发cm事件: RDMA_CM_EVENT_ROUTE_RESOLVED -> cm_error = nvme_rdma_route_resolved(queue)param.retry_count = 7 -> 发生错误时应在连接上重试数据传输操作的最大次数。 此设置控制发生超时时重试发送、RDMA 和原子操作的次数。 仅适用于 RDMA_PS_TCP, param.rnr_retry_count = 7(特殊值,规范中表示无限次) -> 设置连接参数, 最大重试无限次, 收到接收器未就绪 (RNR) 错误后应在连接上重试远程对等方发送操作的最大次数。 当发送请求在缓冲区已发布以接收传入数据之前到达时,会生成 RNR 错误。 仅适用于 RDMA_PS_TCP, 提供可靠、面向连接的 QP 通信,与 TCP 不同,RDMA 端口空间提供基于消息而不是流的通信ret = rdma_connect_locked(queue->cm_id, ¶m) -> 连接服务端, 在服务端tgt, 触发cm事件: RDMA_CM_EVENT_CONNECT_REQUEST -> nvmet_rdma_queue_connect(cm_id, event)nvmet_rdma_find_get_device(cm_id)nline_page_count = num_pagesndev->pd = ib_alloc_pd(ndev->device, 0) -> 分配保护域nvmet_rdma_init_srqs(ndev) ?queue = nvmet_rdma_alloc_queue(ndev, cm_id, event)ret = nvmet_sq_init(&queue->nvme_sq)nvmet_rdma_parse_cm_connect_reqINIT_WORK(&queue->release_work, nvmet_rdma_release_queue_work)nvmet_rdma_alloc_rspsnvmet_rdma_alloc_cmds ?nvmet_rdma_create_queue_ibrdma_create_qpnvmet_rdma_post_recvnvmet_rdma_cm_accept(cm_id, queue, &event->param.conn)rdma_accept(cm_id, ¶m) -> 在host和tgt端触发cm事件: RDMA_CM_EVENT_ESTABLISHEDhost: -> queue->cm_error = nvme_rdma_conn_established(queue)nvme_rdma_post_recv -> nvme_rdma_recv_donecomplete(&queue->cm_done) -> 唤醒等待的函数: nvme_rdma_wait_for_cm-------------------------------------------------tgt: -> nvmet_rdma_queue_established(queue)nvmet_rdma_handle_command(queue, cmd)nvmet_rdma_map_sgllist_add_tail(&queue->queue_list, &nvmet_rdma_queue_list)nvme_rdma_wait_for_cm(queue)set_bit(NVME_RDMA_Q_ALLOCATED, &queue->flags)...ctrl->ctrl.numa_node = ibdev_to_node(ctrl->device->dev) -> 获取ib设备numa节点T10-PI support -> nvme-rdma:添加元数据/T10-PI 支持,对于有能力的 HCA(例如 ConnectX-5/ConnectX-6),这将允许端到端保护信息直通和 NVMe over RDMA 传输验证。 元数据卸载支持是通过新的 RDMA 签名动词 API 实现的,并且为有能力的控制器启用ctrl->max_fr_pages = nvme_rdma_get_max_fr_pagesreturn min_t(u32, NVME_RDMA_MAX_SEGMENTS, max_page_list_len - 1) -> 两数取其小nvme_rdma_alloc_qenvme_alloc_admin_tag_set -> 参考: 驱动 | Linux | NVMe | 2. nvme_probe, -> ...nvme_rdma_alloc_tag_setnvme_rdma_start_io_queuesblk_mq_update_nr_hw_queuesnvme_change_ctrl_statenvme_start_ctrl(&ctrl->ctrl) -> 启动nvme控制器, struct nvme_ctrl 抽象 NVMe 设备中和 NVMe 协议相关的部分nvme_start_keep_alive -> 启动保活nvme_queue_keep_alive_work(ctrl)queue_delayed_work(nvme_wq, &ctrl->ka_work, ctrl->kato * HZ / 2) -> kato: nvme:添加保持活动支持定期保持活动是 NVMe over Fabrics 中的强制功能,在 PCIe 的 NVMe 1.2.1 中是可选功能。 此补丁添加了从主机定期发送的保持活动状态,以验证控制器是否仍然响应,反之亦然。 keep-alive 超时是用户定义的(使用 keep_alive_tmo 连接参数),默认为 5 秒。为了避免主机发送 keep-alive 与目标端 keep-alive 超时过期竞争的竞争条件,主机添加了一个宽限 向目标发布保持活动超时时,时间为 10 秒。如果保持活动失败(或超时),则会启动传输特定的错误恢复。目前仅连接 NVMe over Fabrics 以支持保持活动,但我们可以 一旦实际支持 PCIe 的控制器可用,即可轻松添加 PCIe 支持, nvme:清理 KATO 设置,根据 NVMe 基本规范,KATO 命令应以 KATO 间隔的一半发送,以正确考虑往返时间。 由于我们现在每个连接只发送一个 KATO 命令,因此我们可以轻松使用推荐值。 这还修复了 KATO 命令的请求超时与连接命令中的值不匹配的潜在问题,这可能会导致目标的虚假连接丢失static void nvme_keep_alive_workNVME_CRTL_ARRT_TBKAS , I/O completion seen, 看见IO完成 -> io完成时 nvme_complete_rq, 设置看见IO完成标记位 -> 基于流量的保持活动 (TBKAS) 允许主机和控制器在存在管理或 I/O 命令处理的情况下重新启动基于流量的保持活动计时器。 控制器对 TBKAS 位的支持在识别控制器数据结构的控制器属性中指示(参见图 275)。 如果控制器不支持基于流量的保活(TBKAS 清除为“0”),则保活功能的操作将在第 3.9.1 节中描述。 如果在保持活动超时间隔期间未处理管理命令或 I/O 命令而保持已建立的连接,则会出现基于流量的保持活动超时。 如果在保持活动超时间隔内处理管理命令或 I/O 命令,则在保持活动定时器到期时,应重新启动保持活动定时器。 如果在保持活动超时间隔内没有向控制器提交管理命令或 I/O 命令(如第 3.4.4 节中定义),则控制器可能会认为发生了保持活动超时。 如果管理命令或 I/O 命令在保持活动超时间隔内传输到控制器,则在保持活动定时器到期时,控制器应重新启动保持活动定时器。 如果主机在保持活动超时间隔内未收到任何管理命令或任何 I/O 命令的完成,则主机可能会认为发生了基于流量的保持活动超时。 如果管理命令或 I/O 命令在保持活动超时间隔内完成,则在保持活动定时器到期时,主机应重新启动保持活动定时器。 主机应在保持活动超时的一半时检查任何管理命令和 I/O 命令的命令完成队列条目,考虑到传输往返时间、传输延迟、命令处理时间和保持活动计时器粒度。 为了防止控制器检测到保持活动超时,如果在保持活动超时间隔的一半时间内没有管理命令和 I/O 命令发送到控制器,主机应发送保持活动命令,.html, Keep Alive命令(参考第5.27.1.12节)和相关功能被主机用来确定控制器是否在运行,并被控制器用来确定主机是否在运行。当主机和控制器都可以访问并能够发出或处理命令时,它们就可以运行。控制器在Identify Controller data structure 中的KAS字段中指出Keep Alive Timer的粒度(参考Figure 275blk_mq_alloc_request(ctrl->admin_q, nvme_req_op(&ctrl->ka_cmd) -> 分配KA管理命令nvme_init_request(rq, &ctrl->ka_cmd)blk_execute_rq_nowait(rq, false) -> 在队列尾部插入IOnvme_enable_aen(ctrl) -> nvme:启用aen,无论是否存在I/O队列AEN通常与I/O队列的存在无关,因此无论是否存在都启用它们。 请注意,唯一的例外是发现控制器不支持任何请求的 AEN,并且 nvme_enable_aen 将尊重该请求并返回,因此无论如何启用它仍然是安全的。请注意,即使在初始命名空间扫描之前启用 AEN 也是安全的,因为我们在 工作队列上下文nvme_queue_scan -> 参考: (ctrl)nvme_mpath_update(ctrl) -> 更新nvme多路径, 从 nvme_init_identify() 调用的 nvme_mpath_init_identify() 从 ctrl 获取新的 ANA 日志。 这对于现有命名空间以及 ctrl 启动后可能发现的那些 scan_work 拥有最新的路径状态至关重要nvme_parse_ana_log nvme_update_ana_statenvme_change_uevent(ctrl, "NVME_EVENT=connected")dev_info nvmf_ctrl_subsysnqn -> 打印info级别的调试信息module_put(ops->module) -> module_put函数功能描述:该函数的功能是将一个特定模块module的引用计数减一,这样当一个模块的引用计数因为不为0而不能从内核中卸载时,可以调用此函数一次或多次,实现对模块计数的清零,从而实现模块卸载...pr_info("no handler found for transport %s.\n"seq_file->private = ctrlspdk_tgt启动流程, 通过CM与host端建立RDMA连接
gdb调试spdk_nvme_tgt
nvmf_main.c:47
spdk_app_opts_init -> opts=0x7fffffffdee0, opts_size=224#define SET_FIELD(field, value) -> 临时定义宏SET_FIELD(enable_coredump, true) -> 设置默认选项...SET_FIELD(disable_signal_handlers, false)
gdb -> p opts
spdk_app_parse_args -> 解析参数
spdk_app_start(&opts, nvmf_tgt_started, NULL) -> spdk_app_start(struct spdk_app_opts *opts_user -> g_start_fn = start_fnapp_copy_optsspdk_log_set_print_levelapp_setup_envcalculate_mempool_sizespdk_log_openspdk_reactors_init(size_t msg_mempool_size) -> 初始化 reactor, spdk线程模型: = spdk_mempool_create(mempool_namerte_mempool_createspdk_env_get_last_coreSPDK_ENV_FOREACH_CORE -> 遍历cpuposix_memaligng_core_infos = callocspdk_thread_lib_init_ext(reactor_thread_op, reactor_thread_op_supported -> g_thread_op_fn -> g_thread_op_supported_fn -> 初始化线程库。 必须在分配任何线程之前调用一次 thread_op_fn 和 thread_op_type_supported_fn 必须同时指定或不指定 -> nvmeof_tgt: , 将g_new_thread_fn赋值为reactor_thread_op,从而实现后续以spdk_create_thread创建的逻辑层面的thread都和具体的reactor相关联, 使用SPDK lib搭建自己的NVMe-oF Target应用: = spdk_mempool_create -> 创建消息池msgpoolSPDK_ENV_FOREACH_CORE reactor_construct -> 构造reactorreactor->events = spdk_ring_createif (reactor_interrupt_init(reactor) -> 默认中断模式 -> 中断:在thd和reactor中应用fd_group,每个reactor和每个线程分配一个fd组。 同时,每个线程被视为一个中断源,注册到其相应的反应器中。 reacotr 的egrp函数是唯一等待事件的阻塞点spdk_fd_group_createreactor->resched_fd = eventfdSPDK_FD_GROUP_ADD(reactor->fgrp, reactor->resched_fd, reactor_schedule_thread_eventepoll_ctl(epfdSPDK_FD_GROUP_ADD reactor->events_fd event_queue_run_batchspdk_interrupt_mode_is_enabledreactor = spdk_reactor_get(current_core)g_scheduling_reactor = reactorspdk_cpuset_set_cpuspdk_thread_create("app_thread", &tmp_cpumask) -> 创建一个新的 SPDK 线程对象。 请注意,通过 spdk_thread_create() 创建的第一个线程将被指定为应用程序线程。 其他 SPDK 库可能会限制某些 API 只能在此应用程序线程的上下文中调用spdk_interrupt_mode_is_enabled -> 默认禁用中断g_thread_op_fn(thread, SPDK_THREAD_OP_NEW) -> reactor_thread_op_reactor_schedule_threadspdk_cpuset_zerospdk_cpuset_set_cpuspdk_cpuset_xor -> 异或, eactor:避免在intr中将线程调度到reactor,目前,spdk_thread无法在处于中断模式的reactor上执行,但spdk_thread的中断未启用。 所以避免调度 spdk_thread 就可以了dst->cpus[i] ^= src->cpus[i]spdk_cpuset_copy -> copy dst <- srcspdk_cpuset_and -> dst->cpus[i] &= src->cpus[i]spdk_cpuset_get_cpu(cpumask, core) -> return (set->cpus[cpu / 8] >> (cpu % 8)) & 1U -> 位运算evt = spdk_event_allocate(core, _schedule_thread, lw_thread, NULL)event = spdk_mempool_get(g_spdk_event_mempool)event->fn = fnlw_thread->tsc_start = spdk_get_ticks() -> lib/event: 将线程的运行时间添加到framework_get_reactors RPC的输出中,收集每个SPDK线程的运行时间并将其添加到framework_get_reactors RPC的输出中spdk_event_call(evt)rc = spdk_ring_enqueue(reactor->events, (void **)&event, 1, NULL) -> 消息事件入队rc = write(reactor->events_fd, ¬ify, sizeof(notify)) -> 默认不通知spdk_thread_send_msg(spdk_thread_get_app_thread(), bootstrap_fn, NULL)msg->fn = fnspdk_ring_enqueue(thread->messages, (void **)&msg, 1, NULL) -> 消息其实是通过spdk_ring_enqueue()放入了ring-buffer中的。在后续的poller挂在的函数中对ring-buffer中的消息进行处理, 代码分析: (thread)spdk_reactors_startreactor_run(reactor)while (1)_reactor_run(reactor)event_queue_run_batch(reactor)count = spdk_ring_dequeue(reactor->events, events, SPDK_EVENT_BATCH_SIZE)event->fn(event->arg1, event->arg2) -> _schedule_thread(void *arg1, void *arg2)spdk_thread_poll -> thread_pollmsg_queue_run_batch -> msg->fn(msg->arg) -> bootstrap_fn_nvmf_transport_create_done -> ctx->cb_fn -> _ctx->ops->create -> .create = nvmf_rdma_create,nvmf_rdma_creatertransport->event_channel = rdma_create_event_channel()rtransport->data_wr_pool = spdk_mempool_createrdma_get_devicescreate_ib_deviceibv_query_devicenvmf_rdma_is_rxe_deviceTAILQ_INSERT_TAIL(&rtransport->devices, device, link)ibv_alloc_pdspdk_rdma_create_mem_map"Create IB device xxx"rc = generate_poll_fds(rtransport);nvmf_rdma_acceptspdk_env_thread_wait_all()
nvme落盘io流程, iopath
static const struct blk_mq_ops nvme_rdma_mq_ops = {.queue_rq = nvme_rdma_queue_rq,plete = nvme_rdma_complete_rq,.init_request = nvme_rdma_init_request,.exit_request = nvme_rdma_exit_request,.init_hctx = nvme_rdma_init_hctx,.timeout = nvme_rdma_timeout,.map_queues = nvme_rdma_map_queues,.poll = nvme_rdma_poll,
};
static blk_status_t nvme_rdma_queue_rqnvme_check_ready -> 对于我们无法发送到设备的状态,默认操作是使其忙碌并在控制器状态恢复后重试。 但是,如果控制器正在删除,或者任何内容被标记为快速故障或 nvme 多路径,则会立即失败。 注意:用于初始化控制器的命令将被标记为快速故障。 注意:nvme cli/ioctl 命令被标记为故障快速req->sqe.dma = ib_dma_map_single(dev, req->sqe.dataib_dma_mapping_errorib_dma_sync_single_for_cpunvme_setup_cmdnvme_start_request(rq)nvme_rdma_map_dataib_dma_sync_single_for_devicenvme_rdma_post_send <- drivers/nvme/host/rdma.cib_post_send
参考
NVME规范2.0:
Linux内核5.10.38: .10/readme_linux_with_git_log
Nvme_Cli用户态项目: .8.1_xb/readme
SPDK项目:
晓兵
博客: | |
weixin: ssbandjl
公众号: 云原生云
NVMe
简介
NVMe over Fabrics (NVMe-oF) 是 NVMe 网络协议对以太网和光纤通道的扩展,可在存储和服务器之间提供更快、更高效的连接,并降低应用程序主机服务器的 CPU 利用率
NVM Express over Fabrics 定义了一个通用架构,支持存储网络结构上的 NVMe 块存储协议的一系列存储网络结构。 这包括启用存储系统的前端接口、横向扩展至大量 NVMe 设备以及扩展数据中心内可访问 NVMe 设备和 NVMe 子系统的距离
NVMe over Fabrics 规范的制定工作于 2014 年开始,目标是将 NVMe 扩展到以太网、光纤通道和 InfiniBand® 等结构上。 NVMe over Fabrics 旨在与任何合适的存储结构技术配合使用。 该规范于 2016 年 6 月发布
Nvmf架构图:
本文基于Linux5.10.38及RDMA, OFED驱动
KO文件及流程图
依赖nvme-core, nvme-fabrics, nvme-rdma, nvmet可选
nvme初始化及nvme_cli连接nvmet_tgt流程图:
nvme_cli与spdk_tgt参考流程图:
源码分析
nvme_cli -> spdk_tgt discover流程
gdb --args nvme discover -t rdma -a 172.17.29.217 -s 4420 -> admin_passthru
gdb --args /usr/sbin/nvme discover -t rdma -s 4420 -a 172.17.29.217
nvme.c -> main -> int main(int argc, char **argv)
handle_plugin -> int handle_plugin
plugin->commands[i] -> COMMAND_LIST -> ENTRY("discover", "Discover NVMeoF subsystems", discover_cmd)
cmd->fn(argc, argv, cmd, plugin) -> static int discover_cmddiscover(desc, argc, argv, false) -> int discover(const char *desc, int argc, char **argv, bool connect)argconfig_parsebuild_options -> static int build_optionsstatic int do_discover(char *argstr, bool connect) -> nqn=nqn.2014-08.nvmexpress.discovery,transport=rdma,traddr=172.17.29.217,trsvcid=4420,hostnqn=nqn.2014-08.nvmexpress:uuid:e4a72828-9b7f-454f-ab8a-60b2c57e2439,hostid=cee6f16a-0183-4d00-ac0f-58add_ctrl -> static int add_ctrl(const char *argstr) -> 控制器设备: /dev/nvme0open(PATH_NVME_FABRICS, O_RDWR)write(fd, argstr, len) -> 写设备, 触发内核驱动处理(nvmf_dev_write) -> nqn=nqn.2014-08.nvmexpress.discovery,transport=rdma,traddr=175.17.53.73,trsvcid=4420,hostnqn=nqn.2014-08.nvmexpress:uuid:a8dce057-b5a2-492e-8da3-9cf328f401c7,hostid=a20d3ab6-2c0a-4335-8552-305 -> Jul 12 11:13:56 s63 kernel: nvme nvme0: new ctrl: NQN "nqn.2014-08.nvmexpress.discovery", addr 172.17.29.65:4420Jul 12 11:14:01 s63 systemd: Started Session 3337 of user root.read(fd, buf, BUF_SIZE)nvmf_get_log_page_discovery -> static int nvmf_get_log_page_discoverynvme_discovery_lognvme_get_lognvme_get_log13remove_ctrlnvmf_get_log_page_discovery -> static int nvmf_get_log_page_discovery -> /dev/nvme0nvme_discovery_log -> int nvme_get_log -> nvme_get_log13 -> .opcode = nvme_admin_get_log_page -> return ioctl(fd, ioctl_cmd, cmd) -> 管理命令: nvme_admin_get_log_page = 0x02enum nvme_admin_opcode nvme管理命令 -> linux/nvme.hnvme_submit_admin_passthruioctl(fd, NVME_IOCTL_ADMIN_CMD, cmd) -> 转到内核驱动处理 -> nvme_dev_ioctlremove_ctrlcase DISC_OKret = connect_ctrls(log, numrec)case DISC_NO_LOGprint_discovery_logconnect_ctrls转到内核处理:
...
文件系统fs:
.unlocked_ioctl = nvme_dev_ioctl
nvme_user_cmdcopy_from_usernvme_validate_passthru_nsidcmon.opcode = cmd.opcode;...nvme_cmd_allowedstatus = nvme_submit_user_cmdnvme_alloc_user_requestblk_mq_alloc_requestnvme_init_requestnvme_req(req)->flags |= NVME_REQ_USERCMDnvme_map_user_request -> nvme_alloc_request 需要大量参数。 将其分成两个函数以减少参数数量。 第一个保留名称 nvme_alloc_request, 而第二个名为 nvme_map_user_requestbio = req->bioctrl = nvme_req(req)->ctrlnvme_passthru_startnvme_command_effectsnvme_mpath_start_freezenvme_start_freezeet = nvme_execute_rq(req, false)status = blk_execute_rq(rq, at_head)blk_mq_insert_request -> ... -> nvme_rdma_queue_rqblk_mq_run_hw_queueblk_rq_is_poll -> false HCTX_TYPE_DEFAULTwait_for_completion_ioblk_rq_unmap_user(bio)blk_mq_free_request(req)nvme_passthru_end(ctrl, effects, cmd, ret)spdk_tgt处理查询日志页命令:#0 nvmf_ctrlr_get_log_page (req=0x2000070d4560) at ctrlr.c:2517
#1 0x00000000004d2dbc in nvmf_ctrlr_process_admin_cmd (req=0x2000070d4560) at ctrlr.c:3592
#2 0x00000000004d5365 in spdk_nvmf_request_exec (req=0x2000070d4560) at ctrlr.c:4566
#3 0x000000000050b800 in nvmf_rdma_request_process (rtransport=0xd9a440, rdma_req=0x2000070d4560) at rdma.c:2239
#4 0x000000000050ea12 in nvmf_rdma_qpair_process_pending (rtransport=0xd9a440, rqpair=0xda5ec0, drain=false) at rdma.c:3276
#5 0x00000000005120ab in nvmf_rdma_poller_poll (rtransport=0xd9a440, rpoller=0xda4e70) at rdma.c:4685
#6 0x0000000000512333 in nvmf_rdma_poll_group_poll (group=0xda4da0) at rdma.c:4767
#7 0x00000000004f4d04 in nvmf_transport_poll_group_poll (group=0xda4da0) at transport.c:715
#8 0x00000000004e6a93 in nvmf_poll_group_poll (ctx=0xd29060) at nvmf.c:70
#9 0x000000000059d238 in thread_execute_poller (thread=0xd82a90, poller=0xd82e20) at thread.c:946
#10 0x000000000059d7bb in thread_poll (thread=0xd82a90, max_msgs=0, now=514611841303047) at thread.c:1072
#11 0x000000000059da5b in spdk_thread_poll (thread=0xd82a90, max_msgs=0, now=514611841303047) at thread.c:1156
#12 0x000000000055f7de in _reactor_run (reactor=0xd29140) at reactor.c:914
#13 0x000000000055f8cd in reactor_run (arg=0xd29140) at reactor.c:952
#14 0x000000000055fd4b in spdk_reactors_start () at reactor.c:1068
#15 0x000000000055c20a in spdk_app_start (opts_user=0x7fffffffde70, start_fn=0x407c15 <nvmf_tgt_started>, arg1=0x0) at app.c:808
#16 0x0000000000407d1d in main (argc=3, argv=0x7fffffffe038) at nvmf_main.c:47
nvmf_rdma_qpair_process_pendingSTAILQ_FOREACH_SAFE nvmf_rdma_request_processspdk_nvmf_request_execnvmf_ctrlr_process_admin_cmdcase SPDK_NVME_OPC_GET_LOG_PAGEnvmf_ctrlr_get_log_pagespdk_nvmf_qpair_get_listen_trid.qpair_get_listen_trid = nvmf_rdma_qpair_get_listen_tridspdk_nvme_trid_populate_transportnvmf_get_discovery_log_pagenvmf_generate_discovery_logspdk_nvmf_subsystem_get_firstspdk_nvmf_subsystem_get_nextRB_NEXT(subsystem_tree, &tgt->subsystems, subsystem) -> 创建subsystem的时候将数据插入红黑树: RB_INSERT -> nvmf:使用 RB 树来跟踪 tgt 子系统 目前我们使用数组,这对于许多子系统来说计算成本很高,因为查找需要对子系统 nqns 进行 O(n) 字符串比较。 它的容量并不昂贵,因为它只是一个指针数组。 因此,将其从指针数组切换为 RB_HEAD,这样我们就可以将查找次数减少到 O(log n) 字符串比较。 请注意,我们仍然会为每个传输轮询组分配 spdk_nvmf_subsystem_poll_groups 数组,因为我们不想在 IO 路径中产生 RB_FIND 的额外成本nvmf_transport_listener_discovernvmf_rdma_discoverentry->trtype = SPDK_NVMF_TRTYPE_RDMA...entry->tsas.rdma.rdma_qptype = SPDK_NVMF_RDMA_QPTYPE_RELIABLE_CONNECTEDentry->tsas.rdma.rdma_cms = SPDK_NVMF_RDMA_CMS_RDMA_CMnvme_cli connect 连接SPDK_TGT流程, 创建队列与发现命令类似:
nvme_cli连接:
gdb --args nvme nvme connect -t rdma -n nvme-subsystem-name -a 172.17.29.65 -s 4421
gdb --args nvme connect -t rdma -n "nqn.2022-06.io.spdk:cnode216" -a 172.17.29.217 -s 4420
main -> handle_plugin... -> ENTRY("connect", "Connect to NVMeoF subsystem", connect_cmd)
connect_cmd -> int connectret = argconfig_parse(argc, argv, desc, command_line_options, &cfgbuild_optionsinstance = add_ctrl(argstr) -> static int add_ctrl -> 添加控制器fd = open(PATH_NVME_FABRICS, O_RDWR) -> PATH_NVME_FABRICS "/dev/nvme-fabrics"if (write(fd, argstr, len) -> nqn=nvme-subsystem-name,transport=rdma,traddr=172.17.29.65,trsvcid=4421,hostnqn=nqn.2014-08.nvmexpress:uuid:3195faad-fe20-44d5-8ae4-291d29629c89,hostid=a872f1d1-daae-4da0-be86-5a36131e640b -> [root@s63 ~]# bpftrace -e 'kprobe:nvmf_dev_write{ printf("%s\n", kstack); }' -> nvmf_dev_writelen = read(fd, buf, BUF_SIZE)case OPT_INSTANCE内核处理
static const struct file_operations nvmf_dev_fops = {.owner = THIS_MODULE,.write = nvmf_dev_write,.read = seq_read,.open = nvmf_dev_open,.release = nvmf_dev_release,
};
static ssize_t nvmf_dev_writebuf = memdup_user_nul(ubuf, count) -> 内存拷贝 -> duplicate memory region from user space and NUL-terminatenvmf_create_ctrl(struct device *dev, const char *buf) -> /sys/class/nvme-fabrics -> 参考, kernel-nvmf: nvmf_ctrl_options *opts -> 关键数据结构体 -> nvme-fabrics:添加通用 NVMe over Fabrics 库,NVMe over Fabrics 库为传输和 nvme 核心提供接口,以处理独立于底层传输的特定于结构的命令和属性,此外,fabrics 库添加了一个杂项设备 允许实际创建结构控制器的接口,因为我们不能像 PCI 情况那样自动发现它。 nvme-cli 实用程序已得到增强,可以使用此接口来支持结构连接和发现nvmf_parse_optionsrequest_module("nvme-%s", opts->transport) -> nvme-rdma.ko -> 当内核发现一个需要的module不在内核中时,会调用request_module去用户空间创建进程去加载这个缺失的module, Linux内核模块的自动加载及request_module系统调用: (&nvmf_transports_rwsem) -> nvme-fabrics:将 nvmf_transports_mutex 转换为 rwsem 互斥体可防止在创建控制器时更改传输列表,但使用普通的旧互斥体意味着它还会序列化控制器创建。 这不必要地减慢了创建多个控制器的速度 - 例如,对于 RDMA 传输,创建控制器涉及为每个 IO 队列建立一个连接,这涉及更多的网络/软件往返,因此延迟可能会变得很严重。解决此问题的最简单方法是 将互斥量更改为 rwsem,并仅在列表发生变化时保留它以进行写入。 由于我们可以在创建控制器时读取rwsem,因此我们可以并行创建多个控制器, rw_semaphore,对于无竞争的 rwsem,计数和所有者是任务在获取 rwsem 时需要接触的唯一字段。 因此,它们被放置在彼此旁边,以增加它们共享相同缓存行的机会。 在竞争的 rwsem 中,所有者可能是结构中最常访问的字段,因为持有 osq 锁的乐观等待者将在所有者上旋转。 对于嵌入式 rwsem,包含结构中的其他热字段应远离 rwsem,以减少它们共享相同缓存行而导致缓存行弹跳问题的机会, down_read()是读者用来得到读写信号量sem时调用的,如果该信号量在被写者所持有,则对该函数的调用会导致调用者的睡眠。通过该操作,多个读者可以获得读写信号量, .htmlnvmf_lookup_transporttry_module_get -> 首先判断模块module是否处于活动状态,然后通过local_inc()宏操作将模块module的引用计数加1up_read(&nvmf_transports_rwsem);nvmf_check_required_optsnvmf_check_allowed_opts -> 检查选项ctrl = ops->create_ctrl -> .create_ctrl = nvme_rdma_create_ctrl -> static struct nvme_ctrl *nvme_rdma_create_ctrl -> 创建控制器inet_pton_with_scope -> 将tgt地址和端口转为socket地址nvme_rdma_existing_controller -> 比较6元组: <Host NQN, Host ID, local address, remote address, remote port, SUBSYS NQN>nvmf_ip_options_matchINIT_DELAYED_WORK(&ctrl->reconnect_work nvme_rdma_reconnect_ctrl_work -> nvme-rdma:集中控制器设置序列,将控制器序列集中到单个例程,该例程在故障后正确清理,而不是在多个流程中具有多个外观(创建、重置、重新连接)。我们在这里还获得的一件事是理智/边界 连接回动态控制器时也会检查INIT_WORK(&ctrl->err_work, nvme_rdma_error_recovery_work)nvme_stop_keep_alive -> nvme-rdma:在错误恢复中不要完全停止控制器,通过在已经发生故障的控制器上调用 nvme_stop_ctrl 将等待扫描工作完成(仅通过识别超时到期时间,即 60 秒)。 当我们已经知道控制器发生故障时,这是不必要的cancel_delayed_work_sync(&ctrl->ka_work)flush_worknvme_rdma_teardown_io_queues(ctrl, false);nvme_unquiesce_io_queues(&ctrl->ctrl);nvme_rdma_teardown_admin_queue(ctrl, false);nvme_unquiesce_admin_queue(&ctrl->ctrl);nvme_auth_stop(&ctrl->ctrl);nvme_rdma_reconnect_or_remove -> 重连或移除queue_delayed_work(nvme_wq, &ctrl->reconnect_work -> nvme_rdma_reconnect_ctrl_worknvme_rdma_setup_ctrl...INIT_WORK(&ctrl->ctrl.reset_work, nvme_rdma_reset_ctrl_work)nvme_init_ctrl -> 初始化nvme控制器, 初始化 NVMe 控制器结构。 这需要在最早的初始化期间调用,以便我们在探测期间拥有初始化的结构, nvme:将 chardev 和 sysfs 接口移至通用代码为此,我们需要添加适当的控制器初始化例程和除 PCIe 控制器列表之外的所有控制器列表,该列表保留在 pci.c 中。 请注意,当对控制器的最后一个引用被删除时,我们会删除 sysfs 设备 - 旧代码会将其保留更长时间,这没有多大意义。这需要一个新的 ->reset_ctrl 操作来实现控制器重置,并需要一个新的 ->reset_ctrl 操作来实现控制器重置。 ->write_reg32 实现子系统重置所需的操作。 现在,我们还存储 NVMe 合规版本的缓存副本以及控制器是否连接到子系统或不在通用控制器结构中的标志INIT_LIST_HEAD(&ctrl->namespaces) -> 初始命名空间链表init_rwsem(&ctrl->namespaces_rwsem)INIT_WORK(&ctrl->scan_work, nvme_scan_work)nvme_init_non_mdts_limitsnvme_scan_ns_list -> 扫描命名空间列表: , 驱动架构分析: , nvme_reset_work, 这个函数初始化nvme盘的admin和io队列(struct nvme_queue),同时初始化nvme盘的管理队列和请求队列对应的硬件队列描述结构blk_mq_tag_set,注意:这里的请求队列结构是struct request_queue,并不是nvme盘收发命令的admin和io队列,每个nvme逻辑盘只有一个请求队列,一个该请求队列对应多个nvme盘硬件io队列。nvme逻辑盘用struct nvme_ns结构表示,该结构包含通用盘设备结构:struct gendisk, blk_mq_tag_set结构包含一个物理nvme盘硬件队列数、队列深度、io请求及处理等信息,该结构包含物理块设备所有描述信息,是块设备软件请求队列和硬件物理存储设备队列之间的纽带,建立了系统软件层面的io请求队列和物理存储设备硬件队列的映射关系。通过该结构文件系统读写操作发送的io请求最终到达物理存储设备INIT_WORK(&ctrl->async_event_work, nvme_async_event_work)INIT_WORK(&ctrl->fw_act_work, nvme_fw_act_work)INIT_WORK(&ctrl->delete_work, nvme_delete_ctrl_work)INIT_DELAYED_WORK(&ctrl->ka_work, nvme_keep_alive_work)INIT_DELAYED_WORK(&ctrl->failfast_work, nvme_failfast_work)ctrl->ka_cmdmon.opcode = nvme_admin_keep_aliveida_alloc -> idr, ida内核id机制: (&ctrl->ctrl_device)nvme:将控制器引用计数切换为使用结构设备而不是为字符设备句柄分配单独的结构设备,而是将其嵌入到结构 nvme_ctrl 中并将其用于主控制器引用计数。 这消除了双重引用计数,并为我们提供了字符设备操作的自动引用。 我们暂时将 ctrl->device 保留为指针,以避免到处更改 printks,但将来我们可以研究采用与其他子系统类似的控制器结构的消息打印助手。请注意,delete_ctrl 操作始终已经有一个引用(或者通过 sysfs 由于此更改,或者因为 /dev/nvme-fabrics 节点上的每个打开文件现在输入时都有一个引用,所以我们不需要在那里执行 except_zero 变体kobject_initdevice_pm_initctrl->device->devt = MKDEV(MAJOR(nvme_ctrl_base_chr_devt) -> 创建设备主编号, MKDEV 用于将给定的主设备号和次设备号的值组合成 dev_t 类型的设备号, 创建/dev中的设备, alloc_chrdev_region函数,来让内核自动给我们分配设备号dev_set_drvdata -> 函数用来设置device 的私有数据, 设置控制器指针dev_set_name -> 设置设备名, 如: /dev/nvme2nvme_get_ctrlcdev_init(&ctrl->cdev, &nvme_dev_fops) -> 函数操作表为: static const struct file_operations nvme_dev_fopscdev_device_add -> 添加字符设备device_addnvme_fault_inject_initnvme_mpath_init_ctrl -> 多路径, nvme-multipath:修复 ANA 状态 nvme_init_identify 的双重初始化,因此 nvme_mpath_init 可以被多次调用,因此不得覆盖可能已初始化或正在使用的字段。 当控制器初始化时,分离出一个用于基本初始化的助手,并确保 init_identify 路径不会盲目地更改正在使用的数据结构INIT_WORK(&ctrl->ana_work, nvme_ana_work)nvme_auth_init_ctrlchanged = nvme_change_ctrl_state NVME_CTRL_CONNECTINGret = nvme_rdma_setup_ctrl(ctrl, true) -> 变更: nvme-rdma:集中控制器设置序列将控制器序列集中到单个例程,该例程在故障后正确清理,而不是在多个流程中具有多个外观(创建、重置、重新连接)。 我们在这里还获得的一件事是在连接回动态控制器时进行健全性/边界检查nvme_rdma_configure_admin_queuenvme_rdma_alloc_queue(ctrl, 0, NVME_AQ_DEPTH) -> 队列深度32 -> ...queue->cm_id = rdma_create_id(&init_net, nvme_rdma_cm_handlerrdma_resolve_addr(queue->cm_id, src_addr -> 触发cm事件: RDMA_CM_EVENT_ADDR_RESOLVED -> cm_error = nvme_rdma_addr_resolved(queue)nvme_rdma_create_queue_ib -> nvme-rdma:使 nvme_rdma_[create|destroy]_queue_ib 对称,我们将引用放在销毁例程中的设备上,因此我们应该在创建例程中查找并获取引用queue->device = nvme_rdma_find_get_device(queue->cm_id)nvme_rdma_create_cq(ibdev, queue)ib_alloc_cqnvme_rdma_create_qp(queue, send_wr_factor)init_attr.event_handler = nvme_rdma_qp_eventinit_attr.sq_sig_type = IB_SIGNAL_REQ_WRinit_attr.qp_type = IB_QPT_RCrdma_create_qp(queue->cm_id, dev->pd, &init_attr)queue->rsp_ring = nvme_rdma_alloc_ring(ibdev, queue->queue_sizering = kcalloc(ib_queue_sizenvme_rdma_alloc_qeib_dma_map_single -> 将内核虚拟地址映射为 -> DMA地址dma_map_singlepages_per_mr = nvme_rdma_get_max_fr_pagesib_mr_pool_init ?NVME_RDMA_Q_TR_READYrdma_set_service_type -> 设置服务类型, Setting Type of Service (ToS), , nvme-rdma:为 rdma 传输添加 TOS 对于 RDMA 传输,TOS 是 IB QoS 的扩展,为客户端提供隔离不同类型数据的流量的能力。 RDMA CM 使用 rdma_set_service_type() 将其抽象为 ULP。 在内部,每个流量流都由一个连接来表示,该连接具有与普通连接一样的所有独立资源,并按服务类型进行区分。 换句话说,IP 对之间可以有多个 qp 连接,并且每个连接都支持唯一的服务类型。 TOS 用途之一是带宽管理,它允许为 QoS 类别设置带宽限制,例如 80% 带宽分配给 QoS 类别 A 的控制器,20% 分配给 QoS 类别 B 的控制器。 注意:除了 TOS 配置之外,还必须在目标(发送 RDMA 命令)和发起方的相关 HCA 上配置 QOS,以影响流量, 用法: nvme connect --tos=0 --transport=rdma --traddr=10.0.1.1 --nqn=test-nvmerdma_resolve_route -> 触发cm事件: RDMA_CM_EVENT_ROUTE_RESOLVED -> cm_error = nvme_rdma_route_resolved(queue)param.retry_count = 7 -> 发生错误时应在连接上重试数据传输操作的最大次数。 此设置控制发生超时时重试发送、RDMA 和原子操作的次数。 仅适用于 RDMA_PS_TCP, param.rnr_retry_count = 7(特殊值,规范中表示无限次) -> 设置连接参数, 最大重试无限次, 收到接收器未就绪 (RNR) 错误后应在连接上重试远程对等方发送操作的最大次数。 当发送请求在缓冲区已发布以接收传入数据之前到达时,会生成 RNR 错误。 仅适用于 RDMA_PS_TCP, 提供可靠、面向连接的 QP 通信,与 TCP 不同,RDMA 端口空间提供基于消息而不是流的通信ret = rdma_connect_locked(queue->cm_id, ¶m) -> 连接服务端, 在服务端tgt, 触发cm事件: RDMA_CM_EVENT_CONNECT_REQUEST -> nvmet_rdma_queue_connect(cm_id, event)nvmet_rdma_find_get_device(cm_id)nline_page_count = num_pagesndev->pd = ib_alloc_pd(ndev->device, 0) -> 分配保护域nvmet_rdma_init_srqs(ndev) ?queue = nvmet_rdma_alloc_queue(ndev, cm_id, event)ret = nvmet_sq_init(&queue->nvme_sq)nvmet_rdma_parse_cm_connect_reqINIT_WORK(&queue->release_work, nvmet_rdma_release_queue_work)nvmet_rdma_alloc_rspsnvmet_rdma_alloc_cmds ?nvmet_rdma_create_queue_ibrdma_create_qpnvmet_rdma_post_recvnvmet_rdma_cm_accept(cm_id, queue, &event->param.conn)rdma_accept(cm_id, ¶m) -> 在host和tgt端触发cm事件: RDMA_CM_EVENT_ESTABLISHEDhost: -> queue->cm_error = nvme_rdma_conn_established(queue)nvme_rdma_post_recv -> nvme_rdma_recv_donecomplete(&queue->cm_done) -> 唤醒等待的函数: nvme_rdma_wait_for_cm-------------------------------------------------tgt: -> nvmet_rdma_queue_established(queue)nvmet_rdma_handle_command(queue, cmd)nvmet_rdma_map_sgllist_add_tail(&queue->queue_list, &nvmet_rdma_queue_list)nvme_rdma_wait_for_cm(queue)set_bit(NVME_RDMA_Q_ALLOCATED, &queue->flags)...ctrl->ctrl.numa_node = ibdev_to_node(ctrl->device->dev) -> 获取ib设备numa节点T10-PI support -> nvme-rdma:添加元数据/T10-PI 支持,对于有能力的 HCA(例如 ConnectX-5/ConnectX-6),这将允许端到端保护信息直通和 NVMe over RDMA 传输验证。 元数据卸载支持是通过新的 RDMA 签名动词 API 实现的,并且为有能力的控制器启用ctrl->max_fr_pages = nvme_rdma_get_max_fr_pagesreturn min_t(u32, NVME_RDMA_MAX_SEGMENTS, max_page_list_len - 1) -> 两数取其小nvme_rdma_alloc_qenvme_alloc_admin_tag_set -> 参考: 驱动 | Linux | NVMe | 2. nvme_probe, -> ...nvme_rdma_alloc_tag_setnvme_rdma_start_io_queuesblk_mq_update_nr_hw_queuesnvme_change_ctrl_statenvme_start_ctrl(&ctrl->ctrl) -> 启动nvme控制器, struct nvme_ctrl 抽象 NVMe 设备中和 NVMe 协议相关的部分nvme_start_keep_alive -> 启动保活nvme_queue_keep_alive_work(ctrl)queue_delayed_work(nvme_wq, &ctrl->ka_work, ctrl->kato * HZ / 2) -> kato: nvme:添加保持活动支持定期保持活动是 NVMe over Fabrics 中的强制功能,在 PCIe 的 NVMe 1.2.1 中是可选功能。 此补丁添加了从主机定期发送的保持活动状态,以验证控制器是否仍然响应,反之亦然。 keep-alive 超时是用户定义的(使用 keep_alive_tmo 连接参数),默认为 5 秒。为了避免主机发送 keep-alive 与目标端 keep-alive 超时过期竞争的竞争条件,主机添加了一个宽限 向目标发布保持活动超时时,时间为 10 秒。如果保持活动失败(或超时),则会启动传输特定的错误恢复。目前仅连接 NVMe over Fabrics 以支持保持活动,但我们可以 一旦实际支持 PCIe 的控制器可用,即可轻松添加 PCIe 支持, nvme:清理 KATO 设置,根据 NVMe 基本规范,KATO 命令应以 KATO 间隔的一半发送,以正确考虑往返时间。 由于我们现在每个连接只发送一个 KATO 命令,因此我们可以轻松使用推荐值。 这还修复了 KATO 命令的请求超时与连接命令中的值不匹配的潜在问题,这可能会导致目标的虚假连接丢失static void nvme_keep_alive_workNVME_CRTL_ARRT_TBKAS , I/O completion seen, 看见IO完成 -> io完成时 nvme_complete_rq, 设置看见IO完成标记位 -> 基于流量的保持活动 (TBKAS) 允许主机和控制器在存在管理或 I/O 命令处理的情况下重新启动基于流量的保持活动计时器。 控制器对 TBKAS 位的支持在识别控制器数据结构的控制器属性中指示(参见图 275)。 如果控制器不支持基于流量的保活(TBKAS 清除为“0”),则保活功能的操作将在第 3.9.1 节中描述。 如果在保持活动超时间隔期间未处理管理命令或 I/O 命令而保持已建立的连接,则会出现基于流量的保持活动超时。 如果在保持活动超时间隔内处理管理命令或 I/O 命令,则在保持活动定时器到期时,应重新启动保持活动定时器。 如果在保持活动超时间隔内没有向控制器提交管理命令或 I/O 命令(如第 3.4.4 节中定义),则控制器可能会认为发生了保持活动超时。 如果管理命令或 I/O 命令在保持活动超时间隔内传输到控制器,则在保持活动定时器到期时,控制器应重新启动保持活动定时器。 如果主机在保持活动超时间隔内未收到任何管理命令或任何 I/O 命令的完成,则主机可能会认为发生了基于流量的保持活动超时。 如果管理命令或 I/O 命令在保持活动超时间隔内完成,则在保持活动定时器到期时,主机应重新启动保持活动定时器。 主机应在保持活动超时的一半时检查任何管理命令和 I/O 命令的命令完成队列条目,考虑到传输往返时间、传输延迟、命令处理时间和保持活动计时器粒度。 为了防止控制器检测到保持活动超时,如果在保持活动超时间隔的一半时间内没有管理命令和 I/O 命令发送到控制器,主机应发送保持活动命令,.html, Keep Alive命令(参考第5.27.1.12节)和相关功能被主机用来确定控制器是否在运行,并被控制器用来确定主机是否在运行。当主机和控制器都可以访问并能够发出或处理命令时,它们就可以运行。控制器在Identify Controller data structure 中的KAS字段中指出Keep Alive Timer的粒度(参考Figure 275blk_mq_alloc_request(ctrl->admin_q, nvme_req_op(&ctrl->ka_cmd) -> 分配KA管理命令nvme_init_request(rq, &ctrl->ka_cmd)blk_execute_rq_nowait(rq, false) -> 在队列尾部插入IOnvme_enable_aen(ctrl) -> nvme:启用aen,无论是否存在I/O队列AEN通常与I/O队列的存在无关,因此无论是否存在都启用它们。 请注意,唯一的例外是发现控制器不支持任何请求的 AEN,并且 nvme_enable_aen 将尊重该请求并返回,因此无论如何启用它仍然是安全的。请注意,即使在初始命名空间扫描之前启用 AEN 也是安全的,因为我们在 工作队列上下文nvme_queue_scan -> 参考: (ctrl)nvme_mpath_update(ctrl) -> 更新nvme多路径, 从 nvme_init_identify() 调用的 nvme_mpath_init_identify() 从 ctrl 获取新的 ANA 日志。 这对于现有命名空间以及 ctrl 启动后可能发现的那些 scan_work 拥有最新的路径状态至关重要nvme_parse_ana_log nvme_update_ana_statenvme_change_uevent(ctrl, "NVME_EVENT=connected")dev_info nvmf_ctrl_subsysnqn -> 打印info级别的调试信息module_put(ops->module) -> module_put函数功能描述:该函数的功能是将一个特定模块module的引用计数减一,这样当一个模块的引用计数因为不为0而不能从内核中卸载时,可以调用此函数一次或多次,实现对模块计数的清零,从而实现模块卸载...pr_info("no handler found for transport %s.\n"seq_file->private = ctrlspdk_tgt启动流程, 通过CM与host端建立RDMA连接
gdb调试spdk_nvme_tgt
nvmf_main.c:47
spdk_app_opts_init -> opts=0x7fffffffdee0, opts_size=224#define SET_FIELD(field, value) -> 临时定义宏SET_FIELD(enable_coredump, true) -> 设置默认选项...SET_FIELD(disable_signal_handlers, false)
gdb -> p opts
spdk_app_parse_args -> 解析参数
spdk_app_start(&opts, nvmf_tgt_started, NULL) -> spdk_app_start(struct spdk_app_opts *opts_user -> g_start_fn = start_fnapp_copy_optsspdk_log_set_print_levelapp_setup_envcalculate_mempool_sizespdk_log_openspdk_reactors_init(size_t msg_mempool_size) -> 初始化 reactor, spdk线程模型: = spdk_mempool_create(mempool_namerte_mempool_createspdk_env_get_last_coreSPDK_ENV_FOREACH_CORE -> 遍历cpuposix_memaligng_core_infos = callocspdk_thread_lib_init_ext(reactor_thread_op, reactor_thread_op_supported -> g_thread_op_fn -> g_thread_op_supported_fn -> 初始化线程库。 必须在分配任何线程之前调用一次 thread_op_fn 和 thread_op_type_supported_fn 必须同时指定或不指定 -> nvmeof_tgt: , 将g_new_thread_fn赋值为reactor_thread_op,从而实现后续以spdk_create_thread创建的逻辑层面的thread都和具体的reactor相关联, 使用SPDK lib搭建自己的NVMe-oF Target应用: = spdk_mempool_create -> 创建消息池msgpoolSPDK_ENV_FOREACH_CORE reactor_construct -> 构造reactorreactor->events = spdk_ring_createif (reactor_interrupt_init(reactor) -> 默认中断模式 -> 中断:在thd和reactor中应用fd_group,每个reactor和每个线程分配一个fd组。 同时,每个线程被视为一个中断源,注册到其相应的反应器中。 reacotr 的egrp函数是唯一等待事件的阻塞点spdk_fd_group_createreactor->resched_fd = eventfdSPDK_FD_GROUP_ADD(reactor->fgrp, reactor->resched_fd, reactor_schedule_thread_eventepoll_ctl(epfdSPDK_FD_GROUP_ADD reactor->events_fd event_queue_run_batchspdk_interrupt_mode_is_enabledreactor = spdk_reactor_get(current_core)g_scheduling_reactor = reactorspdk_cpuset_set_cpuspdk_thread_create("app_thread", &tmp_cpumask) -> 创建一个新的 SPDK 线程对象。 请注意,通过 spdk_thread_create() 创建的第一个线程将被指定为应用程序线程。 其他 SPDK 库可能会限制某些 API 只能在此应用程序线程的上下文中调用spdk_interrupt_mode_is_enabled -> 默认禁用中断g_thread_op_fn(thread, SPDK_THREAD_OP_NEW) -> reactor_thread_op_reactor_schedule_threadspdk_cpuset_zerospdk_cpuset_set_cpuspdk_cpuset_xor -> 异或, eactor:避免在intr中将线程调度到reactor,目前,spdk_thread无法在处于中断模式的reactor上执行,但spdk_thread的中断未启用。 所以避免调度 spdk_thread 就可以了dst->cpus[i] ^= src->cpus[i]spdk_cpuset_copy -> copy dst <- srcspdk_cpuset_and -> dst->cpus[i] &= src->cpus[i]spdk_cpuset_get_cpu(cpumask, core) -> return (set->cpus[cpu / 8] >> (cpu % 8)) & 1U -> 位运算evt = spdk_event_allocate(core, _schedule_thread, lw_thread, NULL)event = spdk_mempool_get(g_spdk_event_mempool)event->fn = fnlw_thread->tsc_start = spdk_get_ticks() -> lib/event: 将线程的运行时间添加到framework_get_reactors RPC的输出中,收集每个SPDK线程的运行时间并将其添加到framework_get_reactors RPC的输出中spdk_event_call(evt)rc = spdk_ring_enqueue(reactor->events, (void **)&event, 1, NULL) -> 消息事件入队rc = write(reactor->events_fd, ¬ify, sizeof(notify)) -> 默认不通知spdk_thread_send_msg(spdk_thread_get_app_thread(), bootstrap_fn, NULL)msg->fn = fnspdk_ring_enqueue(thread->messages, (void **)&msg, 1, NULL) -> 消息其实是通过spdk_ring_enqueue()放入了ring-buffer中的。在后续的poller挂在的函数中对ring-buffer中的消息进行处理, 代码分析: (thread)spdk_reactors_startreactor_run(reactor)while (1)_reactor_run(reactor)event_queue_run_batch(reactor)count = spdk_ring_dequeue(reactor->events, events, SPDK_EVENT_BATCH_SIZE)event->fn(event->arg1, event->arg2) -> _schedule_thread(void *arg1, void *arg2)spdk_thread_poll -> thread_pollmsg_queue_run_batch -> msg->fn(msg->arg) -> bootstrap_fn_nvmf_transport_create_done -> ctx->cb_fn -> _ctx->ops->create -> .create = nvmf_rdma_create,nvmf_rdma_creatertransport->event_channel = rdma_create_event_channel()rtransport->data_wr_pool = spdk_mempool_createrdma_get_devicescreate_ib_deviceibv_query_devicenvmf_rdma_is_rxe_deviceTAILQ_INSERT_TAIL(&rtransport->devices, device, link)ibv_alloc_pdspdk_rdma_create_mem_map"Create IB device xxx"rc = generate_poll_fds(rtransport);nvmf_rdma_acceptspdk_env_thread_wait_all()
nvme落盘io流程, iopath
static const struct blk_mq_ops nvme_rdma_mq_ops = {.queue_rq = nvme_rdma_queue_rq,plete = nvme_rdma_complete_rq,.init_request = nvme_rdma_init_request,.exit_request = nvme_rdma_exit_request,.init_hctx = nvme_rdma_init_hctx,.timeout = nvme_rdma_timeout,.map_queues = nvme_rdma_map_queues,.poll = nvme_rdma_poll,
};
static blk_status_t nvme_rdma_queue_rqnvme_check_ready -> 对于我们无法发送到设备的状态,默认操作是使其忙碌并在控制器状态恢复后重试。 但是,如果控制器正在删除,或者任何内容被标记为快速故障或 nvme 多路径,则会立即失败。 注意:用于初始化控制器的命令将被标记为快速故障。 注意:nvme cli/ioctl 命令被标记为故障快速req->sqe.dma = ib_dma_map_single(dev, req->sqe.dataib_dma_mapping_errorib_dma_sync_single_for_cpunvme_setup_cmdnvme_start_request(rq)nvme_rdma_map_dataib_dma_sync_single_for_devicenvme_rdma_post_send <- drivers/nvme/host/rdma.cib_post_send
参考
NVME规范2.0:
Linux内核5.10.38: .10/readme_linux_with_git_log
Nvme_Cli用户态项目: .8.1_xb/readme
SPDK项目:
晓兵
博客: | |
weixin: ssbandjl
公众号: 云原生云