导读:本期聚焦于小伙伴创作的《C++如何使用libverbs进行RDMA编程实现远程内存访问》,敬请观看详情,探索知识的价值。以下视频、文章将为您系统阐述其核心内容与价值。如果您觉得《C++如何使用libverbs进行RDMA编程实现远程内存访问》有用,将其分享出去将是对创作者最好的鼓励。

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

C++如何使用libverbs进行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

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。