某 AI 训练集群在 8 台 GPU 服务器间做 AllReduce 操作,基于 TCP 的参数同步每次迭代要 200 微秒,而改用 RDMA 后降到 20 微秒。训练时间从一周缩短到一天。RDMA 让”内存到内存”的零拷贝传输成为可能。
一、引言:RDMA——绕过远端 CPU 的零拷贝通信
在传统网络编程中,一次数据传输需要经历完整的内核协议栈:应用程序调用 send(),数据从用户缓冲区拷贝到内核空间,内核封装 TCP/IP 协议头,再通过 DMA 交给网卡发送;接收端网卡通过 DMA 将数据写入内核缓冲区,内核解析协议头、拷贝数据到用户空间,最后应用程序才能在 recv() 中读到数据。整个过程涉及两次数据拷贝和至少两次上下文切换,发送端和接收端的 CPU 都深度参与。
RDMA(Remote Direct Memory Access)彻底改变了这一范式:零拷贝(zero-copy)、内核旁路(kernel bypass)、CPU 卸载(CPU offload)三位一体,使得一台机器可以直接读写另一台机器的内存,而远端 CPU 完全不参与数据搬运。这就是”远程直接内存访问”的含义——远程(Remote)、直接(Direct)、内存访问(Memory Access)。
本章将深入 RDMA 的完整技术栈:从三种传输协议的对比,到 Verbs API 编程模型的核心抽象(PD/MR/QP/CQ/SRQ),再到 QP 状态机、RDMA CM 连接管理、单边操作的零拷贝原理,以及性能调优与动手实践。
二、RDMA 是什么?
传统网络的数据路径
在传统 TCP/IP 网络中,一次数据传输的完整路径如下:
发送端: 应用缓冲区 → [CPU 拷贝] → 内核 socket 缓冲区 → [协议栈封装] → sk_buff → [CPU 拷贝到 DMA 映射区] → NIC DMA 读取 → 网络发送
接收端: NIC DMA 写入 → [中断通知 CPU] → 内核协议栈解析 → [CPU 拷贝] → 应用缓冲区这条路径的问题:
- 两次数据拷贝:用户态→内核态、内核态→NIC DMA 缓冲区(发送方向);NIC DMA 缓冲区→内核态、内核态→用户态(接收方向)
- 上下文切换:
send()/recv()系统调用触发用户态↔内核态切换 - 协议栈处理开销:TCP/IP 协议头封装/解析、校验和计算、拥塞控制等全部由 CPU 完成
- 中断开销:接收端每个数据包(或每批数据包)都需要中断通知 CPU
RDMA 的数据路径
RDMA 将数据传输的路径缩短到极致:
发送端: 应用缓冲区(已注册 MR)→ NIC 直接 DMA 读取 → 网络发送
接收端: NIC DMA 直接写入应用缓冲区(已注册 MR)→ 完成 CQE 通知关键差异:
- 零拷贝:数据直接在应用缓冲区和 NIC 之间通过 DMA 传输,不经过内核缓冲区
- 内核旁路:数据路径完全绕过操作系统内核,不需要系统调用
- CPU 卸载:传输协议由 RNIC(RDMA NIC / RNIC)硬件实现,远端 CPU 完全不参与数据搬运
- 单边操作:RDMA Write / RDMA Read / Atomic 操作只需发起端 CPU 参与,远端 CPU 完全无感知
传统网络 vs RDMA 数据路径对比
RDMA 的”零拷贝”是真正的零拷贝——数据从发送端应用缓冲区到接收端应用缓冲区,全程不经过任何中间缓冲区。这与 sendfile() 或 splice() 的”零拷贝”不同,后者只是避免了用户空间的拷贝,数据仍然经过内核缓冲区。
单边操作:远端 CPU 完全不参与
RDMA 最革命性的特性是单边操作(one-sided operations)。在 RDMA Write、RDMA Read 和 Atomic 操作中:
- 发起端:CPU 构造 Work Request,提交到 QP 的 Send Queue
- 网络:RNIC 硬件处理传输协议
- 远端:RNIC 硬件直接读写内存,CPU 完全不参与,甚至不知道发生了什么
这意味着远端 CPU 可以继续执行其他计算任务,网络 I/O 与计算真正实现了并行。这对于分布式存储、数据库、机器学习等场景至关重要——网络延迟不再占用 CPU 周期。
三、三种 RDMA 传输
RDMA 技术有三种主流传输协议,它们在物理介质、部署成本和性能特征上各有差异,但向上都提供统一的 Verbs API 接口。
InfiniBand
InfiniBand(IB)是 RDMA 的”原生”传输协议,从物理层到传输层完全为 RDMA 设计:
- 专用网络:使用 InfiniBand 交换机和线缆,不兼容以太网
- 极致延迟:端到端延迟约 0.5~1μs,是三种传输中最低的
- 高带宽:当前 HDR(High Data Rate)可达 200Gbps,NDR 可达 400Gbps+
- 内置服务质量:支持 SL(Service Level)、VL(Virtual Lane)等 QoS 机制
- 子网管理:由 Subnet Manager(SM)管理路由和地址分配
InfiniBand 的典型部署场景是高性能计算(HPC)超算中心,如 Top500 超算中的互连网络。
RoCEv2
RoCEv2(RDMA over Converged Ethernet version 2)是目前数据中心中最广泛部署的 RDMA 传输:
- 运行在以太网之上:使用 UDP/IP 封装,可部署在标准以太网基础设施上
- 封装格式:
[Ethernet][IP][UDP][IB Transport][Payload],UDP 目标端口 4791 - 需要无损网络:依赖 PFC(Priority Flow Control)防止丢包,配合 DCQCN 拥塞控制
- 性能接近 IB:端到端延迟约 1~2μs,带宽取决于以太网速率(100Gbps/200Gbps/400Gbps)
- 部署成本远低于 IB:复用现有以太网交换机和线缆
RoCEv2 的无损网络配置是性能的关键保障。如果以太网交换机未正确配置 PFC 和 ECN,丢包会导致 RDMA 性能急剧下降——因为 RDMA 传输层没有 TCP 那样的重传机制,丢包恢复的开销远高于 TCP。
iWARP
iWARP(Internet Wide Area RDMA Protocol)将 RDMA 运行在 TCP/IP 之上:
- 运行在 TCP 之上:使用标准 TCP 连接作为传输层
- 可在任何 IP 网络上运行:包括广域网,无需特殊交换机配置
- 开销最高:TCP 协议栈的开销使得延迟和 CPU 占用高于 IB 和 RoCEv2
- 生态有限:硬件支持较少,主要在特定存储场景中使用
iWARP 的优势在于网络兼容性——它可以在路由器、防火墙等标准 IP 网络设备之间传输,不需要专用的无损以太网配置。
三种传输对比
| 特性 | InfiniBand | RoCEv2 | iWARP |
|---|---|---|---|
| 物理介质 | 专用 IB 线缆 | 标准以太网 | 任何 IP 网络 |
| 封装格式 | 原生 IB 帧 | UDP/IP + IB Transport | TCP/IP + DDP |
| 端到端延迟 | ~0.5-1μs | ~1-2μs | ~2-5μs |
| 带宽 | HDR 200Gbps, NDR 400Gbps+ | 取决于以太网(100G/200G/400G) | 取决于以太网 |
| 交换机 | 专用 IB 交换机 | 标准以太网交换机(需 PFC/ECN) | 标准以太网交换机 |
| 部署成本 | 高(专用设备) | 中(复用以太网) | 低(复用以太网) |
| 无损要求 | 原生无损 | 需配置 PFC/DCQCN | 不需要(TCP 重传) |
| 路由支持 | 子网内(SM 管理) | IP 路由 | IP 路由 |
| 生态成熟度 | HPC 领域成熟 | 数据中心主流 | 有限 |
| 典型场景 | HPC 超算 | 云计算、分布式存储 | 广域 RDMA、存储 |
三种传输协议向上都提供统一的 Verbs API——应用程序不需要为不同的传输协议编写不同的代码。选择哪种传输主要取决于基础设施条件和性能需求,而非编程接口的差异。
四、Verbs API 编程模型
Verbs API 是 RDMA 编程的核心接口,由 libibverbs 提供传输操作,librdmacm 提供连接管理。理解 Verbs 的关键在于掌握其核心抽象:Protection Domain、Memory Region、Queue Pair、Completion Queue 和 Shared Receive Queue。
核心对象关系
Protection Domain(PD):内存隔离边界
Protection Domain 是 RDMA 内存隔离的基本单位。所有 MR 和 QP 都必须关联到同一个 PD,才能互相访问:
// 创建 Protection Domainstruct ibv_pd *pd = ibv_alloc_pd(context);if (!pd) { perror("ibv_alloc_pd"); exit(1);}
// 释放 Protection Domainibv_dealloc_pd(pd);PD 的核心规则:
- 同一 PD 下的 MR 可以被同一 PD 下的 QP 访问
- 不同 PD 之间的 MR 和 QP 相互隔离
- 一个 PD 可以关联多个 MR 和多个 QP
- PD 本身不涉及数据传输,只是访问控制的边界
Memory Region(MR):注册内存供远程访问
MR 是 RDMA 编程中最关键的概念之一。应用程序必须先注册内存区域,RNIC 才能对其进行 DMA 操作:
// 注册 Memory Regionvoid *buf = malloc(BUFFER_SIZE);struct ibv_mr *mr = ibv_reg_mr(pd, buf, BUFFER_SIZE, IBV_ACCESS_LOCAL_WRITE | // 本地写权限 IBV_ACCESS_REMOTE_WRITE | // 远端写权限 IBV_ACCESS_REMOTE_READ | // 远端读权限 IBV_ACCESS_REMOTE_ATOMIC); // 远端原子操作权限if (!mr) { perror("ibv_reg_mr"); exit(1);}
// mr 包含两个关键密钥:// mr->lkey — 本地访问密钥(Local Key),提交 Work Request 时使用// mr->rkey — 远程访问密钥(Remote Key),远端进行 RDMA 操作时使用
printf("lkey: 0x%x, rkey: 0x%x\n", mr->lkey, mr->rkey);
// 注销 Memory Regionibv_dereg_mr(mr);MR 注册时内核做了什么:
- 锁定物理页面:调用
get_user_pages()锁定 MR 对应的物理页面,防止被换出 - 建立 DMA 映射:将物理地址映射到 RNIC 可访问的 DMA 地址
- 生成访问密钥:RNIC 硬件生成 lkey 和 rkey,用于后续访问控制
- 注册到 RNIC:将 MR 的虚拟地址→物理地址→DMA 地址映射关系写入 RNIC 的内存翻译表(Memory Translation Table, MTT)
MR 注册是昂贵的操作——涉及内核系统调用、页面锁定和 DMA 映射。不要频繁注册/注销 MR。最佳实践是在程序启动时注册大块内存,在整个生命周期内复用。使用 Huge Pages 可以显著降低 MR 注册的开销和 MTT 表项数量。
Queue Pair(QP):发送与接收队列
QP 是 RDMA 通信的基本端点,每个 QP 包含一个 Send Queue(SQ)和一个 Receive Queue(RQ):
// 创建 Queue Pairstruct ibv_qp_init_attr qp_init_attr = { .send_cq = cq, // 发送完成队列 .recv_cq = cq, // 接收完成队列(可与 send_cq 相同) .qp_type = IBV_QPT_RC, // 可靠连接(Reliable Connection) .cap = { .max_send_wr = 256, // Send Queue 最大 Work Request 数 .max_recv_wr = 256, // Receive Queue 最大 Work Request 数 .max_send_sge = 4, // 每个 Send WR 最大 Scatter/Gather Element 数 .max_recv_sge = 4, // 每个 Recv WR 最大 Scatter/Gather Element 数 .max_inline_data = 64, // 内联数据最大字节数 }, .sq_sig_all = 0, // 仅在 WR 中指定时才产生完成通知};
struct ibv_qp *qp = ibv_create_qp(pd, &qp_init_attr);if (!qp) { perror("ibv_create_qp"); exit(1);}QP 的类型:
| QP 类型 | 全称 | 特性 |
|---|---|---|
IBV_QPT_RC | Reliable Connection | 可靠、有序、一对一连接,最常用 |
IBV_QPT_UC | Unreliable Connection | 不可靠、有序、一对一连接 |
IBV_QPT_UD | Unreliable Datagram | 不可靠、无序、一对多(组播) |
IBV_QPT_XRC | eXtended RC | 共享 QP,多进程复用同一连接 |
QP 的两个队列:
- Send Queue(SQ):存放发送操作的 Work Request(WR),包括 Send、RDMA Write、RDMA Read、Atomic
- Receive Queue(RQ):仅存放 Receive 操作的 WR,告诉 RNIC 接收数据后写入哪个缓冲区
对于 RDMA Write、RDMA Read 和 Atomic 操作,远端不需要在 RQ 中预先 Post Receive——这些是单边操作,远端 RNIC 根据 rkey 和远程地址直接操作内存。只有 Send/Recv 双边操作需要远端预先 Post Receive。
Completion Queue(CQ):完成通知
CQ 是 RDMA 的完成通知机制。当一个 WR 执行完毕后,RNIC 会在对应的 CQ 中产生一个 Completion Queue Entry(CQE):
// 创建 Completion Queuestruct ibv_cq *cq = ibv_create_cq(context, 256, NULL, NULL, 0);if (!cq) { perror("ibv_create_cq"); exit(1);}
// 轮询 CQ 获取完成通知struct ibv_wc wc;int ne = ibv_poll_cq(cq, 1, &wc);if (ne > 0) { if (wc.status != IBV_WC_SUCCESS) { fprintf(stderr, "完成状态错误: %s\n", ibv_wc_status_str(wc.status)); } else { printf("WR 完成,opcode: %d, byte_len: %u\n", wc.opcode, wc.byte_len); // wc.wr_id 是提交 WR 时设置的用户自定义标识 }}
// 也可以使用完成通道(Completion Channel)实现事件驱动struct ibv_comp_channel *channel = ibv_create_comp_channel(context);struct ibv_cq *cq = ibv_create_cq(context, 256, NULL, channel, 0);
// 请求完成通知ibv_req_notify_cq(cq, 0);
// 等待完成事件struct ibv_cq *ev_cq;void *ev_ctx;ibv_get_cq_event(channel, &ev_cq, &ev_ctx);
// 处理完成后需要重新请求通知ibv_ack_cq_events(ev_cq, 1);ibv_req_notify_cq(cq, 0);CQ 的两种使用模式:
- 轮询模式(Polling):反复调用
ibv_poll_cq(),延迟最低但 CPU 占用 100% - 事件驱动模式(Event-driven):通过完成通道等待中断,CPU 占用低但延迟略高
高性能场景通常使用轮询模式,牺牲 CPU 占用换取最低延迟。
Shared Receive Queue(SRQ):共享接收缓冲区
在大规模连接场景中,如果每个 QP 都预分配接收缓冲区,内存消耗巨大。SRQ 允许多个 QP 共享同一个接收队列:
// 创建 Shared Receive Queuestruct ibv_srq_init_attr srq_init_attr = { .attr = { .max_wr = 1024, // 最大 Receive WR 数 .max_sge = 4, // 每个 WR 最大 SGE 数 .srq_limit = 16, // 低水位线,触发事件通知 },};
struct ibv_srq *srq = ibv_create_srq(pd, &srq_init_attr);
// 向 SRQ 投递 Receive WRstruct ibv_recv_wr wr, *bad_wr;struct ibv_sge sge;
sge.addr = (uintptr_t)buf;sge.length = BUFFER_SIZE;sge.lkey = mr->lkey;
wr.next = NULL;wr.wr_id = 1;wr.sg_list = &sge;wr.num_sge = 1;
ibv_post_srq_recv(srq, &wr, &bad_wr);
// 创建 QP 时关联 SRQstruct ibv_qp_init_attr qp_init_attr = { .srq = srq, // 关联 SRQ,QP 不再使用自己的 RQ // ... 其他属性};SRQ 的优势:
- 节省内存:N 个 QP 共享一个 SRQ,接收缓冲区总量从
N × per_qp_buffer降为total_shared_buffer - 灵活分配:接收缓冲区按需分配给活跃的 QP,而非静态预留
- 适合大规模连接:分布式存储系统中数百个连接共享少量接收缓冲区
五、QP 状态机
QP 在其生命周期中经历严格的状态转换,每个状态定义了 QP 可以执行的操作。理解 QP 状态机是 RDMA 编程的基础——状态转换错误是最常见的编程 bug 之一。
QP 状态与转换
各状态详解
RESET 状态:
- QP 刚创建时的初始状态
- 不能提交任何 WR
- 所有队列被清空
INIT 状态:
- QP 已初始化,关联了端口和 PKey
- 可以向 RQ 提交 Receive WR
- 不能向 SQ 提交 Send WR
- 不能发送或接收数据
RTR(Ready to Receive)状态:
- QP 已准备好接收数据
- 必须设置远端 QP 号、PSN、GID/LID 等对端信息
- 可以接收 Send 消息和 RDMA 操作
- 仍不能发送数据
RTS(Ready to Send)状态:
- QP 已完全就绪,可以发送和接收
- 必须设置本地 PSN、超时、重试计数等
- 可以提交所有类型的 WR(Send、RDMA Write、RDMA Read、Atomic)
SQD(Send Queue Drained)状态:
- 发送队列正在排空,等待所有已提交的 Send WR 完成
- 不接受新的 Send WR
- 接收端仍正常工作
- 通常用于修改 QP 属性而不影响接收
SQE(Send Queue Error)状态:
- 发送队列遇到错误
- 接收队列可能仍正常
- 只能转换到 ERR 状态
ERR 状态:
- QP 遇到不可恢复的错误
- 所有未完成的 WR 产生错误 CQE
- 只能转换回 RESET 状态
状态转换代码示例
// RESET → INITstruct ibv_qp_attr attr;memset(&attr, 0, sizeof(attr));attr.qp_state = IBV_QPS_INIT;attr.port_num = 1; // 物理端口编号attr.pkey_index = 0; // PKey 索引attr.qp_access_flags = IBV_ACCESS_REMOTE_WRITE | IBV_ACCESS_REMOTE_READ | IBV_ACCESS_REMOTE_ATOMIC;
if (ibv_modify_qp(qp, &attr, IBV_QP_STATE | IBV_QP_PKEY_INDEX | IBV_QP_PORT | IBV_QP_ACCESS_FLAGS)) { fprintf(stderr, "RESET → INIT 失败\n"); exit(1);}
// INIT → RTRmemset(&attr, 0, sizeof(attr));attr.qp_state = IBV_QPS_RTR;attr.path_mtu = IBV_MTU_4096; // 路径 MTUattr.dest_qp_num = remote_qpn; // 远端 QP 号attr.rq_psn = 0; // 接收端起始 PSNattr.max_dest_rd_atomic = 1; // 远端可接受的未完成 RDMA Read/Atomic 数attr.min_rnr_timer = 12; // RNR (Receiver Not Ready) 定时器
// RoCEv2 需要设置 Global Routing 信息attr.ah_attr.is_global = 1;attr.ah_attr.dlid = remote_lid; // 远端 LIDattr.ah_attr.sl = 0; // Service Levelattr.ah_attr.src_path_bits = 0;attr.ah_attr.port_num = 1;attr.ah_attr.grh.dgid = remote_gid; // 远端 GIDattr.ah_attr.grh.sgid_index = 0; // 本地 GID 索引attr.ah_attr.grh.hop_limit = 64;
if (ibv_modify_qp(qp, &attr, IBV_QP_STATE | IBV_QP_AV | IBV_QP_PATH_MTU | IBV_QP_DEST_QPN | IBV_QP_RQ_PSN | IBV_QP_MAX_DEST_RD_ATOMIC | IBV_QP_MIN_RNR_TIMER)) { fprintf(stderr, "INIT → RTR 失败\n"); exit(1);}
// RTR → RTSmemset(&attr, 0, sizeof(attr));attr.qp_state = IBV_QPS_RTS;attr.timeout = 14; // 重传超时(4.096μs × 2^14 ≈ 67ms)attr.retry_cnt = 7; // 重传次数attr.rnr_retry = 7; // RNR 重试次数attr.sq_psn = 0; // 发送端起始 PSNattr.max_rd_atomic = 1; // 本端可发出的未完成 RDMA Read/Atomic 数
if (ibv_modify_qp(qp, &attr, IBV_QP_STATE | IBV_QP_TIMEOUT | IBV_QP_RETRY_CNT | IBV_QP_RNR_RETRY | IBV_QP_SQ_PSN | IBV_QP_MAX_QP_RD_ATOMIC)) { fprintf(stderr, "RTR → RTS 失败\n"); exit(1);}QP 状态转换的顺序是严格的:RESET → INIT → RTR → RTS。跳过任何状态都会导致 ibv_modify_qp() 返回错误。此外,INIT → RTR 和 RTR → RTS 的属性设置必须完整——遗漏必需的属性标志也会导致失败。这是 RDMA 编程中最常见的错误来源之一。
六、RDMA CM:连接管理
手动交换 QP 信息(QPN、PSN、GID、LID)并调用 ibv_modify_qp() 进行状态转换非常繁琐。RDMA CM(Connection Manager)库 librdmacm 封装了这一过程,提供了类似 socket API 的连接管理接口。
RDMA CM 核心 API
| 函数 | 作用 |
|---|---|
rdma_create_id() | 创建 RDMA CM 标识符 |
rdma_bind_addr() | 绑定地址 |
rdma_listen() | 监听连接 |
rdma_get_request() | 获取连接请求 |
rdma_accept() | 接受连接 |
rdma_connect() | 发起连接 |
rdma_disconnect() | 断开连接 |
rdma_destroy_id() | 销毁 CM 标识符 |
服务端代码骨架
#include <rdma/rdma_cma.h>#include <stdio.h>#include <stdlib.h>#include <string.h>
#define BUFFER_SIZE 4096
struct context { struct ibv_pd *pd; struct ibv_mr *mr; struct ibv_cq *cq; struct ibv_qp *qp; char *buf;};
int main(int argc, char **argv){ struct rdma_cm_id *listen_id, *conn_id; struct rdma_event_channel *ec; struct rdma_cm_event *event; struct context ctx;
// 1. 创建事件通道和监听 ID ec = rdma_create_event_channel(); rdma_create_id(ec, &listen_id, NULL, RDMA_PS_TCP);
// 2. 绑定地址并监听 struct sockaddr_in addr; memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(18515); // 监听端口 addr.sin_addr.s_addr = htonl(INADDR_ANY);
rdma_bind_addr(listen_id, (struct sockaddr *)&addr); rdma_listen(listen_id, 1); printf("服务端监听端口 %d\n", 18515);
// 3. 等待连接请求 rdma_get_cm_event(ec, &event); if (event->event != RDMA_CM_EVENT_CONNECT_REQUEST) { fprintf(stderr, "意外事件: %s\n", rdma_event_str(event->event)); exit(1); } conn_id = event->id; rdma_ack_cm_event(event);
// 4. 在新连接上创建 PD、MR、CQ、QP ctx.pd = ibv_alloc_pd(conn_id->verbs); ctx.buf = malloc(BUFFER_SIZE); ctx.mr = ibv_reg_mr(ctx.pd, ctx.buf, BUFFER_SIZE, IBV_ACCESS_LOCAL_WRITE | IBV_ACCESS_REMOTE_WRITE | IBV_ACCESS_REMOTE_READ); ctx.cq = ibv_create_cq(conn_id->verbs, 16, NULL, NULL, 0);
struct ibv_qp_init_attr qp_attr; memset(&qp_attr, 0, sizeof(qp_attr)); qp_attr.send_cq = ctx.cq; qp_attr.recv_cq = ctx.cq; qp_attr.qp_type = IBV_QPT_RC; qp_attr.cap.max_send_wr = 16; qp_attr.cap.max_recv_wr = 16; qp_attr.cap.max_send_sge = 1; qp_attr.cap.max_recv_sge = 1;
// RDMA CM 自动处理 QP 创建和状态转换 rdma_create_qp(conn_id, ctx.pd, &qp_attr); ctx.qp = conn_id->qp;
// 5. 接受连接 struct rdma_conn_param conn_param; memset(&conn_param, 0, sizeof(conn_param)); conn_param.private_data = &ctx.mr->rkey; // 可传递私有数据 conn_param.private_data_len = sizeof(ctx.mr->rkey); rdma_accept(conn_id, &conn_param);
// 6. 等待建立完成 rdma_get_cm_event(ec, &event); rdma_ack_cm_event(event);
printf("连接建立成功!\n");
// ... 数据传输 ...
// 7. 清理 rdma_disconnect(conn_id); ibv_destroy_qp(ctx.qp); ibv_dereg_mr(ctx.mr); ibv_destroy_cq(ctx.cq); ibv_dealloc_pd(ctx.pd); free(ctx.buf); rdma_destroy_id(conn_id); rdma_destroy_id(listen_id); rdma_destroy_event_channel(ec);
return 0;}客户端代码骨架
#include <rdma/rdma_cma.h>#include <stdio.h>#include <stdlib.h>#include <string.h>
#define BUFFER_SIZE 4096
int main(int argc, char **argv){ struct rdma_cm_id *conn_id; struct rdma_event_channel *ec; struct rdma_cm_event *event;
// 1. 创建事件通道和连接 ID ec = rdma_create_event_channel(); rdma_create_id(ec, &conn_id, NULL, RDMA_PS_TCP);
// 2. 解析服务端地址并连接 struct sockaddr_in addr; memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(18515); inet_pton(AF_INET, argv[1], &addr.sin_addr);
rdma_resolve_addr(conn_id, NULL, (struct sockaddr *)&addr, 2000);
// 3. 等待地址解析完成 rdma_get_cm_event(ec, &event); rdma_ack_cm_event(event);
// 4. 等待路由解析完成 rdma_get_cm_event(ec, &event); rdma_ack_cm_event(event);
// 5. 创建 PD、MR、CQ、QP(与服务端类似) struct ibv_pd *pd = ibv_alloc_pd(conn_id->verbs); char *buf = malloc(BUFFER_SIZE); struct ibv_mr *mr = ibv_reg_mr(pd, buf, BUFFER_SIZE, IBV_ACCESS_LOCAL_WRITE | IBV_ACCESS_REMOTE_WRITE | IBV_ACCESS_REMOTE_READ); struct ibv_cq *cq = ibv_create_cq(conn_id->verbs, 16, NULL, NULL, 0);
struct ibv_qp_init_attr qp_attr; memset(&qp_attr, 0, sizeof(qp_attr)); qp_attr.send_cq = cq; qp_attr.recv_cq = cq; qp_attr.qp_type = IBV_QPT_RC; qp_attr.cap.max_send_wr = 16; qp_attr.cap.max_recv_wr = 16; qp_attr.cap.max_send_sge = 1; qp_attr.cap.max_recv_sge = 1;
rdma_create_qp(conn_id, pd, &qp_attr);
// 6. 发起连接 struct rdma_conn_param conn_param; memset(&conn_param, 0, sizeof(conn_param)); rdma_connect(conn_id, &conn_param);
// 7. 等待连接建立 rdma_get_cm_event(ec, &event); if (event->event != RDMA_CM_EVENT_ESTABLISHED) { fprintf(stderr, "连接失败: %s\n", rdma_event_str(event->event)); exit(1); } rdma_ack_cm_event(event);
printf("连接建立成功!\n");
// ... 数据传输 ...
// 8. 清理 rdma_disconnect(conn_id); rdma_destroy_qp(conn_id); ibv_dereg_mr(mr); ibv_destroy_cq(cq); ibv_dealloc_pd(pd); free(buf); rdma_destroy_id(conn_id); rdma_destroy_event_channel(ec);
return 0;}RDMA CM 连接建立流程
RDMA CM 在内部自动完成了 QP 的创建和 RESET → INIT → RTR → RTS 状态转换,以及 QP 信息的交换(QPN、PSN、GID/LID)。这大大简化了 RDMA 编程的复杂度。但在理解底层原理时,仍需掌握手动 ibv_modify_qp() 的流程。
七、数据传输操作
RDMA 支持两大类数据传输操作:双边操作(Send/Recv)和单边操作(RDMA Write/RDMA Read/Atomic)。
Send / Recv:双边操作
Send/Recv 是最基本的 RDMA 通信方式,类似传统 socket 的 send/recv,但绕过了内核:
- Send 端:向 SQ 提交 Send WR,数据从本地 MR 发送
- Recv 端:必须预先向 RQ 提交 Receive WR,指定接收缓冲区
- 双方 CPU 都参与:发送端 CPU 提交 WR,接收端 CPU 必须预先 Post Receive
// 接收端:预先 Post Receivestruct ibv_recv_wr recv_wr, *bad_recv_wr;struct ibv_sge recv_sge;
recv_sge.addr = (uintptr_t)recv_buf;recv_sge.length = BUFFER_SIZE;recv_sge.lkey = recv_mr->lkey;
recv_wr.wr_id = 1;recv_wr.next = NULL;recv_wr.sg_list = &recv_sge;recv_wr.num_sge = 1;
ibv_post_recv(qp, &recv_wr, &bad_recv_wr);
// 发送端:Post Sendstruct ibv_send_wr send_wr, *bad_send_wr;struct ibv_sge send_sge;
send_sge.addr = (uintptr_t)send_buf;send_sge.length = msg_size;send_sge.lkey = send_mr->lkey;
send_wr.wr_id = 2;send_wr.next = NULL;send_wr.sg_list = &send_sge;send_wr.num_sge = 1;send_wr.opcode = IBV_WR_SEND;send_wr.send_flags = IBV_SEND_SIGNALED; // 请求完成通知
ibv_post_send(qp, &send_wr, &bad_send_wr);如果接收端没有预先 Post Receive,发送端的 Send WR 会触发 RNR(Receiver Not Ready)错误。RNIC 会根据 rnr_retry 和 min_rnr_timer 进行重试,超过重试次数后 QP 进入 ERR 状态。这是 RDMA 编程中另一个常见错误。
RDMA Write:单边写操作
RDMA Write 是最常用的单边操作——发起端直接将数据写入远端内存,远端 CPU 完全不参与:
// RDMA Write 示例// 前提:已通过连接建立交换了远端的 rkey 和远程地址
struct ibv_send_wr wr, *bad_wr;struct ibv_sge sge;
// 本地数据源sge.addr = (uintptr_t)local_buf; // 本地 MR 缓冲区地址sge.length = data_len; // 要写入的数据长度sge.lkey = local_mr->lkey; // 本地 MR 的 lkey
memset(&wr, 0, sizeof(wr));wr.wr_id = 100;wr.next = NULL;wr.sg_list = &sge;wr.num_sge = 1;wr.opcode = IBV_WR_RDMA_WRITE; // RDMA Write 操作wr.send_flags = IBV_SEND_SIGNALED;wr.wr.rdma.remote_addr = remote_addr; // 远端内存地址wr.wr.rdma.rkey = remote_rkey; // 远端 MR 的 rkey
if (ibv_post_send(qp, &wr, &bad_wr)) { fprintf(stderr, "RDMA Write 提交失败\n"); exit(1);}
// 等待完成struct ibv_wc wc;while (ibv_poll_cq(cq, 1, &wc) == 0);if (wc.status != IBV_WC_SUCCESS) { fprintf(stderr, "RDMA Write 失败: %s\n", ibv_wc_status_str(wc.status));}RDMA Write 的零拷贝原理:
- 发送端 CPU 调用
ibv_post_send(),将 WR 提交到 QP 的 SQ - 发送端 RNIC 通过 DMA 从本地 MR 读取数据
- RNIC 封装传输协议头,通过物理链路发送
- 接收端 RNIC 收到数据后,根据 rkey 查找 MR,验证访问权限
- 接收端 RNIC 通过 DMA 将数据直接写入
remote_addr指定的内存位置 - 接收端 CPU 全程不参与——甚至不知道数据被写入了
RDMA Write with Immediate Data
RDMA Write 还可以携带 4 字节的 Immediate Data:
wr.opcode = IBV_WR_RDMA_WRITE_WITH_IMM;wr.imm_data = htonl(0x12345678); // 4 字节立即数// 其余字段与 RDMA Write 相同与普通 RDMA Write 的区别:
- 普通 RDMA Write:远端不产生 CQE,远端 CPU 完全无感知
- RDMA Write with Imm:远端 RQ 产生 CQE(消耗一个预 Post 的 Receive WR),远端 CPU 可以通过 CQE 得到通知
这解决了”远端如何知道数据已到达”的问题——用 Immediate Data 携带元数据(如消息长度、操作类型),远端 CPU 通过 CQE 事件感知数据到达。
RDMA Read:单边读操作
RDMA Read 允许发起端直接从远端内存读取数据:
// RDMA Read 示例struct ibv_send_wr wr, *bad_wr;struct ibv_sge sge;
// 本地目标缓冲区(数据将读到这里)sge.addr = (uintptr_t)local_buf; // 本地 MR 缓冲区地址sge.length = data_len; // 要读取的数据长度sge.lkey = local_mr->lkey; // 本地 MR 的 lkey
memset(&wr, 0, sizeof(wr));wr.wr_id = 200;wr.next = NULL;wr.sg_list = &sge;wr.num_sge = 1;wr.opcode = IBV_WR_RDMA_READ; // RDMA Read 操作wr.send_flags = IBV_SEND_SIGNALED;wr.wr.rdma.remote_addr = remote_addr; // 远端内存地址wr.wr.rdma.rkey = remote_rkey; // 远端 MR 的 rkey
if (ibv_post_send(qp, &wr, &bad_wr)) { fprintf(stderr, "RDMA Read 提交失败\n"); exit(1);}
// 等待完成struct ibv_wc wc;while (ibv_poll_cq(cq, 1, &wc) == 0);if (wc.status != IBV_WC_SUCCESS) { fprintf(stderr, "RDMA Read 失败: %s\n", ibv_wc_status_str(wc.status));}
printf("成功从远端读取 %u 字节\n", wc.byte_len);RDMA Read 的典型应用场景:
- 分布式存储:客户端从存储节点读取数据块
- RPC 框架:服务端读取客户端请求中的大块参数
- 分布式数据库:节点间读取远程数据页
RDMA Read 的吞吐量受 max_rd_atomic 参数限制——一个 QP 同时在途的 RDMA Read/Atomic 请求数不能超过此值。默认值通常为 1~4,需要根据应用场景调整。增大此值可以提高吞吐,但会消耗更多 RNIC 资源。
Atomic 操作:远程原子操作
RDMA Atomic 操作在远端内存上执行原子性的读-修改-写操作:
// Compare and Swap (CAS)struct ibv_send_wr wr, *bad_wr;struct ibv_sge sge;
sge.addr = (uintptr_t)&local_result; // 存放 CAS 返回的原始值sge.length = sizeof(uint64_t);sge.lkey = local_mr->lkey;
memset(&wr, 0, sizeof(wr));wr.wr_id = 300;wr.next = NULL;wr.sg_list = &sge;wr.num_sge = 1;wr.opcode = IBV_WR_ATOMIC_CMP_AND_SWP;wr.send_flags = IBV_SEND_SIGNALED;wr.wr.atomic.remote_addr = remote_addr;wr.wr.atomic.rkey = remote_rkey;wr.wr.atomic.compare_add = expected_value; // 期望值wr.wr.atomic.swap = new_value; // 新值
ibv_post_send(qp, &wr, &bad_wr);
// Fetch and Addmemset(&wr, 0, sizeof(wr));wr.wr_id = 301;wr.next = NULL;wr.sg_list = &sge;wr.num_sge = 1;wr.opcode = IBV_WR_ATOMIC_FETCH_AND_ADD;wr.send_flags = IBV_SEND_SIGNALED;wr.wr.atomic.remote_addr = remote_addr;wr.wr.atomic.rkey = remote_rkey;wr.wr.atomic.compare_add = increment; // 增量值
ibv_post_send(qp, &wr, &bad_wr);Atomic 操作的语义:
- Compare and Swap:如果远端地址的值等于
compare_add,则将其替换为swap,返回原始值 - Fetch and Add:将远端地址的值加上
compare_add,返回原始值
两种操作都是原子性的——RNIC 硬件保证在执行过程中不会被其他操作打断。这使得 RDMA Atomic 可以用于实现分布式锁、无锁数据结构、分布式计数器等。
操作类型对比
| 操作 | 类型 | 远端 CPU | 远端 RQ | 典型用途 |
|---|---|---|---|---|
| Send/Recv | 双边 | 参与(Post Recv) | 消耗 Recv WR | 控制消息、RPC 请求/响应 |
| RDMA Write | 单边 | 不参与 | 不消耗 | 批量数据写入、日志复制 |
| RDMA Write+Imm | 单边+通知 | CQE 通知 | 消耗 Recv WR | 数据写入 + 通知远端 |
| RDMA Read | 单边 | 不参与 | 不消耗 | 远程数据读取 |
| Atomic CAS | 单边 | 不参与 | 不消耗 | 分布式锁、无锁数据结构 |
| Atomic Fetch&Add | 单边 | 不参与 | 不消耗 | 分布式计数器 |
七、性能特征与调优
延迟与吞吐量
RDMA 的性能远超传统 TCP/IP 网络:
| 指标 | InfiniBand | RoCEv2 | TCP/IP(参考) |
|---|---|---|---|
| 延迟(1 字节) | ~0.5-1μs | ~1-2μs | ~20-50μs |
| 带宽 | HDR 200Gbps | 100-400Gbps | 取决于 CPU 和协议栈 |
| CPU 占用 | < 5% | < 10% | 30-100% |
| 消息速率 | ~100M msg/s | ~50M msg/s | ~1-5M msg/s |
Huge Pages 与 MR 注册
MR 注册时,RNIC 需要为每个 4KB 页面创建一个 MTT(Memory Translation Table)条目。对于大块内存,这会导致:
- MTT 表项数量巨大,占用 RNIC 内存
- 注册时间与页面数成正比
- TLB miss 导致地址翻译延迟
使用 Huge Pages 可以显著改善:
# 分配 Huge Pagesecho 1024 > /proc/sys/vm/nr_hugepages # 分配 1024 个 2MB Huge Pages
# 查看 Huge Pages 状态cat /proc/meminfo | grep Huge
# 在程序中使用 Huge Pages# 方式一:通过环境变量export RDMAV_HUGEPAGES_SAFE=1
# 方式二:使用 mmap 分配 Huge Pagesvoid *buf = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB, -1, 0);Huge Pages 的效果:
- 2MB 页面 vs 4KB 页面:MTT 条目减少 512 倍
- 注册时间从秒级降到毫秒级
- RNIC TLB 命中率大幅提升
NUMA 亲和性
RDMA 性能高度依赖 NUMA 亲和性。RNIC 插在特定的 NUMA 节点上,如果应用运行在远端 NUMA 节点,DMA 传输需要跨越 QPI/UPI 互连,延迟增加 30-50%:
# 查看 RNIC 的 NUMA 节点cat /sys/class/infiniband/mlx5_0/device/numa_node
# 将进程绑定到 RNIC 所在的 NUMA 节点numactl --cpunodebind=0 --membind=0 ./rdma_app
# 查看 CPU 与 NUMA 节点的拓扑numactl --hardwarelscpu | grep NUMA最佳实践:
- 应用线程:绑定到 RNIC 所在 NUMA 节点的 CPU 核心
- MR 内存:从 RNIC 所在 NUMA 节点分配
- CQ 轮询线程:与 QP 在同一 NUMA 节点
- 中断亲和性:将 RNIC 中断绑定到同一 NUMA 节点的 CPU
Inline Data:小消息优化
对于小消息(通常 < 64 字节),RDMA 支持将数据直接内联在 WQE(Work Queue Entry)中,省去一次 DMA 读取:
// 创建 QP 时设置 inline data 大小qp_init_attr.cap.max_inline_data = 64;
// 提交 WR 时使用 inline 标志wr.send_flags |= IBV_SEND_INLINE;// 此时 sge.addr 指向的数据会被直接复制到 WQE 中// 不需要 MR,不需要 DMA 读取Inline Data 的适用场景:
- RPC 请求头(通常几十字节)
- 控制消息
- 小于
max_inline_data的消息
Inline Data 消除了小消息的 DMA 读取延迟,但会增加 QP 的 WQE 大小,从而减少 SQ 可容纳的 WR 数量。对于大消息,不要使用 inline——数据会被复制到 WQE 中,反而增加了开销。
Doorbell Batching:减少 MMIO 写入
每次 ibv_post_send() 都会产生一次 MMIO(Memory-Mapped I/O)写入——通知 RNIC 有新的 WR 需要处理。MMIO 写入是 uncached 的,延迟约 50-100ns,是 RDMA 延迟的重要组成部分。
Doorbell Batching 通过一次 MMIO 写入通知 RNIC 处理多个 WR:
// 链式提交多个 WRstruct ibv_send_wr wr1, wr2, wr3, *bad_wr;
// 设置 wr1, wr2, wr3 ...
wr1.next = &wr2;wr2.next = &wr3;wr3.next = NULL;
// 一次 ibv_post_send 提交 3 个 WR// 只产生一次 Doorbell(一次 MMIO 写入)ibv_post_send(qp, &wr1, &bad_wr);其他调优参数
# 调整 CQ 轮询批量大小# 每次轮询处理多个 CQE,减少系统调用次数int ne = ibv_poll_cq(cq, 32, wc_array); // 一次轮询最多 32 个
# 调整 QP 深度# 增大 max_send_wr / max_recv_wr 允许更多在途请求qp_init_attr.cap.max_send_wr = 1024;qp_init_attr.cap.max_recv_wr = 1024;
# 启用自适应轮询# 在低负载时退避,高负载时积极轮询# 需要应用层实现十、动手实践
Practice 1:检查 RDMA 硬件
# 查看系统中的 RDMA 设备ibv_devinfo
# 输出示例:# hca_id: mlx5_0# transport: InfiniBand (0)# fw_ver: 28.39.3000# node_guid: 506b:4b03:00e8:7a40# sys_image_guid: 506b:4b03:00e8:7a43# vendor_id: 0x02c9# vendor_part_id: 4123# hw_ver: 0x0# board_id: MT_0000000012# phys_port_cnt: 1# port: 1# state: PORT_ACTIVE (4)# max_mtu: 4096 (5)# active_mtu: 4096 (5)# sm_lid: 1# port_lid: 2# port_lmc: 0x00# link_layer: InfiniBand
# 查看特定设备的端口状态ibv_devinfo -d mlx5_0 -i 1
# 查看 GID 表(RoCEv2 需要)show_gids
# 列出所有 RDMA 设备ibv_devicesPractice 2:运行 rping(RDMA Ping)
rping 是 RDMA 的 ping 工具,可以验证 RDMA 连接和测量延迟:
# 服务端(在一台机器上运行)rping -s -a 0.0.0.0 -p 18515 -v
# 客户端(在另一台机器上运行)rping -c -a <server_ip> -p 18515 -v -C 10
# 输出示例:# rdma_ping: data 32 bytes 1 trips 10 ms# rdma_ping: data 64 bytes 1 trips 10 ms# rdma_ping: data 256 bytes 1 trips 10 ms# rdma_ping: data 1024 bytes 1 trips 10 ms# ...
# 使用 RDMA CM 测试(RoCEv2)# 服务端rping -s -a <server_ip> -p 18515 -v
# 客户端rping -c -a <server_ip> -p 18515 -vPractice 3:编写简单的 RDMA Write 程序
以下是一个最小化的 RDMA Write 示例,展示核心编程模式:
#include <infiniband/verbs.h>#include <stdio.h>#include <stdlib.h>#include <string.h>
#define BUF_SIZE 4096
int main(){ // 1. 获取设备列表 int num_devices; struct ibv_device **dev_list = ibv_get_device_list(&num_devices); if (!dev_list || num_devices == 0) { fprintf(stderr, "未找到 RDMA 设备\n"); return 1; }
// 2. 打开设备 struct ibv_context *ctx = ibv_open_device(dev_list[0]); if (!ctx) { fprintf(stderr, "打开设备失败\n"); return 1; }
// 3. 创建 PD struct ibv_pd *pd = ibv_alloc_pd(ctx);
// 4. 注册 MR char *buf = malloc(BUF_SIZE); struct ibv_mr *mr = ibv_reg_mr(pd, buf, BUF_SIZE, IBV_ACCESS_LOCAL_WRITE | IBV_ACCESS_REMOTE_WRITE | IBV_ACCESS_REMOTE_READ); printf("MR 注册成功: lkey=0x%x, rkey=0x%x\n", mr->lkey, mr->rkey);
// 5. 创建 CQ struct ibv_cq *cq = ibv_create_cq(ctx, 16, NULL, NULL, 0);
// 6. 创建 QP struct ibv_qp_init_attr qp_attr = { .send_cq = cq, .recv_cq = cq, .qp_type = IBV_QPT_RC, .cap = { .max_send_wr = 16, .max_recv_wr = 16, .max_send_sge = 1, .max_recv_sge = 1, }, }; struct ibv_qp *qp = ibv_create_qp(pd, &qp_attr); printf("QP 创建成功: qpn=0x%x\n", qp->qp_num);
// 7. 修改 QP 状态: RESET → INIT struct ibv_qp_attr attr; memset(&attr, 0, sizeof(attr)); attr.qp_state = IBV_QPS_INIT; attr.port_num = 1; attr.pkey_index = 0; attr.qp_access_flags = IBV_ACCESS_REMOTE_WRITE | IBV_ACCESS_REMOTE_READ; ibv_modify_qp(qp, &attr, IBV_QP_STATE | IBV_QP_PKEY_INDEX | IBV_QP_PORT | IBV_QP_ACCESS_FLAGS);
// 在实际程序中,此处需要与对端交换 QP 信息 // 然后执行 INIT → RTR → RTS 状态转换 // ...
printf("RDMA 环境初始化完成\n");
// 清理 ibv_destroy_qp(qp); ibv_destroy_cq(cq); ibv_dereg_mr(mr); ibv_dealloc_pd(pd); ibv_close_device(ctx); ibv_free_device_list(dev_list); free(buf);
return 0;}编译:
# 编译 RDMA 程序gcc -o rdma_write_demo rdma_write_demo.c -libverbs -lrdmacm
# 运行./rdma_write_demoPractice 4:使用 ib_write_bw 基准测试
ib_write_bw 是 perftest 工具包中的 RDMA Write 带宽基准测试工具:
# 服务端ib_write_bw -d mlx5_0
# 客户端ib_write_bw -d mlx5_0 <server_ip>
# 输出示例:# ---------------------------------------------------------------------------------------
# Dual-port : OFF Device : mlx5_0# Number of qps : 1 Transport type : IB# Connection type : RC Using SRQ : OFF# TX depth : 128# Mtu : 1024[B]# Link type : IB# Max msg size : 65536[B]# ---------------------------------------------------------------------------------------
# 65536 5000 24123. 24118. 0.386
# 测试不同消息大小ib_write_bw -d mlx5_0 -s 1 <server_ip> # 1 字节ib_write_bw -d mlx5_0 -s 4096 <server_ip> # 4KBib_write_bw -d mlx5_0 -s 65536 <server_ip> # 64KB
# 测试延迟ib_write_lat -d mlx5_0 <server_ip>
# 测试 RDMA Read 带宽ib_read_bw -d mlx5_0 <server_ip>
# 测试 Send/Recv 带宽ib_send_bw -d mlx5_0 <server_ip>
# 使用双向测试ib_write_bw -d mlx5_0 -b <server_ip>
# 使用多 QP 测试ib_write_bw -d mlx5_0 -n 4 <server_ip> # 4 个 QP
# 使用 Huge Pagesib_write_bw -d mlx5_0 --use_hugepages <server_ip>其他常用 perftest 工具:
| 工具 | 测试内容 |
|---|---|
ib_write_bw | RDMA Write 带宽 |
ib_write_lat | RDMA Write 延迟 |
ib_read_bw | RDMA Read 带宽 |
ib_read_lat | RDMA Read 延迟 |
ib_send_bw | Send/Recv 带宽 |
ib_send_lat | Send/Recv 延迟 |
ib_atomic_lat | Atomic 操作延迟 |
小结
RDMA 通过零拷贝、内核旁路和 CPU 卸载三大核心技术,将网络 I/O 的性能推向了物理极限。本章深入了 RDMA 的完整技术栈:
-
RDMA 的本质:绕过内核,RNIC 直接通过 DMA 访问应用内存,远端 CPU 可以完全不参与数据搬运——这是单边操作的革命性意义
-
三种传输协议:InfiniBand(原生 RDMA,极致性能)、RoCEv2(以太网上的 RDMA,数据中心主流)、iWARP(TCP 上的 RDMA,广域兼容)——向上统一提供 Verbs API
-
Verbs API 编程模型:PD(内存隔离)、MR(注册内存供远程访问)、QP(通信端点)、CQ(完成通知)、SRQ(共享接收队列)——五大核心抽象构成了 RDMA 编程的基础
-
QP 状态机:RESET → INIT → RTR → RTS 的严格状态转换,每一步都需要设置正确的属性——这是 RDMA 编程中最容易出错的地方
-
RDMA CM:封装了 QP 创建、状态转换和信息交换,提供类似 socket API 的连接管理接口
-
数据传输操作:双边操作(Send/Recv)和单边操作(RDMA Write/Read/Atomic)——单边操作是 RDMA 的核心价值,远端 CPU 完全不参与
-
性能调优:Huge Pages、NUMA 亲和性、Inline Data、Doorbell Batching——每一项都可以显著提升性能
RDMA 技术正在从 HPC 走向更广泛的数据中心场景:分布式存储(NVMe-oF)、分布式数据库(PolarDB、TiDB)、机器学习(NCCL、GDR)、云网络(SR-IOV + RDMA)——理解 RDMA 的底层原理,是构建下一代高性能分布式系统的基础。
参考资料
- InfiniBand Architecture Specification — InfiniBand 协议规范
- RDMA over Converged Ethernet (RoCE) Specification — RoCE 协议规范
- libibverbs Man Pages — Verbs API 参考手册
- librdmacm Man Pages — RDMA CM 参考手册
- RDMAmojo — RDMA 编程实践博客
- Mellanox OFED Documentation — NVIDIA/Mellanox OFED 文档
- perftest GitHub — RDMA 性能测试工具
- rdma-core GitHub — Linux RDMA 用户态库源码
- 《InfiniBand Network Architecture》— Shanley T. — InfiniBand 架构权威参考
- Linux Kernel RDMA Documentation — 内核 RDMA 子系统文档
支持与分享
如果这篇文章对你有帮助,欢迎支持作者或分享给更多人
部分信息可能已经过时






