在 Ubuntu 22.04 上配置 Soft-RoCE 并运行 RDMA 测试程序

一、前言

RDMA(Remote Direct Memory Access,远程直接内存访问)是一种高性能网络传输技术,它允许应用程序直接访问远程主机的内存,而无需经过操作系统内核,从而大幅降低网络传输延迟并提高吞吐量。

本文解决的问题:

  • 在没有真实 RDMA 硬件(如 Mellanox 网卡)的情况下体验 RDMA
  • 使用 Soft-RoCE(RXE)软件模拟 RDMA 设备
  • 完整的源码下载、编译和测试流程
  • 提供完整可运行的 RDMA 客户端 / 服务端代码示例

适用场景:

  • 学习和开发 RDMA 应用程序
  • 在虚拟机或普通服务器上测试 RDMA 功能
  • 快速验证 RDMA 程序的逻辑正确性

二、环境准备

2.1 系统要求

  • 操作系统:Ubuntu 22.04 LTS(推荐)

  • 内核版本:5.15+(Soft-RoCE 需要)

  • 内存:至少 4GB

  • 网络:普通以太网卡即可

    检查系统版本

    lsb_release -a

    检查内核版本

    uname -r

2.2 安装基础依赖

复制代码
# 更新系统包
sudo apt-get update

# 安装基础开发工具
sudo apt-get install -y build-essential git cmake gcc g++ make autoconf automake libtool

# 安装RDMA核心库和工具
sudo apt-get install -y rdma-core libibverbs-dev librdmacm-dev ibverbs-utils rdmacm-utils

# 安装Soft-RoCE所需的包
sudo apt-get install -y ibacm infiniband-diags perftest

# 安装其他依赖
sudo apt-get install -y libnl-3-dev libnl-route-3-dev pandoc doxygen

# 安装Python依赖(用于构建文档)
sudo apt-get install -y python3-dev python3-pip

2.3 验证基础安装

复制代码
# 检查安装的工具版本
ibv_devinfo --help

# 查看已安装的包
dpkg -l | grep rdma
dpkg -l | grep ibverbs

三、配置 Soft-RoCE(RXE)

3.1 什么是 Soft-RoCE

Soft-RoCE(也称为 RXE)是一个软件实现的 RoCE(RDMA over Converged Ethernet)设备。它允许在没有 RDMA 硬件的普通服务器上运行 RDMA 应用程序。

优势:

  • 无需专用 RDMA 硬件
  • 支持标准以太网
  • 完整的 RDMA Verbs API 支持
  • 适合开发、测试和学习

限制:

  • 性能不如真实 RDMA 硬件
  • CPU 开销较高
  • 延迟较高

3.2 加载 RDMA 内核模块

复制代码
# 加载RDMA核心模块
sudo modprobe rdma

# 加载Soft-RoCE模块
sudo modprobe rdma_rxe

# 验证模块已加载
lsmod | grep rdma

输出示例:

复制代码
rdma_rxe              163840  0
ib_uverbs             163840  0
rdma_cm               163840  0
rdma_core             163840  1 rdma_cm
ib_core               450560  6 rdma_cm,rdma_rxe,ib_uverbs,rdma_core

3.3 创建 RXE 设备

方法一:使用 rxe_cfg 工具(推荐)

复制代码
# 查看可用网络接口
ip link show

# 使用rxe_cfg创建RXE设备
# 假设你的网卡是eth0或ens33
sudo rxe_cfg start

# 添加RXE设备(将eth0替换为你的网卡名)
sudo rxe_cfg add eth0
# 或者
sudo rxe_cfg add ens33

# 检查RXE设备状态
sudo rxe_cfg status

方法二:手动配置

如果系统没有 rxe_cfg,可以手动创建:

复制代码
# 查看网卡名称
ip link show

# 创建RXE设备(将eth0替换为你的网卡名)
echo "eth0" | sudo tee /sys/module/rdma_rxe/parameters/add

# 查看创建的设备
ls -l /sys/class/infiniband/

3.4 验证 RXE 设备

复制代码
# 查看RDMA设备列表
ibv_devices

输出示例:

复制代码
    device                 node GUID
    ------              ----------------
    rxe0                505400fffe123456

# 查看设备详细信息
ibv_devinfo

输出示例:

复制代码
hca_id: rxe0
    transport:                      InfiniBand (0)
    fw_ver:                         0.0.0
    node_guid:                      5054:00ff:fe12:3456
    sys_image_guid:                 5054:00ff:fe12:3456
    vendor_id:                      0x0000
    vendor_part_id:                 0
    hw_ver:                         0x0
    phys_port_cnt:                  1
    port:   1
        state:              PORT_ACTIVE (4)
        max_mtu:            4096 (5)
        active_mtu:         1024 (3)
        sm_lid:             0
        port_lid:           0
        port_lmc:           0x00
        link_layer:         Ethernet

3.5 设置开机自动加载(可选)

复制代码
# 编辑modules配置文件
sudo tee /etc/modules-load.d/rdma.conf << EOF
rdma
rdma_rxe
EOF

# 创建启动脚本
sudo tee /etc/network/if-up.d/rxe-setup << 'EOF'
#!/bin/bash
# 等待网络接口就绪
sleep 5
# 加载模块
modprobe rdma_rxe
# 查找第一个非lo的接口
IFACE=$(ip link show | grep -v lo | grep -v DOWN | awk '/^[0-9]/{print $2}' | sed 's/://' | head -n1)
if [ -n "$IFACE" ]; then
    rxe_cfg add $IFACE 2>/dev/null || true
