4. 获取端口GID列表

任务目标

编写一个InfiniBand用户态程序,查询指定端口的GID表,打印每个GID的索引(index)与128位地址(GID)。GID是端口的全局标识符,由子网管理器(SM)分配,在RoCE/IB网络中用于寻址。

核心实现

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

int main(int argc, char *argv[]) {
    struct ibv_device **dev_list;
    struct ibv_context *ctx = NULL;
    struct ibv_port_attr port_attr;
    struct ibv_gid_entry gid_entries[128];
    int num_devices, num_gids;
    int dev_index = 0, port_num = 1;
    int i;

    // 解析命令行参数: [device_index] [port_num]
    if (argc > 1) dev_index = atoi(argv[1]);
    if (argc > 2) port_num = atoi(argv[2]);

    // 获取设备列表
    dev_list = ibv_get_device_list(&num_devices);
    if (!dev_list) {
        perror("ibv_get_device_list");
        return 1;
    }
    if (dev_index >= num_devices || dev_index < 0) {
        fprintf(stderr, "无效设备索引 %d (共 %d 个设备)\n", dev_index, num_devices);
        goto cleanup;
    }

    // 打开设备
    ctx = ibv_open_device(dev_list[dev_index]);
    if (!ctx) {
        perror("ibv_open_device");
        goto cleanup;
    }

    // 查询端口属性
    if (ibv_query_port(ctx, port_num, &port_attr)) {
        perror("ibv_query_port");
        goto cleanup;
    }

    // 查询GID表
    num_gids = ibv_query_gid_table(ctx, gid_entries, 128, port_num);
    if (num_gids < 0) {
        perror("ibv_query_gid_table");
        goto cleanup;
    }

    printf("设备: %s, 端口: %d, GID表长度: %d\n",
           ibv_get_device_name(dev_list[dev_index]), port_num, num_gids);

    // 打印每个GID
    for (i = 0; i < num_gids; i++) {
        char gid_str[INET6_ADDRSTRLEN] = {0};
        uint8_t *raw = gid_entries[i].gid.raw;

        // 格式化GID为IPv6风格字符串
        snprintf(gid_str, sizeof(gid_str),
                 "%02x%02x:%02x%02x:%02x%02x:%02x%02x:"
                 "%02x%02x:%02x%02x:%02x%02x:%02x%02x",
                 raw[0], raw[1], raw[2], raw[3],
                 raw[4], raw[5], raw[6], raw[7],
                 raw[8], raw[9], raw[10], raw[11],
                 raw[12], raw[13], raw[14], raw[15]);

        printf("GID[%2d] %s (所属端口: %d)\n",
               gid_entries[i].gid_index, gid_str,
               gid_entries[i].port_num);
    }

cleanup:
    if (ctx) ibv_close_device(ctx);
    ibv_free_device_list(dev_list);
    return 0;
}

编译与运行

# 编译(需要libibverbs-dev)
gcc -o list_gids list_gids.c -libverbs

# 运行(默认设备0,端口1)
./list_gids

# 指定设备索引和端口
./list_gids 1 2

验证方法

程序执行后,观察输出是否包含GID索引和完整地址。判定通过的依据:

  • 程序正常退出(返回码0),无错误信息
  • 输出中GID表长度大于0,且每个GID索引连续(0,1,2,...)
  • GID地址格式为8组16位十六进制数,每组以冒号分隔
  • 至少有一个GID的地址以fe80:开头(链路本地地址,由端口MAC生成)

若系统配置了RoCE或IB网络,还会看到以0000:开头的GID(对应RoCEv2的IP地址)或SM分配的全局GID。可用ibv_devinfo命令交叉验证端口GID数量。

关键说明

本程序使用ibv_query_gid_table()(verbs 1.2+ API),相比旧版ibv_query_gid()循环调用,一次即可获取完整GID表。GID索引0通常是链路本地地址,索引1及以上为SM分配或RoCEv2使用的全局地址。若设备不支持该API(返回ENOSYS),可改用循环调用ibv_query_gid()直到返回非0。