第一部分 · 6 / 6

一次传输的全流程

把前面所有概念串起来。这一节走一遍一次 RC Send 的完整生命周期——记住这七步, 第二部分你会看到 gordma 如何把每一步封装成 Go 方法,第三部分则会看到 perftest 工具是怎么实操的。

七步生命周期

  1. 打开设备:枚举 DeviceOpen 得到 Context
  2. 建资源:分配 PD,注册内存得到 MR(拿到 lkey/rkey),创建 CQ,再创建 QP
  3. 带外握手:通过 TCP 或 librdmacm 交换双方 QPN / PSN / GID / rkey / 远端地址。
  4. QP 状态迁移:INIT → RTR → RTS,填入对端参数。
  5. 投递工作请求:接收方先 post_recv,发送方 post_send
  6. 轮询完成队列:从 CQ 取出完成项(WC),确认操作成功。
  7. 清理:销毁 QP / CQ / MR / PD,关闭 Context。

时序图

客户端 服务端 ① 各自 open 设备 / 建资源 ③ 带外通道 (TCP / CM) 交换 QPN / PSN / GID / rkey ④ INIT → RTR → RTS(双方) ⑤ post_recv 接收方先挂好接收缓冲 ⑤ post_send 网卡硬件直送对端内存 ⑥ 轮询 CQ,各自确认完成 ✓
为什么接收方要先 post_recv 双边 Send 要求接收方预先挂好接收 WR(指向一块注册内存),发送方的数据才有落点。 若发来时对端没有可用 recv WR,会触发 RNR(Receiver Not Ready),靠重试机制兜底。

对应到 gordma 的方法

步骤底层 gordma 调用
① 打开设备GetDeviceList()dev.Open()
② 建资源ctx.AllocPD() / pd.RegMRBuffer() / ctx.CreateCQ() / pd.CreateQP()
③ 握手handshake 包(TCP)或 gordma.Dial/Listen(rdma_cm)
④ 状态迁移qp.ModifyToInit/RTR/RTS()
⑤ 投递 WRSendWR / RecvWR + post(见 postwr.go
⑥ 轮询完成CQ 轮询 → WorkCompletion(检查 Status.OK()
⑦ 清理各对象的 Close()

完成项怎么读

第 ⑥ 步拿到的每个完成项是一个扁平值类型 WorkCompletioncq.go), 关键是先看状态:

wc := /* 从 CQ 轮询得到 */
if !wc.Status.OK() {        // Status != WCSuccess
    // 失败:可能是 RNR 重试耗尽、长度错误、保护错误…
}
// wc.WRID 对回 post 时填的 id;wc.ByteLen 是收到的字节数
承上启下 第 2~6 步几乎全是样板代码,繁琐又容易出错。这正是 gordma 高层 rdmanet 包要替你做掉的事—— 你只需要 Dial / SendMsg / RecvMsg。 进入 第二部分,看 gordma 库是怎么设计的。
gordma 教程