fi
EOF

sudo chmod +x /etc/network/if-up.d/rxe-setup

四、完整的 RDMA 客户端 / 服务端程序

本节提供完整的、可运行的 RDMA 客户端 / 服务端代码,使用 Socket + Verbs API 方式实现,在 Soft-RoCE 环境下稳定可靠。

4.1 为什么选择 Socket + Verbs API?

RDMA CM 的问题:

  • RDMA CM(Connection Manager)在 Soft-RoCE 环境下存在路由解析问题
  • rdma_resolve_route() 经常返回 "No such device" 错误
  • 兼容性较差

Socket + Verbs API 的优势:

  • 使用传统 TCP Socket 交换连接信息(QPN、GID、rkey 等)
  • 直接使用 Verbs API 管理 QP 状态
  • 在 Soft-RoCE 环境下稳定可靠
  • 更清晰地展示 RDMA 工作原理

传统 TCP/IP 需要 CPU 把数据从应用内存拷贝到内核缓冲区、再封装成以太网帧发送,数据经过多次拷贝和内核处理;RDMA/RoCE 网卡直接通过 DMA 访问应用内存、硬件自动封装以太网帧并传输,数据全程零拷贝、完全绕过内核,实现微秒级延迟和近 100Gbps 带宽;Soft-RoCE 则是让 CPU 用软件模拟 RDMA 网卡的功能,虽然同样提供零拷贝和内核旁路的数据通路,但 "封装以太网帧" 这一步由 CPU 完成、而非硬件卸载,所以性能约为硬件 RDMA 的 10-20%,但好处是无需专用网卡、普通以太网就能跑,适合开发测试学习。三者区别本质:封装以太网帧的是 CPU(传统 TCP + 多次拷贝)、RDMA 硬件(零拷贝 + 最高性能)、还是软件模拟(零拷贝 + 中等性能)。

4.2 创建项目目录

复制代码
# 创建项目目录
mkdir -p ~/RDMA
cd ~/RDMA

4.3 服务端代码 (rdma_server.c)

cpp 复制代码
/*
 * RDMA Server - 使用 Socket + Verbs API(Soft-RoCE 完全兼容版)
 * 
 * 编译: gcc rdma_server.c -o server -libverbs
 * 运行: ./server [device_name]
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <infiniband/verbs.h>

#define PORT 8888
#define BUF_SIZE 1024

/* QP 状态转换函数 */
int qp_to_init(struct ibv_qp *qp, int port_num) {
    struct ibv_qp_attr attr;
    memset(&attr, 0, sizeof(attr));
    attr.qp_state = IBV_QPS_INIT;
    attr.pkey_index = 0;
    attr.port_num = port_num;
    attr.qp_access_flags = IBV_ACCESS_REMOTE_WRITE | IBV_ACCESS_REMOTE_READ;
    
    int flags = IBV_QP_STATE | IBV_QP_PKEY_INDEX | IBV_QP_PORT | IBV_QP_ACCESS_FLAGS;
    return ibv_modify_qp(qp, &attr, flags);
}

int qp_to_rtr(struct ibv_qp *qp, uint32_t remote_qpn, union ibv_gid *remote_gid, int port_num) {
    struct ibv_qp_attr attr;
    memset(&attr, 0, sizeof(attr));
    attr.qp_state = IBV_QPS_RTR;
    attr.path_mtu = IBV_MTU_1024;
    attr.dest_qp_num = remote_qpn;
    attr.rq_psn = 0;
    attr.max_dest_rd_atomic = 1;
    attr.min_rnr_timer = 12;
    attr.ah_attr.dlid = 0;
    attr.ah_attr.sl = 0;
    attr.ah_attr.port_num = port_num;
    attr.ah_attr.is_global = 1;
    attr.ah_attr.grh.dgid = *remote_gid;
    attr.ah_attr.grh.hop_limit = 1;
    attr.ah_attr.grh.sgid_index = 0;
    
    int flags = 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;
    return ibv_modify_qp(qp, &attr, flags);
}

int qp_to_rts(struct ibv_qp *qp) {
    struct ibv_qp_attr attr;
    memset(&attr, 0, sizeof(attr));
    attr.qp_state = IBV_QPS_RTS;
    attr.sq_psn = 0;
    attr.max_rd_atomic = 1;
    attr.retry_cnt = 7;
    attr.rnr_retry = 7;
    attr.timeout = 14;  /* Soft-RoCE 需要设置 timeout */
    
    int flags = IBV_QP_STATE | IBV_QP_SQ_PSN | IBV_QP_MAX_QP_RD_ATOMIC | 
                IBV_QP_RETRY_CNT | IBV_QP_RNR_RETRY | IBV_QP_TIMEOUT;
    return ibv_modify_qp(qp, &attr, flags);
}

