RDMA即远程直接内存访问,允许一台主机直接访问另一台主机的内存空间,整个过程不需要操作系统内核参与,也没有CPU的数据拷贝开销,能大幅降低通信延迟提升传输带宽。在高性能计算、分布式数据库、云存储等场景中,RDMA已经成为提升节点间通信效率的核心技术。libverbs是RDMA用户态编程的标准库,封装了底层RDMA硬件的操作接口,开发者可以通过它完成RDMA相关的所有操作。

RDMA编程核心概念
在开始编写代码前,需要先了解libverbs中的几个核心概念:
- 设备上下文(ibv_context):代表一个RDMA设备,是所有操作的入口。
- 保护域(ibv_pd):用于隔离不同应用的内存和队列资源,避免非法访问。
- 内存区域(ibv_mr):注册到RDMA设备的内存块,只有注册后的内存才能被RDMA操作访问。
- 队列对(ibv_qp):负责发送和接收RDMA请求,包含发送队列和接收队列。
- 完成队列(ibv_cq):存储已经完成的RDMA操作的完成通知。
- 工作请求(ibv_wr):提交给队列对的RDMA操作请求,比如读、写、发送等。
环境准备
首先需要安装libverbs相关的开发库,在Ubuntu系统下可以通过apt安装:
sudo apt update sudo apt install libibverbs-dev
同时需要准备支持RDMA的硬件,比如InfiniBand网卡或者支持RoCE的以太网卡,如果没有硬件也可以使用Soft-RoCE模拟环境,这里不做展开。
基础编程流程
1. 初始化设备并获取设备上下文
首先需要枚举系统中的RDMA设备,选择目标设备并打开获取上下文:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <infiniband/verbs.h>
int main() {
// 获取设备列表
int num_devices;
struct ibv_device** device_list = ibv_get_device_list(&num_devices);
if (num_devices == 0) {
printf("未找到RDMA设备n");
return -1;
}
// 选择第一个设备
struct ibv_device* device = device_list[0];
// 打开设备获取上下文
struct ibv_context* ctx = ibv_open_device(device);
if (ctx == NULL) {
printf("打开设备失败n");
ibv_free_device_list(device_list);
return -1;
}
printf("成功打开RDMA设备: %sn", ibv_get_device_name(device));
// 后续操作...
return 0;
}
2. 创建保护域和完成队列
保护域用于隔离资源,完成队列用于获取操作完成通知:
// 创建保护域
struct ibv_pd* pd = ibv_alloc_pd(ctx);
if (pd == NULL) {
printf("创建保护域失败n");
ibv_close_device(ctx);
ibv_free_device_list(device_list);
return -1;
}
// 创建完成队列,设置队列深度为16
struct ibv_cq* cq = ibv_create_cq(ctx, 16, NULL, NULL, 0);
if (cq == NULL) {
printf("创建完成队列失败n");
ibv_dealloc_pd(pd);
ibv_close_device(ctx);
ibv_free_device_list(device_list);
return -1;
}
3. 注册内存区域
需要将用户空间的内存注册到RDMA设备,才能被远程访问:
// 分配一块内存用于RDMA操作
char* buffer = (char*)malloc(1024);
memset(buffer, 0, 1024);
strcpy(buffer, "Hello RDMA");
// 注册内存区域,设置访问权限为本地写、远程读、远程写
struct ibv_mr* mr = ibv_reg_mr(pd, buffer, 1024,
IBV_ACCESS_LOCAL_WRITE | IBV_ACCESS_REMOTE_READ | IBV_ACCESS_REMOTE_WRITE);
if (mr == NULL) {
printf("注册内存区域失败n");
ibv_destroy_cq(cq);
ibv_dealloc_pd(pd);
ibv_close_device(ctx);
ibv_free_device_list(device_list);
free(buffer);
return -1;
}
printf("内存注册成功,地址: %p, lkey: %u, rkey: %un", buffer, mr->lkey, mr->rkey);
4. 创建队列对
队列对是RDMA操作的核心,需要配置其状态和属性:
// 初始化队列对属性
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;
qp_attr.sq_sig_all = 0; // 不是所有发送请求都生成完成通知
// 创建队列对
struct ibv_qp* qp = ibv_create_qp(pd, &qp_attr);
if (qp == NULL) {
printf("创建队列对失败n");
ibv_dereg_mr(mr);
ibv_destroy_cq(cq);
ibv_dealloc_pd(pd);
ibv_close_device(ctx);
ibv_free_device_list(device_list);
free(buffer);
return -1;
}
5. 队列对状态转换
RC类型的队列对需要经过多个状态转换才能进入可数据传输的状态:
// 状态转换:RESET -> INIT
struct ibv_qp_attr attr;
memset(&attr, 0, sizeof(attr));
attr.qp_state = IBV_QPS_INIT;
attr.pkey_index = 0;
attr.port_num = 1; // 使用第一个端口
attr.qp_access_flags = IBV_ACCESS_REMOTE_READ | IBV_ACCESS_REMOTE_WRITE;
if (ibv_modify_qp(qp, &attr, IBV_QP_STATE | IBV_QP_PKEY_INDEX | IBV_QP_PORT | IBV_QP_ACCESS_FLAGS)) {
printf("队列对状态转换到INIT失败n");
ibv_destroy_qp(qp);
ibv_dereg_mr(mr);
ibv_destroy_cq(cq);
ibv_dealloc_pd(pd);
ibv_close_device(ctx);
ibv_free_device_list(device_list);
free(buffer);
return -1;
}
// 后续还需要转换到RTR(就绪接收)、RTS(就绪发送)状态,需要交换对端的QP信息完成连接
数据传输示例
完成连接建立后,就可以提交RDMA读写请求,以下是提交一个远程写请求的示例:
// 构造远程写工作请求
struct ibv_sge sge;
sge.addr = (uint64_t)buffer; // 本地内存地址
sge.length = 1024; // 数据长度
sge.lkey = mr->lkey; // 本地内存key
struct ibv_send_wr wr;
memset(&wr, 0, sizeof(wr));
wr.wr_id = 1; // 请求标识
wr.sg_list = &sge;
wr.num_sge = 1;
wr.opcode = IBV_WR_RDMA_WRITE; // 远程写操作
wr.send_flags = IBV_SEND_SIGNALED; // 生成完成通知
wr.wr.rdma.remote_addr = 0x12345678; // 对端内存地址,实际场景需要从对端获取
wr.wr.rdma.rkey = 0xabcdef; // 对端内存的rkey,实际场景需要从对端获取
// 提交请求
struct ibv_send_wr* bad_wr;
if (ibv_post_send(qp, &wr, &bad_wr)) {
printf("提交发送请求失败n");
}
// 轮询完成队列获取完成通知
struct ibv_wc wc;
int ret = ibv_poll_cq(cq, 1, &wc);
if (ret > 0 && wc.status == IBV_WC_SUCCESS) {
printf("RDMA写操作完成n");
}
资源清理
程序结束前需要按反向顺序清理所有申请的资源:
// 清理资源
ibv_destroy_qp(qp);
ibv_dereg_mr(mr);
free(buffer);
ibv_destroy_cq(cq);
ibv_dealloc_pd(pd);
ibv_close_device(ctx);
ibv_free_device_list(device_list);
return 0;
}
注意事项
- 远程内存的地址和rkey需要通过对端交换获取,实际场景中通常需要配合TCP连接交换这些连接信息。
- 内存注册是有开销的操作,高频小内存操作建议提前注册大块内存再拆分使用。
- 不同的RDMA操作类型需要对应不同的内存访问权限,注册内存时要按需设置权限。
- 队列对的深度需要根据实际业务的并发量合理设置,避免队列满导致请求提交失败。
RDMAlibverbsC++_RDMA编程远程内存访问修改时间:2026-07-23 11:09:44