一、背景介绍
远程直接内存访问(RDMA)技术通过绕过操作系统内核和CPU直接访问远程内存,实现了超低延迟、高吞吐量的网络通信。该技术广泛应用于高性能计算、分布式存储和机器学习等领域。本文通过一个完整的代码示例,演示如何利用RDMA核心组件(QP、MR、CQ等)实现跨节点内存直接读写。
二、方法设计
A.实现方案
- 控制平面:使用TCP协议交换RDMA连接参数
- 数据平面:基于IB Verbs接口实现零拷贝传输
- 混合模式:客户端主动写入,服务端被动读取
B.关键技术点
- 内存注册机制实现安全访问
- QP状态机转换确保通信可靠性
- 完成队列轮询实现异步通知
- 端到端流控通过TCP协议实现
三、代码及注释
/*----------------------------- 头文件包含 -----------------------------*/
// 标准库和网络相关头文件
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#include <assert.h>
#include <errno.h>
#include <sys/time.h>
#include <byteswap.h>
// RDMA相关头文件
#include <arpa/inet.h>
#include <infiniband/verbs.h>
#ifdef USE_VACC
#include "vaccrt.h"
#include <vaccrt_mem_management.h>
#endif
#ifdef USE_CUDA
#include <cuda.h>
#include <cuda_runtime.h>
#endif
/**********************************************************************
A.关键概念解释:
1.保护域(PD):资源隔离单元,所有资源(QP、MR等)必须属于某个PD
2.内存区域(MR):注册的内存区域,只有注册的内存才能用于RDMA操作
3.队列对(QP):包含发送队列和接收队列,是通信的基本单元
4.工作请求(WR):描述要执行的操作(发送/接收/RDMA读写)
5.完成队列(CQ):用于通知操作完成状态
6.QP状态转换:
a.INIT:初始状态,设置基本参数
b.RTR(Ready to Receive):准备好接收数据
c.RTS(Ready to Send):准备好发送数据
B.程序流程总结:
1.通过TCP交换RDMA连接参数
2.初始化IB资源(PD、CQ、MR、QP)
3.交换QP信息(地址、密钥等)
4.进行QP状态转换(INIT->RTR->RTS)
5.执行RDMA写/读操作
6.轮询完成队列确认操作完成
7.清理资源
C.使用说明:
1.编译命令:
gcc -o cuda -DUSE_CUDA -ggdb main.c -pthread -libverbs \
-I /usr/local/cuda/include \
-L /usr/local/cuda/lib64 -Wl,-rpath=/usr/local/cuda/lib64 \
-lcudart -lcudadevrt -lcuda
2.服务端 :
./cuda 192.168.1.100 mlx5_0
3.客户端 :
./cuda 192.168.1.101 mlx5_0 192.168.1.100
***********************************************************************/
/*----------------------------- 全局配置 -----------------------------*/
#define MAX_POLL_CQ_TIMEOUT 6000 // CQ轮询超时时间(毫秒)
#define MSG "Hello,World" // 要传输的测试消息
#define MSG_SIZE (64<<10)
// 配置参数结构体
struct config {
const char *dev; // IB设备名称
char *local_addr; // 本地IP地址
u_int32_t port; // TCP端口号
int ib_port; // IB端口号(默认1)
int gid_idx; // GID索引(-1表示不使用RoCEv2)
} config = {
NULL, NULL, 12025, 1, -1 };
/*----------------------------- 资源结构体 -----------------------------*/
// 包含所有RDMA相关资源
struct resources {
struct ibv_context *ctx; // IB上下文
struct ibv_pd *pd; // 保护域(Protection Domain)
struct ibv_cq *cq; // 完成队列(Completion Queue)
struct ibv_qp *qp; // 队列对(Queue Pair)
struct ibv_mr *mr; // 内存区域(Memory Region)
void *buf; // 数据缓冲区指针
int sock; // TCP套接字
uint64_t remote_addr; // 远程内存地址
uint32_t remote_rkey; // 远程内存访问密钥
struct ibv_port_attr port_attr; // IB端口属性
};
/*----------------------------- 辅助函数 -----------------------------*/
/**
* 建立TCP连接(客户端)或监听(服务端)
* @param server 本地地址(服务端模式时使用)
* @param port TCP端口号
* @param remote_addr 远程地址(客户端模式时使用)
* @return 成功返回套接字fd,失败返回-1
*/
int sock_connect(const char *server, int port,const char *remote_addr) {
struct addrinfo hints = {
.ai_family = AF_INET, .ai_socktype = SOCK_STREAM };
struct addrinfo *res, *p;
int sock = -1;
char port_str[6];
const char * p_addr=server;
if(remote_addr) p_addr=remote_addr;
sprintf(port_str, "%d", port);
if (getaddrinfo(p_addr, port_str, &hints, &res)) return -1;
for (p = res; p; p = p->ai_next) {
sock = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
int reuse = 1;
if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse))) return -1;
if (sock < 0) continue;
if (remote_addr) {
if (connect