int main(int argc, char *argv[]) {
    const char *dev_name = argc > 1 ? argv[1] : "rxe0";
    
    struct ibv_device **dev_list = NULL;
    struct ibv_context *ctx = NULL;
    struct ibv_pd *pd = NULL;
    struct ibv_cq *cq = NULL;
    struct ibv_qp *qp = NULL;
    struct ibv_mr *mr = NULL;
    char *buf = NULL;
    
    int listen_sock = -1, client_sock = -1;
    
    printf("[Server] 启动 (设备: %s)\n", dev_name);

    /* 获取设备列表 */
    int num_devices;
    dev_list = ibv_get_device_list(&num_devices);
    if (!dev_list || num_devices == 0) {
        fprintf(stderr, "[Server] 没有找到RDMA设备\n");
        return 1;
    }

    /* 查找指定设备 */
    struct ibv_device *target_dev = NULL;
    for (int i = 0; i < num_devices; i++) {
        if (strcmp(ibv_get_device_name(dev_list[i]), dev_name) == 0) {
            target_dev = dev_list[i];
            break;
        }
    }
    
    if (!target_dev) {
        fprintf(stderr, "[Server] 未找到设备 %s\n", dev_name);
        ibv_free_device_list(dev_list);
        return 1;
    }

    printf("[Server] 打开设备: %s\n", ibv_get_device_name(target_dev));

    /* 打开设备 */
    ctx = ibv_open_device(target_dev);
    if (!ctx) {
        perror("ibv_open_device");
        goto cleanup;
    }

    /* 分配 PD */
    pd = ibv_alloc_pd(ctx);
    if (!pd) {
        perror("ibv_alloc_pd");
        goto cleanup;
    }

    /* 创建 CQ */
    cq = ibv_create_cq(ctx, 16, NULL, NULL, 0);
    if (!cq) {
        perror("ibv_create_cq");
        goto cleanup;
    }

    /* 创建 QP */
    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.cap.max_send_wr = 8;
    qp_attr.cap.max_recv_wr = 8;
    qp_attr.cap.max_send_sge = 1;
    qp_attr.cap.max_recv_sge = 1;
    qp_attr.qp_type = IBV_QPT_RC;
    
    qp = ibv_create_qp(pd, &qp_attr);
    if (!qp) {
        perror("ibv_create_qp");
        goto cleanup;
    }
    printf("[Server] QP 创建成功, QPN: 0x%06x\n", qp->qp_num);

    /* QP 转换到 INIT 状态 */
    if (qp_to_init(qp, 1) != 0) {
        fprintf(stderr, "[Server] QP 状态转换到 INIT 失败\n");
        goto cleanup;
    }
    printf("[Server] QP 状态: INIT\n");

    /* 分配并注册内存 */
    buf = calloc(1, BUF_SIZE);
    if (!buf) {
        perror("calloc");
        goto cleanup;
    }

    mr = ibv_reg_mr(pd, buf, BUF_SIZE, 
                    IBV_ACCESS_LOCAL_WRITE | IBV_ACCESS_REMOTE_WRITE | IBV_ACCESS_REMOTE_READ);
    if (!mr) {
        perror("ibv_reg_mr");
        goto cleanup;
    }
    printf("[Server] MR 注册成功, rkey: 0x%08x\n", mr->rkey);

    /* 获取 GID */
    union ibv_gid gid;
    ibv_query_gid(ctx, 1, 0, &gid);

    /* 创建 TCP socket 监听 */
    listen_sock = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_sock < 0) {
        perror("socket");
        goto cleanup;
    }

    int opt = 1;
    setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = INADDR_ANY;
    addr.sin_port = htons(PORT);

    if (bind(listen_sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
        perror("bind");
        goto cleanup;
    }

    if (listen(listen_sock, 1) < 0) {
        perror("listen");
        goto cleanup;
    }

    printf("[Server] 监听端口 %d...\n", PORT);

    /* 等待客户端连接 */
    client_sock = accept(listen_sock, NULL, NULL);
    if (client_sock < 0) {
        perror("accept");
        goto cleanup;
    }
    printf("[Server] 客户端已连接\n");

    /* 交换信息 */
    struct {
        uint32_t qpn;
        uint32_t rkey;
        uint64_t vaddr;
        uint8_t gid[16];
    } local_info, remote_info;

    local_info.qpn = qp->qp_num;
    local_info.rkey = mr->rkey;
    local_info.vaddr = (uint64_t)(uintptr_t)buf;
    memcpy(local_info.gid, gid.raw, 16);

    /* 先发送,再接收 */
    send(client_sock, &local_info, sizeof(local_info), 0);
    recv(client_sock, &remote_info, sizeof(remote_info), 0);

    printf("[Server] 收到客户端信息: QPN=0x%06x, rkey=0x%08x\n", 
           remote_info.qpn, remote_info.rkey);

    /* QP 转换到 RTR 状态 */
    if (qp_to_rtr(qp, remote_info.qpn, (union ibv_gid *)remote_info.gid, 1) != 0) {
        fprintf(stderr, "[Server] QP 状态转换到 RTR 失败\n");
        goto cleanup;
    }
    printf("[Server] QP 状态: RTR\n");

    /* QP 转换到 RTS 状态 */
    if (qp_to_rts(qp) != 0) {
        fprintf(stderr, "[Server] QP 状态转换到 RTS 失败\n");
        goto cleanup;
    }
    printf("[Server] QP 状态: RTS\n");

    /* 发布接收请求 */
    struct ibv_sge sge;
    struct ibv_recv_wr recv_wr, *bad_wr = NULL;
    
    sge.addr = (uint64_t)(uintptr_t)buf;
    sge.length = BUF_SIZE;
    sge.lkey = mr->lkey;
    
    recv_wr.wr_id = 0;
    recv_wr.next = NULL;
    recv_wr.sg_list = &sge;
    recv_wr.num_sge = 1;
    
    if (ibv_post_recv(qp, &recv_wr, &bad_wr) != 0) {
        perror("ibv_post_recv");
        goto cleanup;
    }

    printf("[Server] 等待接收消息...\n");

    /* 轮询 CQ */
    struct ibv_wc wc;
    while (ibv_poll_cq(cq, 1, &wc) == 0) {
        usleep(1000);
    }

    if (wc.status == IBV_WC_SUCCESS && wc.opcode == IBV_WC_RECV) {
        printf("\n========================================\n");
        printf("[Server] 收到消息: %s\n", buf);
        printf("========================================\n");
    } else {
        printf("[Server] 接收失败: %s\n", ibv_wc_status_str(wc.status));
    }

    sleep(1);

cleanup:
    printf("[Server] 清理资源\n");
    
    if (client_sock >= 0) close(client_sock);
    if (listen_sock >= 0) close(listen_sock);
    if (qp) ibv_destroy_qp(qp);
    if (mr) ibv_dereg_mr(mr);
    if (buf) free(buf);
    if (cq) ibv_destroy_cq(cq);
    if (pd) ibv_dealloc_pd(pd);
    if (ctx) ibv_close_device(ctx);
    if (dev_list) ibv_free_device_list(dev_list);

    return 0;
}

