`ibv_get_device_list` 实现分析

基于 rdma-core 2410mlnx54 源码(libibverbs/)。

1. 概述

ibv_get_device_list() 是 libibverbs 向用户程序暴露的入口,用于返回当前系统上所有可用的 RDMA 设备列表。它本身并不直接打开设备,而是:

  1. 扫描内核暴露的 RDMA / uverbs 设备信息;
  2. 加载并匹配用户态 provider(驱动插件);
  3. 为每个匹配到的设备创建 struct ibv_device / struct verbs_device;
  4. 返回一个以 NULL 结尾的 struct ibv_device ** 数组。

用户最终通过 ibv_free_device_list() 释放该数组。


2. 函数调用路径(Call Graph)

2.1 API 入口与符号版本

源码位置:libibverbs/device.c:54

c 复制代码
LATEST_SYMVER_FUNC(ibv_get_device_list, 1_1, "IBVERBS_1.1",
                   struct ibv_device **,
                   int *num)
{
    struct ibv_device **l = NULL;
    struct verbs_device *device;
    static bool initialized;
    int num_devices;
    int i = 0;

    if (num)
        *num = 0;

    pthread_mutex_lock(&dev_list_lock);
    if (!initialized) {
        if (ibverbs_init())
            goto out;
        initialized = true;
    }

    num_devices = ibverbs_get_device_list(&device_list);
    if (num_devices < 0) {
        errno = -num_devices;
        goto out;
    }

    l = calloc(num_devices + 1, sizeof (struct ibv_device *));
    if (!l) {
        errno = ENOMEM;
        goto out;
    }

    list_for_each(&device_list, device, entry) {
        l[i] = &device->device;
        ibverbs_device_hold(l[i]);
        i++;
    }
    if (num)
        *num = num_devices;
out:
    pthread_mutex_unlock(&dev_list_lock);
    return l;
}
  • LATEST_SYMVER_FUNC 把这个实现导出为 ibv_get_device_list@IBVERBS_1.1(见 util/symver.h)。
  • 为了兼容老 ABI,还有一个 IBVERBS_1.0 的包装器:compat-1_0.c:231 的 COMPAT_SYMVER_FUNC(ibv_get_device_list, 1_0, ...),它内部调用新的 ibv_get_device_list(),再把结果包装成旧的 struct ibv_device_1_0 **。

2.2 顶层调用链

text 复制代码
ibv_get_device_list()
    │
    ├── ibverbs_init()               [libibverbs/init.c:666]
    │       ├── (可选) ibv_fork_init()
    │       ├── ibv_get_sysfs_path()
    │       ├── check_memlock_limit()
    │       └── 设置日志级别/日志文件
    │
    ├── ibverbs_get_device_list()    [libibverbs/init.c:551]
    │       │
    │       ├── find_sysfs_devs_nl() [libibverbs/ibdev_nl.c:191]
    │       │   └── 失败则回退到 find_sysfs_devs() [libibverbs/init.c:204]
    │       │
    │       ├── check_abi_version()  [libibverbs/init.c:467]
    │       │
    │       ├── 同步旧 device_list 与新 sysfs 列表
    │       │       └── same_sysfs_dev()
    │       │
    │       ├── try_all_drivers()    [libibverbs/init.c:532]
    │       │       └── try_drivers()
    │       │               └── try_driver()
    │       │                       ├── match_device()
    │       │                       │       ├── match_driver_id()
    │       │                       │       ├── match_name()
    │       │                       │       └── match_modalias_device()
    │       │                       └── ops->alloc_device()
    │       │
    │       ├── load_drivers()       [libibverbs/dynamic_driver.c:212]
    │       │       ├── read_config() -> 读取 IBV_CONFIG_DIR 下的 driver 配置
    │       │       └── dlopen() provider so
    │       │
    │       └── 再次 try_all_drivers()
    │
    └── 遍历 device_list,构造 ibv_device ** 数组
            └── ibverbs_device_hold()  [libibverbs/init.c:687]

