第一部分 · 2 / 6
Verbs 对象模型
RDMA 的编程接口叫 Verbs,用户态实现是 libibverbs。
它不像 socket 那样只有一个文件描述符,而是一组协作的对象。gordma 的底层包正是逐一封装了它们,
所以先把这些名词认全,后面读代码就顺了。
对象全景
| 对象 | 全称 | 作用 |
|---|---|---|
Device | 设备 | 一块 RDMA 网卡(HCA / Channel Adapter),如 mlx5_0。 |
Context | 设备上下文 | 打开设备后的句柄,后续所有资源都从它派生。 |
PD | Protection Domain | 保护域,把 MR / QP 等资源归到同一组,做隔离与权限边界。 |
MR | Memory Region | 注册(pin 住)的内存区域,网卡只能访问已注册内存。返回 lkey/rkey。 |
CQ | Completion Queue | 完成队列。每个操作完成后,网卡往这里放一个完成事件(WC)。 |
QP | Queue Pair | 队列对 = 发送队列(SQ) + 接收队列(RQ)。RDMA 的「连接」端点,类比 socket。 |
AH | Address Handle | 地址句柄,UD 传输里描述对端地址。 |
CompChannel | Completion Channel | 完成通道,让你能「等待」CQ 上的事件而不必忙等轮询。 |
它们之间的派生关系大致是:Device → Context → {PD, CQ},再由 PD → {MR, QP}。
Device
└─ Context (ibv_open_device)
├─ PD (ibv_alloc_pd)
│ ├─ MR (ibv_reg_mr) ← 注册内存,得 lkey/rkey
│ └─ QP (ibv_create_qp) ← 收发端点
├─ CQ (ibv_create_cq) ← 完成队列
└─ CompChannel (可选) ← 事件驱动等待 CQ
内存为什么必须注册
这是 RDMA 编程绕不开、也最反直觉的一步。网卡要 DMA 访问主存,必须满足两个前提:
- 内存被 pin 住:操作系统不能把这页换出或迁移,否则网卡拿到的物理地址会失效。
- 网卡知道这段内存:把虚拟地址→物理地址的映射、访问权限登记进网卡。
注册(ibv_reg_mr)一次完成上述两件事,返回两把钥匙:
| 钥匙 | 用途 |
|---|---|
lkey(local key) | 本地工作请求引用自己的缓冲区时用。 |
rkey(remote key) | 授权远端对这段内存做 RDMA Read/Write,需带外发给对方。 |
代价
注册内存有成本(pin 页 + 建映射),所以高性能程序通常预先注册一块大缓冲区反复用,
而不是每次收发都注册。gordma 的高层
rdmanet 就替你维护了一池注册好的「bounce buffer」。
工作请求与完成队列
RDMA 是异步的。你不「调用一个发送函数等它返回」,而是:
- 把一个工作请求(Work Request, WR)投递(post)到 QP 的发送/接收队列。
- 网卡在后台异步执行它。
- 完成后,网卡往完成队列 CQ 里放一个完成项(Work Completion, WC)。
- 你轮询 CQ(或被 CompChannel 事件唤醒)拿到 WC,确认成功与否。
WC 里带着你 post 时填的 WRID、状态、传输字节数等,所以能把完成项和原始请求对上号。
下一步
对象里最核心、状态最复杂的是 QP。下一节专门讲 QP 与它的状态机——
这是初学者最容易卡住的地方。
gordma 教程