4.4 客户端代码 (rdma_client.c)

cpp 复制代码
/*
 * RDMA Client - 使用 Socket + Verbs API(Soft-RoCE 完全兼容版)
 * 
 * 编译: gcc rdma_client.c -o client -libverbs
 * 运行: ./client [server_ip] [device_name]
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <infiniband/verbs.h>

#define SERVER_IP "127.0.0.1"
#define PORT 8888
#define BUF_SIZE 1024

/* QP 状态转换函数 */
int qp_to_init(struct ibv_qp *qp, int port_num) {
    struct ibv_qp_attr attr;
    memset(&attr, 0, sizeof(attr));
    attr.qp_state = IBV_QPS_INIT;
    attr.pkey_index = 0;
    attr.port_num = port_num;
    attr.qp_access_flags = IBV_ACCESS_REMOTE_WRITE | IBV_ACCESS_REMOTE_READ;
    
    int flags = IBV_QP_STATE | IBV_QP_PKEY_INDEX | IBV_QP_PORT | IBV_QP_ACCESS_FLAGS;
    return ibv_modify_qp(qp, &attr, flags);
}

int qp_to_rtr(struct ibv_qp *qp, uint32_t remote_qpn, union ibv_gid *remote_gid, int port_num) {
    struct ibv_qp_attr attr;
    memset(&attr, 0, sizeof(attr));
    attr.qp_state = IBV_QPS_RTR;
    attr.path_mtu = IBV_MTU_1024;
    attr.dest_qp_num = remote_qpn;
    attr.rq_psn = 0;
    attr.max_dest_rd_atomic = 1;
    attr.min_rnr_timer = 12;
    attr.ah_attr.dlid = 0;
    attr.ah_attr.sl = 0;
    attr.ah_attr.port_num = port_num;
    attr.ah_attr.is_global = 1;
    attr.ah_attr.grh.dgid = *remote_gid;
    attr.ah_attr.grh.hop_limit = 1;
    attr.ah_attr.grh.sgid_index = 0;
    
    int flags = 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;
    return ibv_modify_qp(qp, &attr, flags);
}

int qp_to_rts(struct ibv_qp *qp) {
    struct ibv_qp_attr attr;
    memset(&attr, 0, sizeof(attr));
    attr.qp_state = IBV_QPS_RTS;
    attr.sq_psn = 0;
    attr.max_rd_atomic = 1;
    attr.retry_cnt = 7;
    attr.rnr_retry = 7;
    attr.timeout = 14;  /* Soft-RoCE 需要设置 timeout */
    
    int flags = IBV_QP_STATE | IBV_QP_SQ_PSN | IBV_QP_MAX_QP_RD_ATOMIC | 
                IBV_QP_RETRY_CNT | IBV_QP_RNR_RETRY | IBV_QP_TIMEOUT;
    return ibv_modify_qp(qp, &attr, flags);
}