2.3 关键辅助函数

函数 文件 作用
ibverbs_init() init.c:666 一次性库初始化(fork safe、sysfs 路径、memlock 检查、日志)
ibverbs_get_device_list() init.c:551 核心扫描与匹配逻辑
find_sysfs_devs_nl() ibdev_nl.c:191 通过 RDMA netlink 获取设备列表
find_sysfs_devs() init.c:204 sysfs 回退:遍历 /sys/class/infiniband_verbs
try_all_drivers() / try_drivers() init.c:532/439 为每个 sysfs 设备尝试所有 provider
try_driver() init.c:376 实例化一个 verbs_device
match_device() init.c:337 决定某个 provider 是否支持该设备
load_drivers() dynamic_driver.c:212 动态加载 provider so
ibverbs_device_hold/put() init.c:687/694 引用计数管理

3. 原理分析

ibv_get_device_list() 首先要知道系统里有哪些 RDMA 设备。它使用两条路径:

  1. netlink 优先 :find_sysfs_devs_nl() 通过 RDMA_NLDEV netlink 向内核查询设备名、节点类型、node_guid、端口数等。拿到每个设备后,再查询对应的 uverbs 字符设备名(find_uverbs_nl()),失败则回退到 sysfs(find_uverbs_sysfs())。
  2. sysfs 回退 :如果 netlink 不可用,走 find_sysfs_devs(),遍历 /sys/class/infiniband_verbs/ 下的每个 uverbs 设备节点,读取 ibdev、dev、abi_version、node_type 等属性。

无论哪条路径,最终都会生成一个 struct verbs_sysfs_dev 列表,描述内核侧的 RDMA 设备。

3.2 ABI 版本校验

拿到设备列表后,check_abi_version() 读取 /sys/class/infiniband_verbs/abi_version,并与用户态库编译时支持的 IB_USER_VERBS_MIN_ABI_VERSION / IB_USER_VERBS_MAX_ABI_VERSION 比较。如果不匹配,直接返回错误。这是为了防止用户态库跑在不兼容的内核接口上。

3.3 Provider 匹配机制

rdma-core 采用"核心 + provider 插件"的架构。每个 provider(mlx5、mlx4、efa、rxe 等)提供一组 struct verbs_device_ops,并在 so 加载时通过 verbs_register_driver() 注册到全局 driver_list。

try_driver() 为单个 sysfs 设备匹配 provider 的过程:

text 复制代码
match_device(ops, sysfs_dev)
    ├── match_driver_id()      // 优先:内核 netlink 报告的 driver_id
    ├── match_name()           // 按设备名,如 "mlx5_0"
    ├── match_modalias_device()// 按 /sys/class/infiniband/xxx/device/modalias
    └── ops->match_device()    // provider 自定义匹配函数(可选)

匹配成功后,调用 ops->alloc_device(sysfs_dev) 分配并初始化 struct verbs_device。verbs_device 的第一个成员是 struct ibv_device,因此可以安全地把 &verbs_device->device 当成 struct ibv_device * 返回给用户。

3.4 动态 Provider 加载

如果第一轮匹配没有成功(通常是因为对应 provider so 还没加载),ibverbs_get_device_list() 会调用 load_drivers():

  1. read_config() 读取 IBV_CONFIG_DIR(一般是 /etc/libibverbs.d/)下的 .driver 文件,收集要加载的 provider 名称;
  2. 也支持环境变量 RDMAV_DRIVERS / IBV_DRIVERS 覆盖;
  3. 对每个名称调用 load_driver(),按顺序尝试:绝对路径 → VERBS_PROVIDER_DIR → 系统库搜索路径,最终 dlopen("lib<name>-rdmav<abi>.so", RTLD_NOW)。

加载后,provider 的 PROVIDER_DRIVER() 宏定义的 constructor 函数会自动执行 verbs_register_driver(),把自身注册到 driver_list。随后再次 try_all_drivers() 完成匹配。

