如果想要完全理解 RDMA 软件和硬件的工作流程,包括初始化和数据收发处理,就需要了解一个计算机系统有哪些 RDMA 相关的组件和资源。本文将深入介绍 RDMA 的基本元素,包括 WQE、QP 和 CQ 等,它们并不是孤立的,而是通过彼此协作来完成数据收发的任务。
根据 InfiniBand 协议,RDMA 的实现依赖于几个基本元素,它们的作用基本可以对应以太网方案中的描述符、收发队列和完成队列。
一、WQ 和 WQE:任务书与公文包
1.1 WQE:软件下发给硬件的任务书
工作队列元素 的作用类似于以太网方案中收发队列里的描述符。
可以认为 WQE 是一种 "任务书 ",这种任务是软件下发给硬件的。任务书中包含了软件希望硬件去做的任务类型以及有关这个任务的详细信息:
| 信息类型 | 说明 |
|---|---|
| 任务类型 | 远程读、远程写、发送还是接收等 |
| 数据内存地址 | 数据所在的起始内存地址 |
| 数据长度 | 需要传输的数据大小 |
| 访问密钥 | 内存区域的访问权限密钥 |
任务书示例:
请把以 0x20000000 为起始地址的长度 1MB 的数据
发送给对端已建立连接的节点。
硬件接到任务之后,就会:
- 通过 DMA 操作去主机内存中读取数据
- 按照协议封装数据包
- 通过物理链路发送出去
1.2 WQ:存放任务书的公文包
工作队列 类似于以太网方案中的发送 / 接收队列,就是用来存放所有 "任务书" 的 "公文包"。
特点:
| 特性 | 说明 |
|---|---|
| 容量 | 可以容纳很多 WQE |
| 组织形式 | 先进先出(FIFO)队列 |
| 管理方式 | 实际中按照环形队列的形式进行管理 |
1.3 WQ 与以太网队列对比
┌─────────────────────────────────────────────────────────────────┐
│ RDMA 架构 │
├─────────────────────────────────────────────────────────────────┤
│ 软件(RDMA 驱动程序) │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ WQ │ │
│ │ ┌────┬────┬────┐ │ │
│ │ │WQE0│WQE1│WQE2│ │ │
│ │ └────┴────┴────┘ │ │
│ └────────┬─────────┘ │
│ │ │
│ ▼ │
│ 硬件(RDMA 网卡) │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 以太网架构 │
├─────────────────────────────────────────────────────────────────┤
│ 软件(以太网卡驱动程序) │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ 队列 │ │
│ │ ┌────┬────┬────┐ │ │
│ │ │Desc0│Desc1│Desc2│ │ │
│ │ └────┴────┴────┘ │ │
│ └────────┬─────────┘ │
│ │ │
│ ▼ │
│ 硬件(以太网卡) │
└─────────────────────────────────────────────────────────────────┘
1.4 Post 操作:软件与硬件的交互
对于 WQ 这个队列:
- 软件向其中添加 WQE(即入列)
- 硬件从中取出 WQE(即出列)
这种方式保证了软件的请求会按照顺序被硬件处理。
Post 操作类型:
| 操作类型 | 支持的具体操作 | 说明 |
|---|---|---|
| Post Send | RDMA Write、RDMA Read、Send 等 | 发送类任务 |
| Post Receive | Receive | 接收类任务 |
在 RDMA 技术中,所有的通信请求(无论是读写还是收发)都要按照这种方式告知硬件。
二、QP 和 QPN:通信的基本单位
2.1 QP:Queue Pair 的概念
RDMA 提供了基于消息队列的点对点通信方式,每个应用程序都可以直接获取自己的消息,无须操作系统和软件协议栈的介入。
关键概念:
消息服务建立在通信双方(即本机应用程序和远端应用程序)之间
创建的连接通道之上。
当应用程序需要通信时,就会和对端应用程序合作,
一起创建一条连接通道,每条连接通道的两个端点是一对 Queue Pair。
QP 的定义:
Queue Pair(QP)就是一对 WQ 的意思。
2.2 QP 的构成
大部分通信都有发送和接收两个方向,需要发送队列和接收队列两种队列:
┌─────────────────────────────────┐
│ QP │
├─────────────────────────────────┤
│ ┌───────────────────────────┐ │
│ │ SQ(Send Queue) │ │ ← 发送工作队列
│ │ 存放发送任务书 │ │
│ │ (RDMA Write/Read/Send) │ │
│ └───────────────────────────┘ │
│ │
│ ┌───────────────────────────┐ │
│ │ RQ(Receive Queue) │ │ ← 接收工作队列
│ │ 存放接收任务书 │ │
│ │ (Receive) │ │
│ └───────────────────────────┘ │
└─────────────────────────────────┘
| 队列类型 | 全称 | 存放的任务类型 | 说明 |
|---|---|---|---|
| SQ | Send Queue | RDMA Write、RDMA Read、Send 等 | 发送工作队列 |
| RQ | Receive Queue | Receive | 接收工作队列 |
重要说明: SQ 和 RQ 都是 WQ。
2.3 QP 的工作原理
在一次发送和接收的流程中:
发送端:
┌────────────────────────────────────────────────────────┐
│ 软件(RDMA 驱动程序) │
│ │ │
│ │ Post Send │
│ ▼ │
│ ┌──────────────────────────────────────────────┐ │
│ │ SQ WQE 0 │ │
│ │ 请把内存中起始地址为 0x12345678、 │ │
│ │ 长度为 100 字节的数据发送给对方 │ │
│ └──────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 硬件(RDMA 网卡)────物理链路─────────────────────→ │
└────────────────────────────────────────────────────────┘
接收端:
┌────────────────────────────────────────────────────────┐
│ 硬件(RDMA 网卡)←────物理链路────────────────────── │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────┐ │
│ │ RQ WQE 0 │ │
│ │ 请把接收到的数据保存到内存中起始地址 │ │
│ │ 为 0x10000000 的地方,可存放的数据长度 │ │
│ │ 最大为 200 字节 │ │
│ └──────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 软件(RDMA 驱动程序) │
└────────────────────────────────────────────────────────┘
关键操作:
| 操作 | 位置 | 说明 |
|---|---|---|
| Post Send | 发送端 | 把表示发送任务的 WQE 放到 SQ 里 |
| Post Receive | 接收端 | 把表示接收任务的 WQE 放到 RQ 里 |
特殊场景:
如果是 RDMA Write 或者 RDMA Read 类型的任务,则只会用到 SQ,不会使用 RQ。
2.4 操作码(Opcode)
Post Send 操作对应了 Verbs API ibv_post_send。除了可以发起 Send 操作,还可以发起 RDMA Write 和 RDMA Read,各种操作的主要区别在于向 WQE 中填写的 opcode(操作码)。
操作码定义(来自 rdma-core 的 libibverbs/verbs.h):
enum ibv_wr_opcode {
IBV_WR_RDMA_WRITE, // RDMA 写操作
IBV_WR_RDMA_WRITE_WITH_IMM, // 带立即数的 RDMA 写操作
IBV_WR_SEND, // 发送操作
IBV_WR_SEND_WITH_IMM, // 带立即数的发送操作
IBV_WR_RDMA_READ, // RDMA 读操作
IBV_WR_ATOMIC_CMP_AND_SWP, // 原子比较并交换
IBV_WR_ATOMIC_FETCH_AND_ADD, // 原子获取并增加
IBV_WR_LOCAL_INV, // 本地失效
IBV_WR_BIND_MW, // 绑定内存窗口
IBV_WR_SEND_WITH_INV, // 带失效的发送操作
IBV_WR_TSO, // TCP 分段卸载
IBV_WR_DRIVER1, // 驱动自定义操作
};
2.5 QPN:QP 的唯一标识
QPN(Queue Pair Number)定义:
每个节点(比如一块 RDMA 网卡的一个网络接口)的每个 QP 都有一个唯一的编号,被称为 QPN,通过 QPN 可以唯一确定和标识一个 RDMA 节点上的 QP。
核心概念:
在 RDMA 技术中,通信的基本主体或对象是 QP,而不是节点。
对于每个节点来说:
- 每个进程都可以申请(由内核态驱动程序分配)和使用若干 QP
- 每个本地 QP 可以连接到一个远端的 QP
通信描述示例:
❌ 不够准确的描述:
"节点 A 给节点 B 发送数据"
✅ 准确的描述:
"节点 A 的 QP 2 给节点 B 的 QP 0 发送数据"
2.6 QPN 作用示意
┌─────────────────────────────────────────────────────────────────────┐
│ 节点 A │
│ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ 进程 1 │ │ 进程 2 │ │
│ │ ┌─────┐ ┌─────┐ │ │ ┌─────┐ │ │
│ │ │QP 0 │ │QP 1 │ │ │ │QP 2 │◀──────────┼────┐ │
│ │ └──┬──┘ └──┬──┘ │ │ └──┬──┘ │ │ │
│ └─────┼────────┼─────┘ └─────┼──────────────┘ │ │
│ │ │ │ │ │
│ └────────┴────────────────┘ │ │
│ │ │ │
│ ▼ │ │
│ 网卡硬件 │ │
└─────────────────────────────────────────────────────────────────────┘
│ │
│ RDMA 连接 │
└───────────────────────────────────┘
│
┌─────────────────────────────────────────────────────────────────────┐
│ 节点 B │
│ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ 进程 1 │ │ 进程 2 │ │
│ │ ┌─────┐ ┌─────┐ │ │ ┌─────┐ │ │
│ │ │QP 0 │ │QP 1 │ │ │ │... │ │ │
│ │ └──┬──┘ └──┬──┘ │ │ └─────┘ │ │
│ └─────┼────────┼─────┘ └─────────────────────┘ │
│ │ │ │
│ └────────┴───────────────────────────────────┐ │
│ │ │ │
│ ▼ │ │
│ 网卡硬件 │ │
└─────────────────────────────────────────────────────────────────────┘
▲
└───── 节点 A 的 QP 2 连接到节点 B 的 QP 0
三、CQ 和 CQN:任务完成报告
3.1 CQ 和 CQE 的概念
CQ(Completion Queue,完成队列):装载任务完成报告的容器。
CQE(Completion Queue Element,完成队列元素):队列中的每个元素。
类比关系:
| RDMA 元素 | 含义 | 以太网对应 |
|---|---|---|
| WQE | 软件下发给硬件的任务书 | 收发队列描述符 |
| CQE | 硬件完成任务后返回给软件的任务完成报告 | 完成队列描述符 |
3.2 CQE 的内容
如果 WQE 是软件下发给硬件的任务书,那么 CQE 就是硬件完成任务之后返回给软件的任务完成报告。
CQE 包含的信息:
| 信息类型 | 说明 |
|---|---|
| 任务完成状态 | 正确无误完成还是遇到错误 |
| 错误原因 | 如果遇到错误,错误的原因是什么 |
| 任务标识 | 完成的是哪个 QP 的哪个 WQE 中的任务 |
3.3 CQ 与 WQ 的关系
数据流向对比:
WQ 和 WQE(软件 → 硬件):
软件(RDMA 驱动程序)
│
│ 写入 WQE
▼
┌──────────────────┐
│ WQ │
│ ┌────┬────┬────┐ │
│ │WQE0│WQE1│WQE2│ │
│ └────┴────┴────┘ │
└────────┬─────────┘
│
▼
硬件(RDMA 网卡)
CQ 和 CQE(硬件 → 软件):
硬件(RDMA 网卡)
│
│ 写入 CQE
▼
┌──────────────────┐
│ CQ │
│ ┌────┬────┬────┐ │
│ │CQE0│CQE1│CQE2│ │
│ └────┴────┴────┘ │
└────────┬─────────┘
│
▼
软件(RDMA 驱动程序)
3.4 CQN:CQ 的唯一标识
与 QPN 用来标识 QP 类似:
每个节点的每个 CQ 都有一个唯一的编号,称为 CQN(Completion Queue Number,完成队列编号),通过 CQN 可以唯一确定和标识一个 RDMA 节点上的 CQ。
3.5 CQE 和 WQE 的对应关系
每个 CQE 都包含某个 WQE 的完成信息。
硬件填写 CQE 的过程:
1. 硬件完成任务
2. 往 CQE 中填充信息:
- 完成了哪个 QP 的 SQ 还是 RQ
- 完成了哪个 WQE
3. 选择哪个 CQ 去填充 CQE:
- 由 QP 中的对应属性(CQN)来决定
- 多个 QP 可以使用一个 CQ
示意:
┌─────────────────────────────────────────────────────────────────────┐
│ 软件(RDMA 驱动程序) │
│ │
│ 写入 WQE 读取 CQE │
│ │ ▲ │
│ ▼ │ │
│ ┌────────────────────────────────┐ ┌────────────────────────┐ │
│ │ QP 1 │ │ CQ 0 │ │
│ │ ┌────────────────────────┐ │ │ ┌────┬────┬────┐ │ │
│ │ │ SQ │ │ │ │CQE0│CQE1│CQE2│ │ │
│ │ │ ┌────────────────────┐ │ │ │ └────┴────┴────┘ │ │
│ │ │ │ SQ WQE 1 │ │ │ └────────────┬───────────┘ │
│ │ │ │ 请把内存中起始地址为│ │ │ │ │
│ │ │ │ 0x12345678、长度为 │ │ │ │ │
│ │ │ │ 100字节的数据写到 │ │ │ │ │
│ │ │ │ 对方内存起始地址为 │ │ │ │ │
│ │ │ │ 0x11111111的地方 │ │ │ │ │
│ │ │ └────────────────────┘ │ │ │ │
│ │ └────────────────────────┘ │ │ │
│ └────────────────┬───────────────┘ │ │
│ │ │ │
│ ▼ │ │
│ ┌─────────────────────────────────────────┐ │
│ │ 硬件(RDMA 网卡) │ │
│ │ │ │
│ │ 1. 读取 WQE 1 │ │
│ │ 2. 执行任务 │ │
│ │ 3. 填写 CQE 1 │ │
│ │ "已完成 QP1 的 SQ 中 WQE 1 这个任务" │ │
│ └─────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
四、WR 和 WC:用户层的映射
4.1 概念定义
| 术语 | 全称 | 含义 |
|---|---|---|
| WR | Work Request | 工作请求 |
| WC | Work Completion | 工作完成 |
4.2 WR/WC 与 WQE/CQE 的关系
核心概念:
WR 和 WC 是 WQE 和 CQE 在用户层的映射。
原因:
应用程序是通过调用 Verbs API 来完成 RDMA 通信的:
- WQE 和 CQE 本身对用户并不可见
- 它们是驱动程序中的概念
用户真正通过 API:
- 下发的是 WR
- 收到的是 WC
4.3 数据结构层面的差异
| 层面 | WR/WC | WQE/CQE |
|---|---|---|
| 可见性 | 用户可见 | 驱动程序可见 |
| 数据结构 | 对所有应用程序都一样 | 随硬件的不同而变化 |
| 操作方式 | 通过 Verbs API | 驱动程序内部处理 |
4.4 概念对应关系
┌─────────────────────────────────────────────────────────────────┐
│ 用户层 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ WR(工作请求) ──────────────▶ WC(工作完成) │
│ │ ▲ │
│ │ Verbs API │ Verbs API │
│ ▼ │ │
├─────────────────────────────────────────────────────────────────┤
│ 驱动层 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ WQE(任务书) ──────────────▶ CQE(任务完成报告) │
│ │ ▲ │
│ │ 硬件处理 │ 硬件反馈 │
│ ▼ │ │
├─────────────────────────────────────────────────────────────────┤
│ 硬件层 │
└─────────────────────────────────────────────────────────────────┘
关键点:
- WR/WC 和 WQE/CQE 是相同的概念在不同层次的体现
- 它们都是"任务书/任务完成报告"的关系
五、数据长度相关的错误事件
5.1 问题场景
如果接收端的 RQ 的 WQE 中填写的数据缓存长度小于发送端的 SQ 的 WQE 中填写的数据长度,也就是说接收端的数据缓存可能放不下对端发过来的数据,会出现什么情况?
5.2 错误处理
接收端的硬件会填写一个 CQE 到 CQ 中,向软件报告发生了错误事件。
错误码:
| 错误码 | 说明 |
|---|---|
| IBV_WC_LOC_LEN_ERR | 当接收缓存小于到来的数据长度时产生 |
官方解释(来自 RDMA Aware Networks Programming User Manual):
"This event is generated when the receive buffer is smaller than the incoming send. It is generated on the receiver side of the connection."
翻译:
当接收缓存小于到来的数据长度时,接收端会产生此事件。
六、总结:RDMA 基本元素关系图
6.1 完整的数据流
┌─────────────────────────────────────────────────────────────────────┐
│ 应用程序 │
│ │ │
│ │ Verbs API │
│ ▼ │
│ WR(工作请求) │
│ │ │
├─────────────────────────────┼───────────────────────────────────────┤
│ 驱动程序 │
│ │ │
│ ▼ │
│ WQE(任务书) │
│ │ │
│ ┌────────────────┼────────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │ QP │ │ QP │ │ QP │ │
│ │ SQ │ │ RQ │ │ ... │ │
│ └──┬──┘ └──┬──┘ └─────┘ │
│ │ │ │
├────────────┼────────────────┼──────────────────────────────────────┤
│ 硬件 │
│ │ │ │
│ └────────┬───────┘ │
│ │ │
│ ▼ │
│ 网卡执行任务 │
│ │ │
│ ▼ │
│ CQE(任务完成报告) │
│ │ │
├─────────────────────┼──────────────────────────────────────────────┤
│ 驱动程序 │
│ │ │
│ ▼ │
│ WC(工作完成) │
│ │ │
├─────────────────────┼──────────────────────────────────────────────┤
│ 应用程序 │
│ │ │
│ ▼ │
│ 处理完成结果 │
└─────────────────────────────────────────────────────────────────────┘
6.2 核心元素对比表
| 元素 | 全称 | 所在层次 | 方向 | 对应以太网概念 |
|---|---|---|---|---|
| WQE | Work Queue Element | 驱动层 | 软件 → 硬件 | 收发队列描述符 |
| WQ | Work Queue | 驱动层 | 容器 | 收发队列 |
| QP | Queue Pair | 驱动层 | 通信端点 | - |
| QPN | Queue Pair Number | 驱动层 | 标识符 | - |
| CQE | Completion Queue Element | 驱动层 | 硬件 → 软件 | 完成队列描述符 |
| CQ | Completion Queue | 驱动层 | 容器 | 完成队列 |
| CQN | Completion Queue Number | 驱动层 | 标识符 | - |
| WR | Work Request | 用户层 | 用户 → 驱动 | - |
| WC | Work Completion | 用户层 | 驱动 → 用户 | - |
6.3 关键设计理念
1. QP 是通信的基本单位
- 不是节点,而是 QP
- 准确描述:"节点 A 的 QP 2 给节点 B 的 QP 0 发送数据"
2. 任务书与完成报告的闭环
- WQE(任务书)→ 硬件处理 → CQE(完成报告)
- WR(工作请求)→ Verbs API → WC(工作完成)
3. 层次化设计
- 用户层:WR/WC(对所有应用统一)
- 驱动层:WQE/CQE(随硬件变化)
- 硬件层:执行具体操作
4. 多对多关系
- 多个 QP 可以使用同一个 CQ
- 每个进程可以使用多个 QP