int main(int argc, char *argv[]) {
    const char *server_ip = argc > 1 ? argv[1] : SERVER_IP;
    const char *dev_name = argc > 2 ? argv[2] : "rxe0";
    
    struct ibv_device **dev_list = NULL;
    struct ibv_context *ctx = NULL;
    struct ibv_pd *pd = NULL;
    struct ibv_cq *cq = NULL;
    struct ibv_qp *qp = NULL;
    struct ibv_mr *mr = NULL;
    char *buf = NULL;
    
    int sock = -1;
    
    printf("[Client] 连接服务器: %s:%d (设备: %s)\n", server_ip, PORT, dev_name);

    /* 获取设备列表 */
    int num_devices;
    dev_list = ibv_get_device_list(&num_devices);
    if (!dev_list || num_devices == 0) {
        fprintf(stderr, "[Client] 没有找到RDMA设备\n");
        return 1;
    }

    /* 查找指定设备 */
    struct ibv_device *target_dev = NULL;
    for (int i = 0; i < num_devices; i++) {
        if (strcmp(ibv_get_device_name(dev_list[i]), dev_name) == 0) {
            target_dev = dev_list[i];
            break;
        }
    }
    
    if (!target_dev) {
        fprintf(stderr, "[Client] 未找到设备 %s\n", dev_name);
        ibv_free_device_list(dev_list);
        return 1;
    }

    printf("[Client] 打开设备: %s\n", ibv_get_device_name(target_dev));

    /* 打开设备 */
    ctx = ibv_open_device(target_dev);
    if (!ctx) {
        perror("ibv_open_device");
        goto cleanup;
    }

    /* 分配 PD */
    pd = ibv_alloc_pd(ctx);
    if (!pd) {
        perror("ibv_alloc_pd");
        goto cleanup;
    }

    /* 创建 CQ */
    cq = ibv_create_cq(ctx, 16, NULL, NULL, 0);
    if (!cq) {
        perror("ibv_create_cq");
        goto cleanup;
    }

    /* 创建 QP */
    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.cap.max_send_wr = 8;
    qp_attr.cap.max_recv_wr = 8;
    qp_attr.cap.max_send_sge = 1;
    qp_attr.cap.max_recv_sge = 1;
    qp_attr.qp_type = IBV_QPT_RC;
    
    qp = ibv_create_qp(pd, &qp_attr);
    if (!qp) {
        perror("ibv_create_qp");
        goto cleanup;
    }
    printf("[Client] QP 创建成功, QPN: 0x%06x\n", qp->qp_num);

    /* QP 转换到 INIT 状态 */
    if (qp_to_init(qp, 1) != 0) {
        fprintf(stderr, "[Client] QP 状态转换到 INIT 失败\n");
        goto cleanup;
    }
    printf("[Client] QP 状态: INIT\n");

    /* 分配并注册内存 */
    buf = malloc(BUF_SIZE);
    if (!buf) {
        perror("malloc");
        goto cleanup;
    }
    memset(buf, 0, BUF_SIZE);
    strcpy(buf, "Hello RDMA! Soft-RoCE 连接成功!");

    mr = ibv_reg_mr(pd, buf, BUF_SIZE, 
                    IBV_ACCESS_LOCAL_WRITE | IBV_ACCESS_REMOTE_WRITE | IBV_ACCESS_REMOTE_READ);
    if (!mr) {
        perror("ibv_reg_mr");
        goto cleanup;
    }
    printf("[Client] MR 注册成功, rkey: 0x%08x\n", mr->rkey);

    /* 获取 GID */
    union ibv_gid gid;
    ibv_query_gid(ctx, 1, 0, &gid);

    /* 连接服务器 */
    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0) {
        perror("socket");
        goto cleanup;
    }

    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(PORT);
    inet_pton(AF_INET, server_ip, &addr.sin_addr);

    if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
        perror("connect");
        goto cleanup;
    }
    printf("[Client] 已连接到服务器\n");

    /* 交换信息 */
    struct {
        uint32_t qpn;
        uint32_t rkey;
        uint64_t vaddr;
        uint8_t gid[16];
    } local_info, remote_info;

    local_info.qpn = qp->qp_num;
    local_info.rkey = mr->rkey;
    local_info.vaddr = (uint64_t)(uintptr_t)buf;
    memcpy(local_info.gid, gid.raw, 16);

    /* 先接收,再发送 */
    recv(sock, &remote_info, sizeof(remote_info), 0);
    send(sock, &local_info, sizeof(local_info), 0);

    printf("[Client] 收到服务端信息: QPN=0x%06x, rkey=0x%08x\n", 
           remote_info.qpn, remote_info.rkey);

    /* QP 转换到 RTR 状态 */
    if (qp_to_rtr(qp, remote_info.qpn, (union ibv_gid *)remote_info.gid, 1) != 0) {
        fprintf(stderr, "[Client] QP 状态转换到 RTR 失败\n");
        goto cleanup;
    }
    printf("[Client] QP 状态: RTR\n");

    /* QP 转换到 RTS 状态 */
    if (qp_to_rts(qp) != 0) {
        fprintf(stderr, "[Client] QP 状态转换到 RTS 失败\n");
        goto cleanup;
    }
    printf("[Client] QP 状态: RTS\n");

    printf("[Client] 连接建立成功!\n");

    /* 发送消息 */
    struct ibv_sge sge;
    struct ibv_send_wr send_wr, *bad_wr = NULL;
    
    sge.addr = (uint64_t)(uintptr_t)buf;
    sge.length = strlen(buf) + 1;
    sge.lkey = mr->lkey;
    
    memset(&send_wr, 0, sizeof(send_wr));
    send_wr.wr_id = 1;
    send_wr.next = NULL;
    send_wr.sg_list = &sge;
    send_wr.num_sge = 1;
    send_wr.opcode = IBV_WR_SEND;
    send_wr.send_flags = IBV_SEND_SIGNALED;
    
    printf("[Client] 发送消息: %s\n", buf);
    
    if (ibv_post_send(qp, &send_wr, &bad_wr) != 0) {
        perror("ibv_post_send");
        goto cleanup;
    }

    /* 轮询 CQ */
    struct ibv_wc wc;
    while (ibv_poll_cq(cq, 1, &wc) == 0) {
        usleep(1000);
    }

    if (wc.status == IBV_WC_SUCCESS && wc.opcode == IBV_WC_SEND) {
        printf("[Client] 发送成功!\n");
    } else {
        printf("[Client] 发送失败: %s\n", ibv_wc_status_str(wc.status));
    }

    sleep(1);

cleanup:
    printf("[Client] 清理资源\n");
    
    if (sock >= 0) close(sock);
    if (qp) ibv_destroy_qp(qp);
    if (mr) ibv_dereg_mr(mr);
    if (buf) free(buf);
    if (cq) ibv_destroy_cq(cq);
    if (pd) ibv_dealloc_pd(pd);
    if (ctx) ibv_close_device(ctx);
    if (dev_list) ibv_free_device_list(dev_list);

    return 0;
}

4.5 Makefile

复制代码
CC = gcc
CFLAGS = -g -Wall -O2
LIBS = -libverbs

.PHONY: all clean

all: server client

server: rdma_server.c
	$(CC) $(CFLAGS) -o $@ $< $(LIBS)

client: rdma_client.c
	$(CC) $(CFLAGS) -o $@ $< $(LIBS)

clean:
	rm -f server client

