3. RDMA技术基础:RDMA原理、verbs API、操作类型
3.1 核心原理:为什么RDMA能绕过CPU与内核
RDMA(Remote Direct Memory Access)的核心思想是“让网卡直接读写远端内存”。传统TCP/IP网络路径中,数据从网卡到达内核协议栈,经过多次内存拷贝、CPU中断处理,再由应用通过socket读取,延迟通常在数十微秒量级。而RDMA将数据通路从“内核—CPU—内存”中解放出来,由网卡上的专用硬件(DMA引擎)完成端到端的数据搬运。
理解RDMA的关键在于三个“绕过”:
- 绕过内核:应用通过verbs API直接注册用户态内存区域,网卡硬件可直接访问该区域,无需系统调用介入。
- 绕过CPU:数据从本地内存到远端内存的拷贝由网卡DMA完成,CPU仅在提交请求和完成轮询时参与。
- 绕过额外拷贝:数据从发送端应用缓冲区直接到达接收端应用缓冲区,中间不经过任何内核缓冲或socket缓冲。
下图展示了RDMA与传统网络在数据路径上的本质差异,以及verbs API在其中的位置:
3.2 verbs API:控制面与数据面的接口
verbs API是RDMA编程的统一接口层,类似于socket API之于TCP/IP。它分为控制面操作(管理资源)和数据面操作(收发数据)两部分。控制面负责创建QP、注册内存、建立连接;数据面负责提交WR(Work Request)和轮询CQ(Completion Queue)。
一个典型的verbs调用流程如下:
- 获取设备列表:
ibv_get_device_list()枚举可用的RDMA设备。 - 打开设备并分配保护域:
ibv_open_device()+ibv_alloc_pd(),PD用于隔离不同应用的内存和QP。 - 注册内存区域:
ibv_reg_mr()将应用缓冲区注册为MR,返回lkey和rkey。 - 创建QP:
ibv_create_qp()指定SQ和RQ深度、QP类型(RC/UC/UD)。 - 连接建立:通过
ibv_modify_qp()将QP从RESET迁移到RTS状态,需要交换QP号、LID、GID等信息(通常借助TCP带外通道)。 - 数据收发:发送端
ibv_post_send()提交WR,接收端ibv_post_recv()预先提交接收WR,然后通过ibv_poll_cq()检查完成状态。
// 核心verbs调用示例(伪代码)
struct ibv_pd *pd = ibv_alloc_pd(context);
struct ibv_mr *mr = ibv_reg_mr(pd, buf, size,
IBV_ACCESS_LOCAL_WRITE | IBV_ACCESS_REMOTE_WRITE);
struct ibv_qp *qp = ibv_create_qp(pd, &qp_init_attr);
// 发送侧:提交发送请求
struct ibv_send_wr wr = {0};
wr.opcode = IBV_WR_SEND;
wr.sg_list = &sge; // 描述本地内存地址和长度
wr.num_sge = 1;
ibv_post_send(qp, &wr, &bad_wr);
// 完成侧:轮询完成队列
struct ibv_wc wc;
while (ibv_poll_cq(cq, 1, &wc) == 0) { /* 忙等或阻塞 */ }
if (wc.status == IBV_WC_SUCCESS) { /* 发送成功 */ }
3.3 操作类型:从SEND到RDMA Read/Write
RDMA提供了多种数据传输语义,每种适用于不同场景。理解这些操作类型的差异,是后续调优的基础。
| 操作类型 | 方向 | 接收端参与 | 典型场景 |
|---|---|---|---|
| SEND / RECV | 双向 | 必须预先post RECV | 短消息、控制消息、RPC |
| RDMA WRITE | 单向(本地→远端) | 不需要(仅需rkey) | 批量数据传输、GPU显存写入 |
| RDMA READ | 单向(远端→本地) | 不需要(仅需rkey) | 拉取远端数据、GPU显存读取 |
| ATOMIC | 单向 | 不需要 | 分布式锁、计数器 |
关键区别在于:SEND/RECV需要接收端CPU参与(预先提交接收缓冲区),而RDMA WRITE/READ完全绕过接收端CPU,接收端网卡根据rkey直接读写内存。这意味着RDMA WRITE/READ可以实现真正的“零参与”数据传输,非常适合GPU显存这类需要避免CPU介入的场景。
3.4 案例:GPUDirect RDMA中的数据流
以NVIDIA GPUDirect RDMA为例,GPU显存地址通过cudaMalloc分配后,使用cudaHostRegister或GPUDirect RDMA专用API将显存映射为RDMA可访问的内存区域。此时,远端节点可以直接对GPU显存发起RDMA WRITE,数据从远端网卡直达本地GPU显存,完全绕过CPU和主机内存。
数据流路径对比:
- 传统路径:远端内存 → 远端网卡 → 本地网卡 → 主机内存 → CPU拷贝 → GPU显存(经过PCIe多次传输)
- GPUDirect RDMA路径:远端内存 → 远端网卡 → 本地网卡 → PCIe → GPU显存(单次DMA直达)
这种路径将端到端延迟从约10μs降低到2-3μs,带宽利用率提升30%-50%。在分布式深度学习训练中,梯度同步时间可缩短40%以上。
3.5 常见误区与复盘
实际上,接收端必须提前注册内存区域(MR),并将rkey告知发送端。如果接收端缓冲区未注册或已注销,发送端会收到
IBV_WC_REMOTE_INV_REQ_ERR错误。调优时必须确保接收端内存注册的生命周期覆盖整个通信过程。
QP必须经过RESET → INIT → RTR → RTS的状态迁移,每一步都需要
ibv_modify_qp()正确配置。常见错误是跳过INIT直接到RTR,导致ibv_post_send()返回EINVAL。建议封装状态管理函数,避免手工操作出错。
SEND是“推”模式,接收端必须预先post RECV,且数据长度由发送端决定;RDMA WRITE是“写”模式,接收端完全被动,数据长度由发送端WR中的sge决定。在GPU显存场景中,如果误用SEND,接收端GPU无法主动post RECV,会导致通信失败。
复盘要点:RDMA的高性能来源于“卸载”和“绕过”,但这也意味着资源管理责任前移。开发者必须自己管理内存注册、QP状态、完成队列轮询等底层细节。建议在项目初期先使用ibv_async_watch监控异步事件,并开启IBV_CQ_INIT_ATTR_MASK_FLAGS中的调试标志,快速定位状态机异常。