2. 第一个RDMA程序

任务目标

编写第一个RDMA程序,使用verbs API完成以下操作:枚举系统中的RDMA设备、查询每个设备的端口属性、打印端口的GID(Global ID)与端口状态,验证RDMA环境可用性。

环境依赖

  • Linux系统,已安装RDMA网卡(如Mellanox CX系列)或软RoCE环境
  • 安装rdma-core开发库:apt install libibverbs-dev rdma-core(Debian/Ubuntu)或 yum install libibverbs-devel rdma-core(RHEL/CentOS)
  • 编译需链接 -libverbs

关键完整代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <infiniband/verbs.h>

static const char *port_state_str(enum ibv_port_state state) {
    switch (state) {
        case IBV_PORT_DOWN:   return "DOWN";
        case IBV_PORT_ACTIVE: return "ACTIVE";
        default:              return "UNKNOWN";
    }
}

int main(void) {
    struct ibv_device **dev_list;
    struct ibv_context *ctx = NULL;
    struct ibv_port_attr port_attr;
    struct ibv_gid_entry gid_entry;
    int num_devices, i, j;

    dev_list = ibv_get_device_list(&num_devices);
    if (!dev_list) {
        perror("ibv_get_device_list");
        return 1;
    }

    if (num_devices == 0) {
        fprintf(stderr, "未发现RDMA设备,请检查驱动或硬件\n");
        ibv_free_device_list(dev_list);
        return 1;
    }

    printf("发现 %d 个RDMA设备:\n", num_devices);

    for (i = 0; i < num_devices; i++) {
        printf("\n设备 %d: %s (%s)\n", i,
               ibv_get_device_name(dev_list[i]),
               ibv_node_type_str(ibv_node_type(dev_list[i])));

        ctx = ibv_open_device(dev_list[i]);
        if (!ctx) {
            fprintf(stderr, "  无法打开设备: %s\n", strerror(errno));
            continue;
        }

        /* 查询端口1的属性(多数RNIC端口号为1) */
        if (ibv_query_port(ctx, 1, &port_attr) != 0) {
            fprintf(stderr, "  查询端口1属性失败\n");
            ibv_close_device(ctx);
            continue;
        }

        printf("  端口: %d\n", 1);
        printf("  状态: %s\n", port_state_str(port_attr.state));
        printf("  链路层: %s\n",
               (port_attr.link_layer == IBV_LINK_LAYER_INFINIBAND) ? "InfiniBand" :
               (port_attr.link_layer == IBV_LINK_LAYER_ETHERNET) ? "Ethernet" : "Unknown");
        printf("  LID: 0x%04x\n", port_attr.lid);
        printf("  速率: %d Gbps\n", port_attr.active_speed == IBV_SPEED_EDR ? 100 :
               port_attr.active_speed == IBV_SPEED_HDR ? 200 :
               port_attr.active_speed == IBV_SPEED_NDR ? 400 :
               port_attr.active_speed == IBV_SPEED_FDR ? 56 :
               port_attr.active_speed == IBV_SPEED_QDR ? 40 :
               port_attr.active_speed == IBV_SPEED_SDR ? 10 : 0);

        /* 枚举并打印GID(前4个) */
        printf("  GID列表:\n");
        for (j = 0; j < 4; j++) {
            if (ibv_query_gid_ex(ctx, 1, j, &gid_entry, 0) != 0)
                break;
            printf("    [%d] %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x\n",
                   j,
                   gid_entry.gid.raw[0], gid_entry.gid.raw[1],
                   gid_entry.gid.raw[2], gid_entry.gid.raw[3],
                   gid_entry.gid.raw[4], gid_entry.gid.raw[5],
                   gid_entry.gid.raw[6], gid_entry.gid.raw[7],
                   gid_entry.gid.raw[8], gid_entry.gid.raw[9],
                   gid_entry.gid.raw[10], gid_entry.gid.raw[11],
                   gid_entry.gid.raw[12], gid_entry.gid.raw[13],
                   gid_entry.gid.raw[14], gid_entry.gid.raw[15]);
        }

        ibv_close_device(ctx);
    }

    ibv_free_device_list(dev_list);
    return 0;
}

编译与运行

gcc -o rdma_query rdma_query.c -libverbs
sudo ./rdma_query   # 部分环境需要root权限访问设备

验证方法

  • 通过条件:程序能列出至少1个RDMA设备,端口状态显示为ACTIVE(若网卡已连接)或DOWN(未插线/未配置),且能打印出非全零的GID(通常index 0为全零,index 1及以上为有效RoCE GID)。
  • 交叉验证:运行系统命令 ibv_devinfo -v 对比输出,设备名、端口状态、LID和GID应一致。
  • 预期输出示例(具体值取决于硬件环境):
发现 1 个RDMA设备:

设备 0: mlx5_0 (InfiniBand)
  端口: 1
  状态: ACTIVE
  链路层: Ethernet
  LID: 0x0000
  速率: 100 Gbps
  GID列表:
    [0] 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00
    [1] fe:80:00:00:00:00:00:00:ec:0d:9a:03:fe:80:00:01

常见问题

  • ibv_get_device_list返回NULL:检查是否加载驱动模块(lsmod | grep rdma),或使用ibv_devinfo确认系统能看到设备。
  • ibv_query_gid_ex返回ENOSYS:旧版rdma-core不支持该API,可改用ibv_query_gid()(非_ex版本),但需自行处理GID index范围。
  • 权限不足:访问verbs设备通常需要root或CAP_NET_RAW权限,使用sudo运行即可。