第一部分 · 3 / 6
QP 与状态机
QP(Queue Pair)是 RDMA 的核心,相当于 socket 在 RDMA 世界里的对应物。 一个 QP 由发送队列(SQ)和接收队列(RQ)组成。但与 socket 不同,QP 要能收发,必须先经过一段 状态机迁移——这是新手最容易卡住、也最值得理解清楚的一环。
QP 是什么
你不会「往 socket 写数据」,而是往 QP 的发送队列投递工作请求,网卡异步执行。 QP 上还绑定了一个或两个 CQ(发送完成、接收完成可共用一个 CQ)。一个 RC QP 一对一连接一个对端, 一个 UD QP 则可以发往多个对端。
状态机:RESET → INIT → RTR → RTS
RESET ──▶ INIT ──▶ RTR ──▶ RTS
(Ready (Ready
To To
Receive) Send)
每一步迁移都要带上一组参数(用 ibv_modify_qp 设置)。在 gordma 里,这三步迁移就是
QP 上的三个方法,对端信息用 RCConnParams 结构体携带:
| 迁移 | 关键参数 | gordma 方法 |
|---|---|---|
| RESET → INIT | 本地端口号、访问权限(AccessFlag) |
qp.ModifyToInit(portNum, access) |
| INIT → RTR | 对端 QPN、对端起始 PSN、对端 GID/LID、路径 MTU、跳数限制 | qp.ModifyToRTR(params) |
| RTR → RTS | 本地起始 PSN、超时/重试参数 | qp.ModifyToRTS(params) |
- INIT:QP 初始化好端口和权限,但还不能收发。
- RTR(Ready to Receive):填入了对端寻址信息,可以开始接收。
- RTS(Ready to Send):填入本地序列号,可以开始发送。
RCConnParams:迁移到 RTR/RTS 需要的对端信息
gordma 把迁移所需的对端参数收拢成一个结构体(节选自 types.go):
type RCConnParams struct {
DestQPN uint32 // 对端 QP 号
DestPSN uint32 // 对端起始包序列号(RTR 的 rq_psn)
LocalPSN uint32 // 本地起始包序列号(RTS 的 sq_psn)
MTU int // 路径 MTU:256/512/1024/2048/4096
PortNum int // 本地端口号(1 起)
IsRoCE bool // 选 GID(RoCE) 还是 LID(InfiniBand) 寻址
DestLID uint16 // 对端 LID(InfiniBand)
DestGID GID // 对端 GID(RoCE)
SGIDIndex int // 本地 GID 表索引(RoCE)
HopLimit uint8 // 全局路由跳数限制,常用 1 或 64
}
先有鸡还是先有蛋
关键洞察
迁移到 RTR 需要知道对端的 QPN / PSN / GID,但这些信息没法通过 RDMA 本身交换
——因为 RDMA 通道还没建立。所以必须先用一条带外通道把这些参数交换好。
这条带外通道通常是:
- 普通 TCP:perftest 默认做法,自己开个 socket 把参数互发。gordma 的
handshake包做这件事。 - librdmacm:RDMA 连接管理器,用类似 socket 的
connect/accept流程帮你协商, 完成后 QP 直接就在 RTS。gordma 的Dial/Listen走这条路。
两种方式的细节会在 第二部分 · 两种连接方式 展开。
下一步
刚才一直说「RC QP」,但 QP 其实有不同传输类型。下一节讲 RC 与 UD 的区别,
以及双边 / 单边两类操作。
gordma 教程