3.5 对象模型与引用计数

c 复制代码
struct verbs_device {
    struct ibv_device device;      // 必须放第一个,兼容 public API
    const struct verbs_device_ops *ops;
    atomic_int refcount;
    struct list_node entry;
    struct verbs_sysfs_dev *sysfs;
    uint64_t core_support;
};
  • ibv_get_device_list() 返回前会对每个设备调用 ibverbs_device_hold(),增加 refcount。
  • ibv_free_device_list() 遍历数组,调用 ibverbs_device_put() 减少引用计数;当计数归零时,释放 sysfs 并调用 provider 的 uninit_device()。
  • device_list 是全局静态链表,受 dev_list_lock 保护,保证多线程安全。

3.6 线程安全

ibv_get_device_list() 使用:

c 复制代码
static pthread_mutex_t dev_list_lock = PTHREAD_MUTEX_INITIALIZER;

整个设备扫描、匹配、构造数组的过程都在这把锁里完成。initialized 也是 static bool,同样在该锁保护下变成 true。因此并发调用时:

  • 只有一个线程会真正执行 ibverbs_init();
  • device_list 的增删是串行的;
  • 最终返回的数组对每个设备持有引用,用户线程可安全读取 ibv_device 的公开字段。

3.7 内存管理

ibv_get_device_list() 内部:

c 复制代码
l = calloc(num_devices + 1, sizeof (struct ibv_device *));

数组本身由调用者负责通过 ibv_free_device_list() 释放。ibv_free_device_list() 做两件事:

  1. 对每个元素调用 ibverbs_device_put();
  2. free(l) 释放数组。

注意:它只释放数组和引用计数,不会释放 ibv_device 对象本身------这些对象的生命周期由引用计数控制。


4. 小结

ibv_get_device_list() 的核心职责是把"内核侧的 RDMA 设备信息"转换成"用户侧可用的 struct ibv_device 数组"。它通过 sysfs/netlink 发现设备,通过 ABI 校验保证兼容性,通过 provider 匹配把具体硬件交给对应的用户态驱动,并通过引用计数和互斥锁管理对象生命周期与并发安全。

理解这一函数,有助于定位以下问题:

  • 为什么设备列表为空?------检查 sysfs/netlink 是否暴露设备、provider 是否加载、ABI 是否匹配。
  • 为什么出现某个设备的警告?------通常是 provider 未安装或 modalias / driver_id 不匹配。
  • 为什么内存泄漏或崩溃?------注意 ibv_free_device_list() 与引用计数的配对。
相关推荐
山东孙燕姿1 小时前
前端框架升级:不敢动的组件怎么办
大数据·运维·服务器·网络·人工智能·前端框架
hhhjhl9 小时前
Protobuf.js 跨端三方库仓颉适配全流程
javascript·网络·udp
ITxiaobing202311 小时前
中国公司海外广告投放的 IP 地址库选型:从“查归属”到“验真实”的工程视角
网络·网络协议·tcp/ip
KKKlucifer11 小时前
运营商非结构化文档安全管控体系建设实践
大数据·网络·安全
蒸鱼Yuzheng11 小时前
移动端 Overdraw 治理:从单帧热图到屏幕空间预算
性能优化·gpu·renderdoc·overdraw·移动端渲染
Eloudy12 小时前
MLNX_OFED 源码补齐、Debug/Release 构建命令与修改记录
gpu·rdma
网络小江13 小时前
组网第35课:静态路由——下一跳、出接口、默认路由、浮动静态与黑洞路由
服务器·网络·默认路由·黑洞路由·浮动静态路由
acrel713 小时前
医院用电隐患治理与节能降耗及智慧能源平台应用
网络·数据库
Escalating_xu14 小时前
【Linux 网络编程】应用层协议 HTTP(下):Header、连接管理与 C++ HTTP 服务器
linux·服务器·网络