4、设备管理:ibv_get_device_list、ibv_open_device、ibv_query_device、ibv_query_port
好,咱们正式开始动手操作 RDMA 设备了。
这一章,我带你过一遍最基础的设备管理流程。说白了,就是四步走:找设备 → 打开设备 → 查设备属性 → 查端口属性。这四个 API,你写任何 RDMA 程序都绕不开。
4.1 先找到设备:ibv_get_device_list
你想想看,一台机器上可能插着多张 RDMA 网卡。比如 Mellanox ConnectX-5、ConnectX-6,甚至还有软 RoCE。第一步,你得知道有哪些设备可用。
ibv_get_device_list 就是干这个的。它返回一个 struct ibv_device ** 数组,每个元素指向一个设备描述符。
struct ibv_device **dev_list;
int num_devices;
dev_list = ibv_get_device_list(&num_devices);
if (!dev_list) {
fprintf(stderr, "获取设备列表失败\n");
return -1;
}
printf("发现 %d 个 RDMA 设备\n", num_devices);
for (int i = 0; i < num_devices; i++) {
printf(" 设备 %d: %s - %s\n",
i,
ibv_get_device_name(dev_list[i]),
ibv_get_device_description(dev_list[i]));
}
4.2 打开设备:ibv_open_device
找到设备了,下一步就是打开它。ibv_open_device 返回一个 struct ibv_context *,这个 context 就是后续所有操作的“通行证”。
struct ibv_context *ctx;
ctx = ibv_open_device(dev_list[0]);
if (!ctx) {
fprintf(stderr, "打开设备失败\n");
goto cleanup;
}
printf("设备已打开,设备句柄: %p\n", (void *)ctx);
ibv_free_device_list(dev_list) 释放设备列表。但要注意顺序——先打开设备,再释放列表。因为 ibv_open_device 内部会引用设备列表中的信息,你提前释放了,context 可能就坏了。
嗯,这里要特别提一下。我见过不少新手踩这个坑:先释放列表,再打开设备,结果程序莫名其妙崩溃。你想想看,context 内部还指着列表里的内存呢,你把它 free 了,不崩才怪。
4.3 查设备属性:ibv_query_device
设备打开了,接下来得看看它到底能干啥。ibv_query_device 返回一个 struct ibv_device_attr 结构体,里面塞满了设备的能力信息。
struct ibv_device_attr device_attr;
int ret = ibv_query_device(ctx, &device_attr);
if (ret) {
fprintf(stderr, "查询设备属性失败, ret=%d\n", ret);
goto cleanup;
}
printf("设备属性:\n");
printf(" FW 版本: %s\n", device_attr.fw_ver);
printf(" 节点 GUID: 0x%016llx\n", (unsigned long long)device_attr.node_guid);
printf(" 系统镜像 GUID: 0x%016llx\n", (unsigned long long)device_attr.sys_image_guid);
printf(" 最大 QP 数: %u\n", device_attr.max_qp);
printf(" 最大 CQ 数: %u\n", device_attr.max_cq);
printf(" 最大 MR 数: %u\n", device_attr.max_mr);
printf(" 最大 PD 数: %u\n", device_attr.max_pd);
printf(" 最大 SGE 数: %u\n", device_attr.max_sge);
这个结构体里字段非常多。我个人最关注的是 max_qp 和 max_sge。为什么?因为这两个参数直接决定了你能创建多少个连接、每个请求能挂多少个数据段。
max_qp 被用光了。每个连接占一个 QP,连接数一多,设备就扛不住了。后来我改成在初始化时先查一下 max_qp,根据剩余量动态控制连接数。
4.4 查端口属性:ibv_query_port
设备属性查完了,但还不够。RDMA 设备通常有多个端口(Port),每个端口的状态、速率、MTU 都可能不同。ibv_query_port 就是用来查这个的。
struct ibv_port_attr port_attr;
int port_num = 1; // 端口号从 1 开始
ret = ibv_query_port(ctx, port_num, &port_attr);
if (ret) {
fprintf(stderr, "查询端口 %d 属性失败\n", port_num);
goto cleanup;
}
printf("端口 %d 属性:\n", port_num);
printf(" 状态: %s\n",
port_attr.state == IBV_PORT_ACTIVE ? "ACTIVE" : "DOWN");
printf(" 物理层: %s\n",
port_attr.phys_state == IBV_PORT_PHYS_STATE_LINK_UP ? "LINK UP" : "LINK DOWN");
printf(" 速率: %u Gbps\n", port_attr.active_speed);
printf(" MTU: %u\n", port_attr.active_mtu);
printf(" LID: 0x%04x\n", port_attr.lid);
printf(" SM LID: 0x%04x\n", port_attr.sm_lid);
这里有个关键点:端口号从 1 开始,不是 0。我第一次写的时候习惯性地传了 0,结果 ibv_query_port 返回错误,我还以为是设备坏了。
state 不是 IBV_PORT_ACTIVE,或者 phys_state 不是 IBV_PORT_PHYS_STATE_LINK_UP,那就说明网线没插好或者对端没启动。这时候直接报错退出,比等到建连时再报错要友好得多。
4.5 完整流程示例
把上面四步串起来,就是一个完整的设备初始化流程:
#include <infiniband/verbs.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
struct ibv_device **dev_list;
struct ibv_context *ctx;
struct ibv_device_attr dev_attr;
struct ibv_port_attr port_attr;
int num_devices, ret;
// 1. 获取设备列表
dev_list = ibv_get_device_list(&num_devices);
if (!dev_list || num_devices == 0) {
fprintf(stderr, "没有找到 RDMA 设备\n");
return -1;
}
printf("发现 %d 个设备\n", num_devices);
// 2. 打开第一个设备
ctx = ibv_open_device(dev_list[0]);
if (!ctx) {
fprintf(stderr, "打开设备失败\n");
ibv_free_device_list(dev_list);
return -1;
}
// 打开成功后,可以释放设备列表了
ibv_free_device_list(dev_list);
// 3. 查询设备属性
ret = ibv_query_device(ctx, &dev_attr);
if (ret) {
fprintf(stderr, "查询设备属性失败\n");
ibv_close_device(ctx);
return -1;
}
printf("设备: %s, FW: %s, 最大QP: %u\n",
ibv_get_device_name(ctx->device),
dev_attr.fw_ver,
dev_attr.max_qp);
// 4. 查询端口属性
ret = ibv_query_port(ctx, 1, &port_attr);
if (ret) {
fprintf(stderr, "查询端口属性失败\n");
ibv_close_device(ctx);
return -1;
}
printf("端口1: %s, 速率: %u Gbps\n",
port_attr.state == IBV_PORT_ACTIVE ? "ACTIVE" : "DOWN",
port_attr.active_speed);
// 5. 清理
ibv_close_device(ctx);
return 0;
}
4.6 常见错误与排查
| 错误现象 | 可能原因 | 排查方法 |
|---|---|---|
ibv_get_device_list 返回 NULL |
内核模块未加载,或没有 RDMA 设备 | 执行 ibv_devinfo 确认设备是否存在 |
ibv_open_device 失败 |
权限不足,或设备已被其他进程独占打开 | 检查是否以 root 运行,或使用 lsof 查看谁占用了设备 |
ibv_query_port 返回端口 DOWN |
网线未插,或对端设备未启动 | 检查物理连接,确认对端链路状态 |
ibv_query_device 返回错误 |
设备 context 已损坏,或设备被移除 | 重新打开设备,检查硬件是否正常 |
好了,设备管理这块就讲到这里。下一章,咱们开始聊 PD(保护域)和 MR(内存注册)。这两个概念,说白了就是 RDMA 的“权限管理”和“内存映射”。到时候我会结合一个实际的内存注册例子,带你一步步搞明白。