本文以 RC QP、verbs 接口和 mlx5 为主线分析发送和接收请求的提交流程、完成事件的数据处理过程。除特别说明外“本端”表示发起操作的一端“远端”表示 RC 连接的另一端。1. 架构下图展示了基于 Linux mlx5 驱动的 RDMA 数据收发与完成事件通知架构。整体分为用户态、内核态和 HCA 硬件三个部分以 QPQueue Pair、CQCompletion Queue和 EQEvent Queue 为核心描述 RDMA 数据请求的提交、硬件执行、完成状态记录及事件通知机制。RDMA 直接在用户态访问硬件队列从而实现了高性能数据传输。正常的数据收发和 CQ 轮询不需要内核参与内核主要负责资源管理、中断处理以及完成事件通知。消费者ConsumerRDMA 应用程序负责发送数据、接收数据及处理完成事件。通过 RDMA Verbs 接口操作 QP、CQ 等资源支持轮询和事件通知两种处理方式。libibverbs提供统一的 RDMA Verbs 用户态接口屏蔽不同 HCA 设备的实现差异。将应用的收发和完成管理请求分发给对应的设备 Provider并管理完成事件通道。mlx5 ProviderMellanox/NVIDIA HCA 的用户态驱动实现负责将 Verbs 操作转换为设备相关的队列和寄存器操作。管理用户态 QP、CQ构建 WQE并通过 DBRDoorbell Record和 UARUser Access Region 与 HCA 交互。直接访问 CQECompletion Queue Entry完成硬件执行状态的获取与解析。Linux 内核态RDMA Core / mlx5 驱动负责 RDMA 硬件资源的创建、配置和管理并建立用户态与硬件之间的资源映射。通过 MSI-X 中断和 EQ 处理 HCA 上报的完成事件并根据 CQ 标识找到对应的内核 CQ 对象。通过 Completion Channel 将完成通知传递到用户态支持应用采用事件驱动方式处理 CQ。HCA 硬件Host Channel AdapterRDMA 数据传输的硬件执行主体负责解析和执行 SQ/RQ 中的 WQE完成网络数据收发和 DMA 操作。通过 QP/QPC 管理收发队列执行状态通过 CQ/CQC 管理完成状态通过 EQ/EQC 管理异步事件与中断通知。将执行结果写入 CQE并在满足通知条件时通过 EQ 和 MSI-X 向主机上报完成事件。整体架构实现了用户态直接提交数据传输请求和获取结果、HCA 执行 RDMA 操作、内核处理异步完成通知的职责划分从而减少数据路径上的内核开销同时支持高性能轮询与中断驱动两种完成处理机制。2. 请求提交应用通过 Verbs 接口创建数据路径对象、发布 WR并从 CQ 取得执行结果。2.1 接口定义接口定义功能int ibv_post_send(struct ibv_qp *qp, struct ibv_send_wr *wr, struct ibv_send_wr **bad_wr)把一个或多个发送 WR 发布到 SQ。int ibv_post_recv(struct ibv_qp *qp, struct ibv_recv_wr *wr, struct ibv_recv_wr **bad_wr)把一个或多个接收 WR 发布到 RQ。struct ibv_comp_channel *ibv_create_comp_channel(struct ibv_context *context)为指定设备 context 创建 completion channel成功返回 channel失败返回NULL并设置errno。应用随后在创建 CQ 时传入该 channel以关联完成通知。int ibv_destroy_comp_channel(struct ibv_comp_channel *channel)销毁 completion channel关闭其 fd 并释放用户态对象。仍有关联 CQ 时返回EBUSY成功返回 0。int ibv_poll_cq(struct ibv_cq *cq, int num_entries, struct ibv_wc *wc)从 CQ 获取 WC(work completions)。int ibv_req_notify_cq(struct ibv_cq *cq, int solicited_only)为 CQ 请求一次性通知solicited_only0时任意新 CQE 均可触发非零时仅带 Solicited 标记的接收完成或错误完成触发。int ibv_get_cq_event(struct ibv_comp_channel *channel, struct ibv_cq **cq, void **cq_context)从 completion channel 获取产生完成事件的 CQ请求完成结果通过ibv_poll_cq获取。void ibv_ack_cq_events(struct ibv_cq *cq, unsigned int nevents)确认已经成功取得的完成事件。2.2 示例以下是 Verbs 流程片段省略初始化细节。步骤 1创建资源。设备列表用于选择 RDMA 设备并取得 context列表本身在打开设备后即可释放。PD 隔离访问域MR 把业务缓冲区注册到该 PD后续 SGE 使用 MR 返回的lkey。completion channel 提供事件 fdCQ 保存完成记录CQEQP 引用 PD 和 CQQP 创建后先迁移到 INIT。devicesibv_get_device_list(num_devices);contextibv_open_device(devices[0]);ibv_free_device_list(devices);pdibv_alloc_pd(context);mribv_reg_mr(pd,buffer,buffer_length,IBV_ACCESS_LOCAL_WRITE);channelibv_create_comp_channel(context);cqibv_create_cq(context,16,cq_context,channel,0);qpibv_create_qp(pd,qp_init_attr);structibv_qp_attrinit_attr{/* 将新创建的 QP 从 RESET 迁移到 INIT。 */.qp_stateIBV_QPS_INIT,/* 使用 P_Key 表的第 0 项。 */.pkey_index0,/* 指定 QP 使用的本地物理端口。 */.port_numib_port,/* 本例只使用 SEND/RECV不开放远端 RDMA/Atomic 访问。 */.qp_access_flags0,};/* 指定本次 ibv_modify_qp() 应用 init_attr 中的四个属性。 */intinit_maskIBV_QP_STATE|IBV_QP_PKEY_INDEX|IBV_QP_PORT|IBV_QP_ACCESS_FLAGS;ibv_modify_qp(qp,init_attr,init_mask);步骤 2交换参数并连接 QP。ibv_query_port()和ibv_query_gid()取得本端路径信息QP 本身提供 QPN应用还要为连接选择 PSN。两端通过 TCP、RDMA CM 或其他控制面交换 QPN、PSN、LID/GID 和 MTU。该交换不属于正常 RDMA 数据路径代码中只保留占位注释。应用根据远端信息填好rtr_attr和rts_attr依次把 RC QP 迁移到 RTR、RTS。ibv_query_port(context,ib_port,port_attr);if(gid_index0)ibv_query_gid(context,ib_port,gid_index,gid);/* 控制面交换连接参数并据此准备 rtr_attr 和 rts_attr。 */ibv_modify_qp(qp,rtr_attr,rtr_mask);ibv_modify_qp(qp,rts_attr,rts_mask);步骤 3接收和发送。ibv_req_notify_cq()在完成事件产生前 arm CQ避免应用错过完成事件通知。接收端先用ibv_post_recv()提供 RQ 缓冲区控制面确认对端 RQ 已准备好后发送端再发布 SEND。两个 post 接口返回成功只表示 WR 已发布。SEND/RECV 的执行结果仍要通过 WC 判断。ibv_req_notify_cq(cq,0);ibv_post_recv(qp,recv_wr,bad_recv_wr);/* 控制面确认对端也已经发布 RECV。 */ibv_post_send(qp,send_wr,bad_send_wr);步骤 4等待事件并轮询完成。ibv_get_cq_event()阻塞读取 completion channel返回需要检查的 CQ 和用户 context。ibv_ack_cq_events()更新用户态确认计数随后重新 arm CQ为下一次通知建立条件。ibv_poll_cq()才真正消费 CQE 并生成 WC。循环须排空 CQ按wr_id和 QP 识别请求先检查status成功后再读取opcode等字段。for(;;){if(ibv_get_cq_event(channel,event_cq,event_context)!0)break;ibv_ack_cq_events(event_cq,1);if(ibv_req_notify_cq(event_cq,0)!0)break;while((nibv_poll_cq(event_cq,wc_capacity,wc))0)process_wc(wc,n);if(n0||exchange_finished)break;}步骤 5释放资源。应用必须等预期 WC 全部返回并停止并发提交再按对象依赖关系逆序销毁 QP、CQ、channel、MR、PD 和 context。销毁 CQ 前必须确认所有已经由ibv_get_cq_event()取得的事件否则ibv_destroy_cq()可能等待未完成的事件确认。/* 仅在相关设备访问已终止后清理轮询失败不能直接视为全部完成。 */ibv_destroy_qp(qp);ibv_destroy_cq(cq);ibv_destroy_comp_channel(channel);ibv_dereg_mr(mr);ibv_dealloc_pd(pd);ibv_close_device(context);3. 发送流程3.1 提交 WQE发送过程把应用 WR 转换为 SQ WQE再通过 DBR 和 UAR 发布请求。下图聚焦 SEND、RDMA READ 和 WRITE 的 CPU 提交路径展开 WQE 编码、发布顺序与 MMIO 实现设备执行与各操作的数据流见 3.2 节。Verbs 分发与 SQ 锁共同确定请求入口和生产者顺序。ibv_post_send()最终调用_mlx5_post_send()后者持有 SQ 锁检查 opcode、队列容量、SGE 数量和 QP 能力。图中省略了mlx5_post_send()函数。mlx5_wq_overflow()先读取 head/tail接近容量上限时在 CQ 锁下重新读取完成进度失败时bad_wr标出本批第一个未接受的请求。WQE 编码把操作、地址权限和数据来源转换为 HCA 可读的描述符。_mlx5_post_send()以 16 B DS 统计长度以 64 B WQEBB 推进cur_post设备多字节字段采用大端格式。provider 另在sq.wrid[]和sq.wqe_head[]中分别保存应用的wr_id和 SQ 请求序号供 CQ 轮询时识别请求并推进 SQ tail。opcode 决定是否编码远端地址段。set_raddr_seg()为 RDMA READ 和 WRITE 编码远端地址与rkeySEND 使用远端 RQ 提供的数据目的地。inline 与非 inline 决定 payload 的保存位置。set_data_inl_seg()检查 inline 长度把 SGE 内容用memcpy()复制进主机 SQ跨 SQ 尾部时分两段复制并写入 inline 标志和长度。非 inline 的set_data_ptr_seg()编码byte_count、lkey和本地 MR 地址零长度 SGE 被跳过。control segment 使 HCA 能识别完整 WQE。opmod_idx_opcode编码 opmod、cur_post低 16 位和 mlx5 opcodeqpn_ds编码 QPN 与最终 DS 数fm_ce_se保存 fence、signaled 和 solicited 控制send_ieth()提供 immediate/invalidate 信息。启用 WQE signature 时wq_sig()调用calc_sig()对指定范围逐字节异或并取反。post_send_db()建立 WQE、DBR 与 WC MMIO 之间的发布顺序。nreq为零时直接返回WR 正确时推进sq.head再依次发布 DBR、写门铃、刷新 WC 写。Send DBR 发布 WQE 对应的生产者位置。udma_to_device_barrier()位于 WQE 写入和 DBR 更新之间x86_64 使用带memoryclobber 的空汇编即编译器屏障ARM64 使用DMB OSHST。随后把cur_post 0xffff以大端格式写入主机内存中的MLX5_SND_DBR。x86_64 的保证适用于普通 cacheable store应用使用 non-temporal store 或特殊内存类型时需在调用 verbs 前自行执行SFENCE。WC 写入前的屏障保证 DBR 先于新的 UAR 写入。mmio_wc_start()宏展开为mmio_flush_writes()在 x86_64 执行SFENCE在 ARM64 执行DSB ST把此前 DBR/既有 MMIO 访问排在新的 WC UAR 写之前。BlueFlame 通过有序 MMIO 复制直接携带 WQE。 当 BF 已启用、本批只有一个 WR、UAR 有效、inline 或prefer_bf成立且 WQE 长度符合窗口限制时调用mlx5_bf_copy()复制长度为align(size * 16, 64)。函数按 64 B 调用mmio_memcpy_x64()。x86_64 每块使用八次递增地址的mmio_write64_be()最终经 relaxed 64-bit atomic store 落到一条MOV存储指令。ARM64 进入_mmio_memcpy_x64_64b()通常使用vld4q_u64()/vst4q_u64()编译启用__ARM_FEATURE_LS64且运行时aarch64_has_ls64为真时采用__arm_st64b()ST64B目标要求 64 B 对齐。普通门铃通过一次 64-bit MMIO store 提交 control segment 的前 64 bit。 BF 条件不满足时mmio_write64_be()向bf-reg bf-offset写入门铃。mmio_write64_be()将该访问实现为atomic_store_explicit()目标类型为_Atomic(uint64_t)内存序为memory_order_relaxed。x86_64 用一条普通MOV完成 64-bit 存储。 对当前按 8 B 对齐的访问编译器通常生成MOV r/m64, r64。ARM64 的普通 64-bit 门铃写使用STR。 对相同的自然对齐 relaxed store典型映射为STR Xt, [Xn]由 64-bit 数据寄存器写入目标地址。这里是单次标量存储与 BlueFlame 64 B 复制采用的 NEONST64B分支不同。地址映射决定 MMIO 目标外围屏障保证写入顺序。 目标虚拟地址映射到设备 BAR 中的 UAR访问按该映射的内存类型进入设备写入路径。_be参数已经按设备大端格式编码访问器原样保存其位模式不再执行字节交换。relaxed store 负责这次 64-bit 写入前后的mmio_wc_start()和mmio_flush_writes()负责 WC 排序与刷新。WC 刷新使共享窗口中的门铃按临界区的执行顺序发出。 写门铃后mmio_flush_writes()在 x86_64 执行SFENCE、在 ARM64 执行DSB ST促使本 CPU 的 WC 写及时发出。共享 UAR 时刷新位于 BF 锁内防止后一 CPU 的门铃先于前一 CPU 尚未刷出的门铃到达设备随后通过bf-offset ^ bf-buf_size切换窗口。3.2 执行ibv_send_wr经 provider 翻译为设备格式的 SQ WQE。HCA 从 control segment 取得 opcode 和队列位置从 data segment 取得本地 MR 访问地址与lkey从 remote address segment 取得远端地址与rkey。MKey 将 MR 地址转换为 DMA 地址并检查长度边界和访问权限remote write、remote read 和 remote atomic 权限分别授权远端 WRITE、READ 和 Atomic。操作HCA 的业务数据访问远端 RQ 参与方式远端正常 WC发起端正常 WCSENDDMA 读本端 SGE经 RC 写入远端 RQ SGE消耗一个 RQ WQEIBV_WC_RECVIBV_WC_SENDWRITEDMA 读本端 SGE写remote_addr/rkey指定的远端 MR直接访问 MR—IBV_WC_RDMA_WRITEWRITE with immediate数据写远端 MRimmediate value 进入远端 receive WC消耗一个 RQ WQEIBV_WC_RECV_RDMA_WITH_IMMIBV_WC_RDMA_WRITEREAD读取远端 MRDMA 写回本端 SGE直接访问 MR—IBV_WC_RDMA_READAtomic对远端 8 字节位置执行原子操作DMA 写回原值直接访问 MR—IBV_WC_COMP_SWAP或IBV_WC_FETCH_ADD3.3 提交边界wr-next允许一次提交多个 WR。mlx5 在持锁区间逐项构造 WQE遇到队列溢出、SGE 超限或 opcode 超出支持范围时接口返回非零错误码并让bad_wr指向第一个尚未发布的 WR。此前成功构造的 WQE 继续执行bad_wr因而成为调用者处理剩余 WR 的起点。IBV_SEND_INLINE适用于 SEND 与 RDMA WRITE它把 payload 复制进 WQEibv_post_send()成功后即可复用原始发送缓冲区。非 inline WQE 保存 SGE 地址、长度和lkeyMR 与缓冲区的设备所有期持续到对应完成条件成立。selective signaling 通常周期性插入 signaled WR同一 SQ 上后续 signaled WR 的成功 WC 可作为此前未 signaled WR 已完成的进度点据此回收关联资源。4. 接收流程4.1 提交 WQE接收过程把描述符放入 RQ再通过 Receive DBR 发布生产者位置。下图上半部分展示 provider 的函数树下半部分独立展示报文到达后的 HCA 数据流。mlx5_post_recv()将可写缓冲区编码到固定 stride 的 RQ 槽位。 RQ 锁串行化生产者容量和 SGE 检查保证描述符能够放入当前队列。定位槽位时同时考虑环形索引和 WQE stride。 首个索引由rq.head (rq.wqe_cnt - 1)得到get_recv_wqe()按缓冲区基址、rq.offset和index rq.wqe_shift定位每接受一个 WR 后按队列掩码推进索引。mlx5_wq_overflow()必要时在 CQ 锁下复核 tailSGE 数量还受rq.qp_state_max_gs限制失败项通过bad_wr返回。data segment 保存 HCA 写入缓冲区所需的长度、key 和地址。set_data_ptr_seg()将非零长度 SGE 的byte_count、lkey和addr编成大端设备字段零长度 SGE 被跳过。实际段数少于rq.max_gs时mlx5_post_recv()直接写入零长度、MLX5_INVALID_LKEY和零地址的结束哨兵。可选 signature 覆盖描述符与队列身份。 启用wq_sig时先清零槽位并保留 signature segment完成数据段后由set_sig_seg()组合 WQE 范围、QPN 和传入索引的校验值。其calc_sig()实现是逐字节异或后取反签名用于设备格式校验独立于应用请求标识。软件保存接收请求的wr_id供完成时交还应用。 provider 把它写入rq.wrid[]。CQ 轮询根据 CQE 的 WQE counter 找回该值并推进 RQ tail硬件 RQ WQE 只保存设备所需的描述符。Receive DBR 在 WQE 完整写入后发布新进度。 成功编码至少一个 WR 后provider 推进rq.head通过 DMA 写屏障和 DBR 完成交接。屏障保证 producer index 对应的描述符先对设备可见。udma_to_device_barrier()在 x86_64 使用编译器屏障在 ARM64 使用DMB OSHST其适用内存类型与发送路径相同。随后将rq.head 0xffff转为大端写入主机内存中的MLX5_RCV_DBR。普通 RC RQ 以 DBR 结束发布。 HCA 在入站处理时依据 RQ 进度取得 WQE该路径不调用mmio_wc_start()、mmio_wc_spinlock()或mmio_flush_writes()。共享实现中的 Raw Packet/underlay QP 在尚未到 RTR 时暂缓写 DBR这项状态条件不改变普通 RC 主线。HCA 根据可用的 RQ WQE 处理接收请求。 对 SEND设备取得并消费 WQE、检查 MKey、DMA 写入接收 payload然后在满足完成条件后写 receive CQE数据写入与完成发布具有先后关系。RQ 为空时RC 返回 RNR NAK重试行为由 QP 参数控制WRITE with immediate 的数据目的地差异在 4.2 节说明。4.2 接收数据SEND 的 payload 写入 RQ WQE 指定的 SGE完成 WC 的byte_len表示实际接收长度。WRITE with immediate 也消耗一个 RQ WQE业务数据写入发送者给出的remote_addr/rkeyRQ 在该操作中提供通知所需的资源receive WC 通过IBV_WC_WITH_IMM和imm_data交付 immediate value。普通 WRITE、READ 和 Atomic 直接以远端 MR 为操作对象。RC 的 RNR 表示接收端暂时缺少可用 RQ WQE。发送端按 QP 的 RNR 重试参数再次发送重试耗尽时发起端得到IBV_WC_RNR_RETRY_EXC_ERR。接收端在消费 receive WC 后及时补充 RQ WQE可以持续提供消息接收资源。4.3 缓冲区所有权ibv_post_recv()成功后RQ WQE 引用的缓冲区进入设备所有期。HCA 完成 DMA 写并产生 CQE 后ibv_poll_cq()解析 responder CQE、恢复wr_id、推进 RQ tailreceive WC 随即把缓冲区所有权交还应用供应用处理和复用。RECV 使用注册内存 SGE 为 HCA 提供可写缓冲区每个非空 SGE 都引用具备相应访问条件的 MR。mlx5 同时接受num_sge 0的接收请求此时 WQE 不包含有效数据段并写入零长度、MLX5_INVALID_LKEY和零地址的终止项表示接收缓冲区容量为零。该请求仍占用 RQ 槽位软件保留其wr_id终止项标记数据段结束不要求 HCA 访问无效 MR。接收 WR 链也可能部分提交bad_wr指向第一个尚未发布的请求项之前的 RQ WQE 已经生效调用者从bad_wr开始处理剩余请求。零长度接收 WQE 的使用场景为 WRITE with immediate 提供接收完成资源。 该操作会消费接收端的一个 RQ WQE但业务数据直接写入发送端指定的 remote_addr/rkey不需要接收 WQE 提供 payload 缓冲区。应用通过接收完成 WC 获取 wr_id、IBV_WC_RECV_RDMA_WITH_IMM 和 imm_data。WRITE 的业务数据长度可以非零。接收零 payload 的 SEND 通知或握手消息。 零长度 SEND 和零 payload 的 SEND with immediate 均可消费该 WQE 并产生接收完成。对于 SEND with immediate应用可通过 WC 获取 imm_data无需为 immediate data 提供 SGE 缓冲区。提供一个容量为零的有效接收请求。 该 WQE 已存在于 RQ 中与 RQ 为空不同。它不能用于丢弃普通 SEND 携带的非零 payload此类消息因超出接收缓冲区容量而产生长度错误。WQE 被消费后若还需接收后续消息应用应按协议及时提交新的 Receive WQE新 WQE 仅用于匹配后续消息不会接收或恢复此前已经发生长度错误的消息。5. 完成事件下图展示 HCA、内核和应用之间的 CQ 通知交付以及应用轮询 CQ 获取 WC 的流程。应用先建立 CQ 通知再等待完成事件。 通知配置由用户态 provider 直接提交等待事件则通过 channel fd 进入内核。发布通知条件。ibv_req_notify_cq()经mlx5_arm_cq()更新主机 arm DBR再向已映射的 UAR 写入 doorbell。读取完成事件。ibv_get_cq_event()调用read(channel-fd)经ib_uverbs_comp_event_read()到达ib_uverbs_event_read()事件队列为空时wait_event_interruptible()使读取线程在ev_queue-poll_wait上等待已有事件时可直接返回。这里的 fd 属于 completion channel由 uverbs 提供文件操作并非 Linuxeventfd对象。HCA 发布请求结果与完成通知。 HCA 先通过 DMA 向 CQ Buffer 写入 CQE在 CQ 已 armed 且通知条件满足时向关联 EQ Buffer 写入携带 CQN 的 EQE再在 EQ 通知条件满足时触发 MSI-X。CQE 保存请求结果EQE 标识需要处理的 CQ一次 CQ 通知可以对应多个 CQE。内核通过 EQ 找到 CQ再经 CQ 回调找到应用使用的 channel。 IRQ 处理与延后的 tasklet 处理分属两个执行阶段。IRQ 与 EQ 的关联来自预先建立的 notifier 绑定。irq_int_handler()调用 IRQ notifier chainmlx5_eq_comp_int()从 notifier 对象取得绑定的 completion EQnext_eqe_sw()检查 EQE owner随后执行dma_rmb()再按 EQE.CQN 经mlx5_eq_cq_get()查找 CQ。用户 CQ 通过mlx5_add_cq_to_tasklet()入队并按待处理列表状态调用tasklet_schedule()请求调度IRQ 路径随后通过eq_update_ci()更新 EQ consumer index 并重新 arm EQ。该绑定不要求在所有配置下 MSI-X vector 与 EQ 都一一对应。tasklet 把 CQ 通知转换为 channel 中可读取的事件。mlx5_cq_tasklet_cb()经mlx5_ib_cq_comp()调用ib_uverbs_comp_handler()后者通过内核 CQ 回调的cq_context取得 channel 的ev_queue将用户 CQ handle 加入event_list再调用wake_up_interruptible()唤醒poll_wait上的等待线程。获取产生完成事件的 CQ。 线程被唤醒后调用ib_uverbs_event_read()读取事件同时增加已报告事件计数最后获取 CQ handle。libibverbs 根据该 handle 找到 CQ 及其应用 context调用mlx5_cq_event()推进用户态 arm 序列号再将 CQ 和 context 返回给应用。ibv_ack_cq_events()累加已确认事件计数用于 CQ 生命周期管理CQE 和 EQE 的消费进度则通过各自的 consumer 体现。应用在读取 CQ 前重新 arm使下一轮等待具备通知条件。 应用依次执行 ack、ibv_req_notify_cq()和循环ibv_poll_cq()直到 poll 返回 0。完成事件读取与资源回收。 provider 检查 CQE owner确认可消费后执行udma_from_device_barrier()再读取内容mlx5_parse_cqe()解析 WC 并推进关联队列的回收进度update_cons_index()更新主机内存中的 CQ consumer DBR。这段路径直接访问用户映射的 CQ Buffer无需进入 uverbs。获取请求完成结果。 循环轮询 CQprovider 解析 CQE 并返回 WCchannel 事件只提示检查 CQ。5.1 创建完成通道ibv_create_comp_channel()为指定 context 创建接收 CQ 通知的 completion channel。channel fd。context-cmd_fd对应打开 context 时已打开的/dev/infiniband/uverbsNioctl-write 使用RDMA_VERBS_IOCTL通过UVERBS_METHOD_INVOKE_WRITE携带IB_USER_VERBS_CMD_CREATE_COMP_CHANNEL。legacy write 与 ioctl-write 共用同一创建路径返回的channel-fd与命令 fd 分开。独立内核事件对象、匿名文件和事件队列。 内核对象属于创建它的 context匿名文件名为[infinibandevent]访问模式为O_RDONLYfd 带有O_CLOEXEC标志。事件队列保存关联 CQ 的通知等待队列支持应用通过read()、poll()等接口读取和轮询事件。channel 是 uverbs 软件资源创建它无需新建 HCA EQ。预留和安装 fd。 预留 fd、初始化对象以及向用户态复制 fd 编号创建成功后框架通过fd_install()建立进程 fd 到匿名文件的映射提交完成后 ioctl 才成功返回。文件的private_data关联内核事件对象供后续事件访问使用。同一 context 可创建多个独立的 channel每个 CQ 最多关联其中一个。 多个 CQ 可以共享同一 channel应用通过 CQ 创建时的关联关系决定通知的接收通道。创建成功后会得到一个 channel 和一个新 fd。 各 channel 拥有独立的事件队列dup()只复制 fd 引用仍共享原文件和事件队列。channel 数量取决于可用资源。 context 创建的 channel 数量通常没有上限实际受进程RLIMIT_NOFILE、系统文件资源和内存限制。完成事件只投递到 CQ 关联的 channel。ibv_create_cq()的channel参数指定 CQ 关联的 channel。CQ 已 arm 且满足通知条件时内核将通知放入该 channel 的事件队列不会向同一 context 的其他 channel 广播或自动分流。若创建 CQ 时传入NULL该 CQ 不通过 channel 交付通知应用直接轮询 CQ 获取 WC。销毁 channel 前需释放关联 CQ 的引用。refcnt记录关联 CQ 的数量应用应先销毁 CQ再销毁 channel。事件读取见 5.2 节请求结果的获取见 5.4 节。5.2 读取完成事件ibv_get_cq_event()读取 channel成功后返回有完成事件的 CQ 及其应用 context具体请求完成结果仍由 CQ 轮询获取。阻塞属性。 阻塞模式下等待事件非阻塞模式下通过errno报告EAGAIN。应用也可先用poll()或epoll()等待 fd 可读。获取产生完成事件的 CQ。 返回的 CQ 指针和cq_context标识通知对象及应用私有上下文。用户态序列号。 mlx5 provider 的arm_sn随完成事件读取递增其低两位参与后续 CQ 通知配置见 5.3 节。5.3 配置 CQ 通知ibv_req_notify_cq()配置一次性 CQ 完成通知。doorbell 编码。mlx5_arm_cq()取arm_sn低两位和 consumer index 低 24 位并按solicited_only选择 solicited 或 all-event 命令高 32 位组成 arm word低 32 位保存 CQN。最终组成 64 位 doorbell。DBR。MLX5_CQ_ARM_DB写入大端格式的 arm word记录本次 CQ arm 的序列号、消费进度和通知模式。保证 DBR 先于 UAR 写入。 该写前屏障展开为mmio_flush_writes()x86_64 执行SFENCEARM64 执行DSB ST。更新 UAR CQ doorbell。mmio_write64_be()向ctx-cq_uar_reg MLX5_CQ_DOORBELL写入大端格式的 doorbell底层atomic_store_explicit()使用memory_order_relaxed。x86_64 用 64-bitMOV写入。 指令写出已编码的 doorbell 位模式。ARM64 用STR Xt, [Xn]写入。 指令写出同一位模式。刷出 UAR CQ doorbell。 x86_64 执行SFENCEARM64 执行DSB ST。UAR 写入后执行写屏障当该 UAR 映射为 WC 时还会促使 WC 写缓冲中的内容刷新出去。5.4 轮询 CQibv_poll_cq()从用户映射的 CQ 内存取得 WC并回收关联 SQ、RQ 和 CQ 的槽位。查找有效 CQE。判定 CQE。 opcode 必须不是MLX5_CQE_INVALIDowner 必须与当前环形队列轮次匹配避免重复消费旧 CQE。DMA 屏障。udma_from_device_barrier()在 x86_64 使用LFENCE在 ARM64 使用DMB OSHLD确保判定 CQE opcode 和 owner 的操作完成后才能读取 CQE 内容。解析 CQE 并回收队列。 CQE 中的资源标识和 WQE counter 定位请求provider 从wrid[]取回应用的wr_id不同类型决定返回字段及回收对象。request CQE 回收 SQ。 WC 保留原请求的wr_id和操作类型sq.wqe_head[]保存的 SQ 请求序号用于推进 tail。READ/Atomic 还提供结果长度inline-scatter 数据需复制到请求的本地缓冲区。responder CQE 交还接收缓冲区。 普通 RC RQ 完成包含wr_id、长度、receive opcode 及 immediate/invalidate 信息并推进 RQ tail。存在 inline-scatter 时CPU 按接收数据段容量回填 payload完成复制后才交付 WC容量不足时报告长度错误。error CQE 报错并回收请求。 WC 保存映射后的status和vendor_errSQ 或 RQ 的消费进度仍会推进。错误字段的有效范围与后续处理见 5.6 节。consumer DBR 更新 CQ 消费进度。MLX5_CQ_SET_CI保存大端格式的 consumer index 低 24 位向设备归还已消费槽位completion-channel 事件确认使用独立的软件计数。轮询返回本次取得的 WC 数量或轮询错误。5.5 通知竞态可靠的事件循环采用“首次等待前 arm事件返回后确认并重新 arm最后排空 CQ”的顺序。重新 arm 位于最终空轮询之前使这个窗口内到达的完成由本次轮询消费或触发下一次事件。重新 arm 与排空 CQ 之间到达的 CQE 可能同时被本次轮询看到并产生下一次 event因此后续 event 读取成功时CQ 可能已经被前一次循环排空ibv_poll_cq()返回 0 属于正常结果。一个 event 也可能覆盖多个 CQE应用以 event 作为唤醒提示以 WC 作为请求完成的处理单位。solicited_only非零时ibv_req_notify_cq()仅对带 Solicited 标记的接收完成或错误完成请求通知其他 CQE 仍按正常规则产生。应用可用poll()或epoll()等待 channel fdfd 可读表示ev_queue中存在 CQ 事件实际请求结果由 CQ 轮询取得。5.6 错误完成错误可能发生在发布阶段或执行阶段。发布阶段的容量、参数和 opcode 检查通过 API 返回值与bad_wr报告执行阶段的本地保护错误、远端访问错误、重试耗尽等通过 WCstatus和vendor_err报告。严重错误可能使 QP 进入 error 状态。尚未完成的 WR 随后以IBV_WC_WR_FLUSH_ERR返回flush completion 表示该请求已经终止相关软件资源进入错误回收路径。处理第一个错误时应用先保留 QP、CQ 和 MR排空所有可能的 completion 并停止提交请求再按对象依赖顺序销毁。CQ overrun 是另一类资源错误HCA 产生 CQE 的速度超过软件处理速度时完成信息可能丢失关联 CQ/QP 进入错误状态。应用通过合适的 CQ 容量、消费速率和 signaling 频率控制 CQ 压力并周期性提交 signaled WR 形成回收进度和错误检查点。6. 总结用户态收发数据与轮询 CQ。 mlx5 provider 在用户态 QP/CQ 队列内存上编码 WQE、发布门铃并读取 CQEHCA 负责发送和接收数据内核负责管理资源以及通过 EQ、中断和 completion channel 交付完成事件。提交 WQE。 provider 通过屏障、DBR 及发送门铃控制 WQE 的发布顺序。操作类型决定数据落点与 RQ 用途。 SEND 消耗远端 RQ WQE 并写入其缓冲区WRITE、READ 和 Atomic 直接访问远端 MR。WRITE with immediate 的数据写入远端 MR同时消耗 RQ WQE 以产生接收完成事件零长度接收 WQE 不需要提供 payload 缓冲区。CQE 提供完成信息。 provider 根据 CQE 找到请求利用软件保存的wr_id恢复请求并推进 SQ/RQ/CQ 的消费进度。WC 的status报告执行结果。完成事件通知。 CQ 通知采用一次性 arm事件模式通过 completion channel 唤醒应用应用确认事件、重新 arm 并读取所有 CQE。一个事件可能对应多个 CQE请求完成结果以轮询得到的 WC 为准。7. 参考资料7.1 源码位置层与功能主要位置用户态示例rdma-core/libibverbs/examples/rc_pingpong.cverbs WR、WC 与公开入口rdma-core/libibverbs/verbs.h、rdma-core/libibverbs/verbs.cmlx5 provider 操作表rdma-core/providers/mlx5/mlx5.c用户态 SQ/RQ 提交与门铃rdma-core/providers/mlx5/qp.c、rdma-core/providers/mlx5/wqe.h用户态 DMA 与 WC MMIO 屏障rdma-core/util/udma_barrier.h、rdma-core/util/mmio.hcompletion channel 创建与事件读取rdma-core/libibverbs/verbs.c、cmd_write.h、cmd_fallback.c、cmd_ioctl.c用户态 CQ 轮询与 armrdma-core/providers/mlx5/cq.cmlx5_ib 用户 CQ 完成回调linux/drivers/infiniband/hw/mlx5/cq.c内核 completion channel 创建与 fd 提交linux/drivers/infiniband/core/uverbs_cmd.c、uverbs_ioctl.c、uverbs_std_types_device.c、uverbs_std_types.c、rdma_core.clinux/include/rdma/uverbs_std_types.huverbs completion eventlinux/drivers/infiniband/core/uverbs_main.c进程 fd 分配与安装linux/fs/file.cmlx5 completion EQ 与 CQ 分发linux/drivers/net/ethernet/mellanox/mlx5/core/eq.c、linux/drivers/net/ethernet/mellanox/mlx5/core/cq.c内核 EQ doorbell 与架构屏障linux/drivers/net/ethernet/mellanox/mlx5/core/lib/eq.h、linux/arch/x86/include/asm/barrier.h、linux/arch/arm64/include/asm/barrier.hmlx5 设备格式与门铃linux/include/linux/mlx5/device.h、linux/include/linux/mlx5/doorbell.h7.2 接口与规范libibverbsibv_post_send(3)、ibv_post_recv(3)rdma-core/libibverbs/man/ibv_post_send.3、rdma-core/libibverbs/man/ibv_post_recv.3说明 WR 链、bad_wr、inline 与缓冲区所有权。libibverbsibv_poll_cq(3)rdma-core/libibverbs/man/ibv_poll_cq.3说明 WC 字段、状态与轮询返回值。libibverbsibv_req_notify_cq(3)、ibv_get_cq_event(3)rdma-core/libibverbs/man/ibv_req_notify_cq.3.md、rdma-core/libibverbs/man/ibv_get_cq_event.3说明 CQ arm、事件确认及额外事件竞态。libibverbsibv_create_comp_channel(3)、ibv_destroy_comp_channel(3)rdma-core/libibverbs/man/ibv_create_comp_channel.3说明 completion channel 的创建、返回值和销毁条件。Linux Kernel DocumentationUserspace verbs accesslinux/Documentation/infiniband/user_verbs.rst说明用户态 verbs、内核资源和直接数据路径的边界。Mellanox TechnologiesMellanox Adapters Programmer’s Reference Manual (PRM), Rev 0.53rdma-core/docs/analysis/rdma-stack/Mellanox Adapters Programmer’s Reference Manual Rev 0.53.pdf第 2.4 节与第 8 章说明 HCA operation、WQ/WQE、CQ/CQE、EQ 及可靠传输完成语义。University of CambridgeC/C11 mappings to processors提供 relaxed atomic store 到 x86_64MOV与 AArch64STR的指令映射参考MMIO 顺序另依据本地访问器、屏障实现及设备接口语义。
阅读完成 · 觉得有帮助?