4.6 编译程序

复制代码
# 保存上述代码到对应文件后,编译
cd ~/RDMA
make

# 或者单独编译
gcc -g -Wall -O2 -o server rdma_server.c -libverbs
gcc -g -Wall -O2 -o client rdma_client.c -libverbs

编译成功后,目录结构如下:

复制代码
RDMA/
├── rdma_server.c    # 服务端源码
├── rdma_client.c    # 客户端源码
├── Makefile         # 编译脚本
├── server           # 服务端可执行文件
└── client           # 客户端可执行文件

五、运行测试

5.1 单机测试(回环模式)

在同一台机器上打开两个终端:

终端 1 - 启动服务端:

复制代码
cd ~/RDMA
./server rxe0

终端 2 - 启动客户端:

服务端输出示例:

复制代码
[Server] 启动 (设备: rxe0)
[Server] 打开设备: rxe0
[Server] QP 创建成功, QPN: 0x000011
[Server] QP 状态: INIT
[Server] MR 注册成功, rkey: 0x000003cc
[Server] 监听端口 8888...
[Server] 客户端已连接
[Server] 收到客户端信息: QPN=0x000012, rkey=0x000004c9
[Server] QP 状态: RTR
[Server] QP 状态: RTS
[Server] 等待接收消息...

========================================
[Server] 收到消息: Hello RDMA! Soft-RoCE 连接成功!
========================================
[Server] 清理资源

客户端输出示例:

复制代码
[Client] 连接服务器: 127.0.0.1:8888 (设备: rxe0)
[Client] 打开设备: rxe0
[Client] QP 创建成功, QPN: 0x000012
[Client] QP 状态: INIT
[Client] MR 注册成功, rkey: 0x000004c9
[Client] 已连接到服务器
[Client] 收到服务端信息: QPN=0x000011, rkey=0x000003cc
[Client] QP 状态: RTR
[Client] QP 状态: RTS
[Client] 连接建立成功!
[Client] 发送消息: Hello RDMA! Soft-RoCE 连接成功!
[Client] 发送成功!
[Client] 清理资源

5.2 双机测试

如果两台机器都配置了 Soft-RoCE,可以进行跨机器测试:

服务器 A:

复制代码
# 查看本机IP
ip addr show

# 启动服务端
./server rxe0

服务器 B:

复制代码
# 连接服务器A(假设IP为192.168.1.100)
./client 192.168.1.100 rxe0

5.3 测试成功标志

测试成功的标志:

  1. 服务端显示 "QP 状态: RTS"
  2. 客户端显示 "QP 状态: RTS" 和 "连接建立成功!"
  3. 服务端收到消息:Hello RDMA! Soft-RoCE 连接成功!
  4. 客户端显示 "发送成功!"

六、程序执行流程详解

6.1 RDMA 核心概念

队列对(Queue Pair, QP):

  • RDMA 通信的基本单元
  • 包含发送队列(SQ)和接收队列(RQ)
  • 每个 QP 有唯一的 QPN(QP Number)

完成队列(Completion Queue, CQ):

  • 用于通知操作完成
  • 包含完成队列元素(CQE)
  • 支持轮询和事件通知两种模式

内存区域(Memory Region, MR):

  • 注册的内存缓冲区
  • 具有远程访问权限
  • 包含内存密钥(lkey/rkey)

保护域(Protection Domain, PD):

  • 资源隔离的容器
  • QP 和 MR 必须属于同一 PD

GID(Global Identifier):

  • RDMA 网络中的全局标识符
  • 用于 RoCE 网络中的路由

6.2 连接建立流程

复制代码
┌─────────────────────────────────────────────────────────────────┐
│                     连接建立时序图                               │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   Server                              Client                    │
│     │                                   │                       │
│     │  1. 打开设备,创建资源             │                       │
│     │  2. QP: RESET → INIT             │                       │
│     │  3. 监听TCP端口                   │                       │
│     │◄──────────────────────────────────│ TCP连接请求           │
│     │  4. 接受TCP连接                   │                       │
│     │                                   │                       │
│     │  5. 发送本地信息(QPN,GID,rkey)    │                       │
│     │──────────────────────────────────►│                       │
│     │                                   │                       │
│     │  6. 接收对端信息                   │                       │
│     │◄──────────────────────────────────│ 发送本地信息          │
│     │                                   │                       │
│     │  7. QP: INIT → RTR               │  7. QP: INIT → RTR    │
│     │     (配置对端QPN, GID)            │     (配置对端QPN, GID)│
│     │                                   │                       │
│     │  8. QP: RTR → RTS                │  8. QP: RTR → RTS     │
│     │     (设置发送参数)                │     (设置发送参数)    │
│     │                                   │                       │
│     │        RDMA连接建立完成!         │                       │
│     │                                   │                       │
│     │  9. ibv_post_recv()              │                       │
│     │                                   │  10. ibv_post_send() │
│     │◄──────────────────────────────────│  RDMA SEND操作       │
│     │  11. 轮询CQ,收到数据              │                       │
│     │                                   │  12. 轮询CQ,发送完成  │
│     │                                   │                       │
└─────────────────────────────────────────────────────────────────┘

6.3 QP 状态转换详解

复制代码
QP状态转换图:

RESET → INIT → RTR → RTS → SQD → SQE → ERROR
          ↑                           ↓
          └───────────────────────────┘

各状态说明:
┌─────────┬────────────────────────────────────────────────────┐
│ 状态    │ 说明                                               │
├─────────┼────────────────────────────────────────────────────┤
│ RESET   │ 初始状态,QP刚创建                                 │
│ INIT    │ 已初始化,可以发布接收请求,设置端口和访问权限       │
│ RTR     │ Ready to Receive,配置了对端信息,可以接收数据      │
│ RTS     │ Ready to Send,可以发送数据                        │
│ SQD     │ Send Queue Drained,发送队列已清空                 │
│ SQE     │ Send Queue Error,发送队列错误                     │
│ ERROR   │ 错误状态                                           │
└─────────┴────────────────────────────────────────────────────┘

关键状态转换参数:
1. RESET → INIT:
   - port_num: 端口号
   - pkey_index: 分区键索引
   - qp_access_flags: 访问权限 (IBV_ACCESS_REMOTE_WRITE/READ)

2. INIT → RTR:
   - dest_qp_num: 对端QP号
   - ah_attr.grh.dgid: 对端GID
   - path_mtu: 路径MTU
   - rq_psn: 接收PSN
   - max_dest_rd_atomic: 最大入站RDMA Read

3. RTR → RTS:
   - sq_psn: 发送PSN
   - max_rd_atomic: 最大出站RDMA Read
   - retry_cnt: 重试次数
   - rnr_retry: RNR重试次数
   - timeout: 超时参数 (Soft-RoCE必须设置!)

6.4 关键代码解析

1. 信息交换结构体

cpp 复制代码
struct {
    uint32_t qpn;      // QP号,用于对端配置RTR状态
    uint32_t rkey;     // 远程内存密钥,用于RDMA Read/Write
    uint64_t vaddr;    // 虚拟地址,用于RDMA操作的远程地址
    uint8_t gid[16];   // 全局标识符,用于RoCE路由
} local_info, remote_info;

2. QP 状态转换到 RTR 的关键配置

cpp 复制代码
attr.dest_qp_num = remote_qpn;           // 对端QP号
attr.ah_attr.is_global = 1;              // 使用全局路由(RoCE必须)
attr.ah_attr.grh.dgid = *remote_gid;     // 对端GID
attr.ah_attr.grh.sgid_index = 0;         // 本地GID索引

3. Soft-RoCE 的 timeout 设

cpp 复制代码
// Soft-RoCE必须设置timeout,否则RTS转换会失败
attr.timeout = 14;  // 约8.4秒超时 (4.096μs × 2^14)

6.5 RDMA Verbs API 详解

控制通路 API:

API 功能 说明
ibv_get_device_list() 获取设备列表 返回可用 RDMA 设备
ibv_open_device() 打开设备 获取设备上下文
ibv_alloc_pd() 分配 PD 创建保护域
ibv_create_cq() 创建 CQ 创建完成队列
ibv_create_qp() 创建 QP 创建队列对
ibv_modify_qp() 修改 QP 状态 状态转换
ibv_reg_mr() 注册 MR 注册内存区域
ibv_query_gid() 查询 GID 获取全局标识符

数据通路 API:

API 功能 说明
ibv_post_send() 发布发送请求 发起 RDMA 操作
ibv_post_recv() 发布接收请求 准备接收数据
ibv_poll_cq() 轮询 CQ 检查完成状态
ibv_wc_status_str() 状态字符串 错误信息转换

七、Soft-RoCE 性能优化

7.1 调整 MTU

复制代码
# 查看当前MTU
ip link show eth0

# 设置更大的MTU(需要网卡支持)
sudo ip link set eth0 mtu 9000

# 验证MTU设置
ip link show eth0

7.2 CPU 亲和性设置

复制代码
# 查看CPU核心
lscpu

# 设置进程CPU亲和性
taskset -c 0,1 ./server rxe0

7.3 内存锁定限制

复制代码
# 查看当前限制
ulimit -l

# 设置更大的内存锁定限制
ulimit -l unlimited

# 永久设置(添加到~/.bashrc)
echo "ulimit -l unlimited" >> ~/.bashrc

7.4 内核参数调优

复制代码
# 编辑sysctl配置
sudo tee -a /etc/sysctl.conf << EOF
# RDMA优化参数
net.core.rmem_max = 134217728
net.core.wmem_max = 134217728
net.core.rmem_default = 33554432
net.core.wmem_default = 33554432
net.core.netdev_max_backlog = 5000
EOF

# 应用配置
sudo sysctl -p

八、常见问题排查

8.1 问题:ibv_devices 显示 "Function not implemented"

原因: 系统中没有 RDMA 设备(硬件或 Soft-RoCE)

解决方案:

复制代码
# 1. 加载RDMA模块
sudo modprobe rdma_rxe

# 2. 创建RXE设备
sudo rxe_cfg add eth0

# 3. 验证
ibv_devices

8.2 问题:创建 RXE 设备失败

原因: 内核不支持或模块未加载

解决方案:

复制代码
# 检查内核版本(需要5.0+)
uname -r

# 检查模块是否存在
modinfo rdma_rxe

# 如果模块不存在,安装额外的内核模块
sudo apt-get install linux-modules-extra-$(uname -r)

8.3 问题:QP 状态转换失败

原因: Soft-RoCE 需要设置 timeout 参数

解决方案:

确保代码中包含 timeout 设置:

复制代码
attr.timeout = 14;  // Soft-RoCE必须设置!

8.4 问题:连接超时

原因: 网络配置或防火墙问题

解决方案:

复制代码
# 1. 检查网络连通性
ping 192.168.1.100

# 2. 检查防火墙
sudo ufw status
sudo ufw disable  # 临时关闭防火墙测试

# 3. 检查GID配置
ibv_devinfo -v

8.5 问题:权限不足

原因: 用户没有访问 RDMA 设备的权限

解决方案:

复制代码
# 方法1:添加用户到rdma组
sudo usermod -a -G rdma $USER
# 需要重新登录生效

# 方法2:修改设备权限
sudo chmod 666 /dev/infiniband/uverbs*

# 方法3:使用sudo运行
sudo ./server rxe0

8.6 问题:编译错误

原因: 缺少依赖

解决方案:

复制代码
# 安装完整的开发依赖
sudo apt-get install -y \
    build-essential \
    libibverbs-dev \
    librdmacm-dev

# 重新编译
make clean && make

九、RDMA 架构原理详解

9.1 控制通路与数据通路

RDMA 架构将操作分为两个独立的通路:

控制通路(Control Path):

复制代码
┌─────────────────────────────────────┐
│ 应用程序                             │
│   ↓ ibv_* API                       │
│ 用户态驱动 (libibverbs)              │
│   ↓ ioctl                           │
│ 内核态驱动 (ib_core)                 │
│   ↓                                 │
│ 硬件固件                             │
└─────────────────────────────────────┘

操作:设备打开、PD分配、MR注册、CQ/QP创建、QP状态修改
特点:低频操作,需要内核参与
目的:资源初始化和配置

数据通路(Data Path):

复制代码
┌─────────────────────────────────────┐
│ 应用程序                             │
│   ↓ ibv_post_send/poll_cq           │
│ 用户态驱动 (libibverbs)              │
│   ↓ 直接访问硬件寄存器               │
│ RDMA网卡硬件                         │
└─────────────────────────────────────┘

操作:数据发送、接收、完成通知
特点:高频操作,旁路内核
目的:零拷贝数据传输

9.2 分层架构图

复制代码
┌────────────────────────────────────────────────────────┐
│                     应用程序层                          │
│   RDMA应用程序 (使用Verbs API)                         │
├────────────────────────────────────────────────────────┤
│                     用户态层                            │
│ ┌──────────────────┐  ┌──────────────────────────┐     │
│ │   控制通路        │  │      数据通路            │     │
│ │ libibverbs       │  │ libibverbs              │     │
│ │ librdmacm        │  │ (直接访问硬件)           │     │
│ └──────────────────┘  └──────────────────────────┘     │
├────────────────────────────────────────────────────────┤
│                     内核态层                            │
│ ┌──────────────────────────────────────────────────┐   │
│ │          RDMA核心子系统 (ib_core)                 │   │
│ │  - 设备管理  - PD管理  - MR管理                   │   │
│ │  - CQ管理    - QP管理  - 安全控制                 │   │
│ └──────────────────────────────────────────────────┘   │
│ ┌──────────────────────────────────────────────────┐   │
│ │          Soft-RoCE驱动 (rdma_rxe)                 │   │
│ │  - 软件实现的RDMA设备                             │   │
│ │  - UDP封装 (RoCEv2)                              │   │
│ └──────────────────────────────────────────────────┘   │
├────────────────────────────────────────────────────────┤
│                     硬件层                              │
│          普通以太网卡 (Soft-RoCE)                       │
│          或 RDMA网卡 (真实硬件)                         │
└────────────────────────────────────────────────────────┘

9.3 Soft-RoCE 工作原理

复制代码
Soft-RoCE数据包封装 (RoCEv2):

┌─────────────────────────────────────────┐
│           IP Header                      │
│   Source/Dest IP Address                │
├─────────────────────────────────────────┤
│           UDP Header                     │
│   Source Port: 4791 (RoCE)              │
│   Dest Port: 4791                        │
├─────────────────────────────────────────┤
│           IB BTH (Base Transport Header) │
│   - Opcode (操作类型: SEND/WRITE/READ)  │
│   - PSN (包序列号)                       │
│   - QPN (队列对号)                       │
├─────────────────────────────────────────┤
│           IB RETH (RDMA Extended Header) │
│   - VA (虚拟地址)                        │
│   - Rkey (远程密钥)                      │
│   - DMA Length (数据长度)                │
├─────────────────────────────────────────┤
│           Payload (负载数据)             │
├─────────────────────────────────────────┤
│           ICRC (完整性校验)              │
└─────────────────────────────────────────┘

传输过程:
1. 应用程序调用ibv_post_send()
2. 用户态驱动构造WQE(工作队列元素)
3. Soft-RoCE驱动处理WQE,封装为RoCEv2数据包
4. 通过标准UDP/IP协议栈发送
5. 接收端Soft-RoCE驱动解析数据包
6. 写入目标内存区域
7. 生成CQE(完成队列元素)

0voice · GitHub

相关推荐
twc8292 小时前
大模型生成 QA Pairs 提升 RAG 应用测试效率的实践
服务器·数据库·人工智能·windows·rag·大模型测试
宇擎智脑科技2 小时前
A2A Python SDK 源码架构解读:一个请求是如何被处理的
人工智能·python·架构·a2a
lay_liu2 小时前
Linux安装redis
linux·运维·redis
虾..3 小时前
UDP协议
网络·网络协议·udp
uzong3 小时前
Harness Engineering 是什么?一场新的 AI 范式已经开始
人工智能·后端·架构
墨有6663 小时前
FieldFormer:基于物理场论的极简AI大模型底层架构,附带源码
人工智能·架构·电磁场算法映射
w-w0w-w3 小时前
Unix网络编程
服务器·网络·unix
未知鱼4 小时前
Python安全开发之子域名扫描器(含详细注释)
网络·python·安全·web安全·网络安全
寂柒4 小时前
序列化与反序列化
linux·网络