1. 概述
Linux RDMA Core 位于 uverbs、内核消费者与硬件驱动之间。其用struct ib_device表示一个 RDMA 设备,以struct ib_device_ops连接 mlx5_ib 驱动,并为 PD、MR、CQ、QP 等资源提供通用对象、依赖计数和资源跟踪。用户态命令和内核 Verbs 最终都可以到达同一组驱动回调,但两条路径的对象入口、用户数据和清理规则不同。
RDMA 驱动创建设备对象:mlx5_ib 的mlx5r_probe()调用ib_alloc_device_with_net(),由 RDMA Core 的_ib_alloc_device()分配包含内嵌ib_device的完整mlx5_ib_dev对象。Device(设备管理)负责ib_device的注册、端口和设备引用;Verbs 通过显式设备参数或 PD、QP、CQ 等对象的device成员取得该设备,再使用内嵌的ib_device.ops分发到驱动。例如ib_create_qp_kernel()从pd->device取得设备,通用create_qp()再调用dev->ops.create_qp()。mlx5_ib 在初始化时通过ib_set_device_ops()安装操作表,并通过ib_register_device()发布设备。
RDMA Core 的关键作用可以归纳为四点:
- 设备抽象:为不同厂商驱动提供统一的
ib_device、端口、属性和注册状态。 - 操作分发:通过
ib_device_ops把通用 Verbs 请求分发到具体驱动。 - 对象管理:建立
ib_ucontext、PD、MR、CQ、QP 等通用对象及其依赖关系。 - 生命周期:使用设备引用、对象
usecnt、uverbs 对象引用和 restrack 引用维护生命周期。
2. 设备注册与注销
2.1 设备对象分配
mlx5_ib 的设备探测入口mlx5r_probe()使用ib_alloc_device_with_net(mlx5_ib_dev, ib_dev, net)分配struct mlx5_ib_dev。宏通过成员类型和偏移检查保证ib_dev位于驱动对象起始位置,再调用_ib_alloc_device()分配完整的驱动对象。这样,同一块内存既能以struct ib_device *交给 RDMA Core,也能在 mlx5_ib 内部还原为struct mlx5_ib_dev *。
_ib_alloc_device()此时只建立尚未发布的设备对象,主要完成以下工作:
- 按驱动对象大小分配并清零内存。
- 调用
rdma_restrack_init(),为各类可跟踪资源建立 XArray 根。 - 初始化内嵌的 Linux
struct device、网络命名空间和端口链表。 - 初始化事件、Client 数据、注销、CQ pool、cache 和子设备相关的锁、XArray、链表与 completion。
- 设置 legacy uverbs 命令能力掩码。
struct ib_device中与本文主线有关的成员如下。设备操作表、注册引用和 restrack 根分别承担分发、注销同步和资源可观察性。
structib_device{/* 执行 DMA 的底层 Linux 设备 */structdevice*dma_device;/* 由硬件驱动安装的操作回调和对象大小 */structib_device_opsops;/* RDMA 设备名称 */charname[IB_DEVICE_NAME_MAX];/* 端口属性、cache 与端口状态 */structib_port_data*port_data;/* Linux 设备模型与 RDMA Core 设备视图 */union{structdevicedev;structib_core_devicecoredev;};/* 全局设备 XArray 使用的设备索引 */u32 index;/* 按资源类型划分的 restrack 根数组 */structrdma_restrack_root*res;/* 非零表示设备仍处于可获取的注册状态 */refcount_trefcount;/* refcount 归零时唤醒注销流程 */structcompletionunreg_completion;/* 串行化并发注销与注册竞争 */structmutexunregistration_lock;};2.2 注册流程
mlx5_ib 在设备对象和操作表准备好后,调用ib_register_device()注册设备。
- 确定身份与能力。分配唯一名称和索引,取得设备及端口属性,为缓存和资源操作提供依据。
- 准备端口缓存。建立 GID、P_Key 和端口状态的初始视图;RoCE GID 随网络设备扫描及事件更新,为后续查询提供统一数据。
- 建立设备视图。接入 Linux 设备模型,准备 sysfs、统计和 cgroup 状态。暂缓 ADD 事件,避免用户空间收到通知时设备还未准备好。
- 发布并保护注册状态。设置
DEVICE_REGISTERED,建立基础引用和注册线程临时引用;用锁保护接入阶段,防止并发注销提前 remove 设备。 - 接入消费者。执行可选项、关联 Client,并按需建立网络命名空间兼容视图。Client 机制见 RDMA-Verbs-内核 uverbs 接口(五)。
- 通知就绪。发送设备 ADD 和 RDMA 注册等通知。
2.3 注销流程
mlx5_ib 注销流程和初始化流程相反:先注销设备,清理完全部资源后再调用ib_dealloc_device()。
- 保护清理过程。临时持有 Linux device 内存引用,并串行化设备注销过程,避免对象提前释放或被重复清理。
- 撤销注册资格。清除注册标记,解除 Client 关联并清理共用 CQ pool。
- 等待使用者退出。返还注册引用,等待所有注册引用归零,再移除兼容视图。
- 停止外部与后台活动。remove 设备、端口和 cgroup 视图,禁止 GID 更新并等待异步任务结束;缓存停止工作,但内存暂不释放。
- 交还分配引用。驱动清理完成后,dealloc 移除全局索引、检查资源是否遗留,并释放分配阶段持有的 Linux device 引用。
- 最终回收内存。最后一个内存引用触发
ib_device_release(),释放缓存等内部状态,再通过 RCU 延迟回收设备对象。因此,注销完成不等于内存立即释放。
3. 操作接口与分发
3.1ib_device_ops
struct ib_device_ops是 RDMA Core 与硬件驱动之间的主要接口。它既包含控制路径回调,也包含内核消费者使用的数据路径回调。
| 操作类别 | 代表回调 | RDMA Core 的主要工作 |
|---|---|---|
| 设备与端口 | query_device、query_port、get_port_immutable | 建立统一设备属性、端口属性和 link layer 视图 |
| 用户上下文 | alloc_ucontext、dealloc_ucontext、mmap、disassociate_ucontext | 关联 uverbs file、映射对象和设备移除状态 |
| 资源创建 | alloc_pd、create_cq、create_qp、reg_user_mr | 分配或接收通用对象,传递ib_udata,建立依赖并加入 restrack |
| 资源销毁 | dealloc_pd、destroy_cq、destroy_qp、dereg_mr | 检查依赖、调用驱动、解除引用并移除 restrack |
| 内核数据路径 | post_send、post_recv、poll_cq、req_notify_cq | 提供统一内核 Verbs 入口并分发到驱动实现 |
| 对象布局 | size_ib_pd、size_ib_cq、size_ib_qp、size_ib_ucontext | 决定 Core 分配的通用对象与驱动私有对象总大小 |
操作表中的重点声明如下。这里保留实际参数类型,用户态附加请求通过ib_udata或uverbs_attr_bundle进入驱动。
structib_device_ops{/* 查询设备和端口能力 */int(*query_device)(structib_device*device,structib_device_attr*device_attr,structib_udata*udata);int(*query_port)(structib_device*device,u32 port_num,structib_port_attr*port_attr);/* 建立和释放用户上下文 */int(*alloc_ucontext)(structib_ucontext*context,structib_udata*udata);void(*dealloc_ucontext)(structib_ucontext*context);void(*disassociate_ucontext)(structib_ucontext*ibcontext);/* 创建和释放 PD */int(*alloc_pd)(structib_pd*pd,structib_udata*udata);int(*dealloc_pd)(structib_pd*pd,structib_udata*udata);/* 创建、修改和销毁 QP */int(*create_qp)(structib_qp*qp,structib_qp_init_attr*qp_init_attr,structib_udata*udata);int(*modify_qp)(structib_qp*qp,structib_qp_attr*qp_attr,intqp_attr_mask,structib_udata*udata);int(*destroy_qp)(structib_qp*qp,structib_udata*udata);/* 创建和销毁 CQ */int(*create_cq)(structib_cq*cq,conststructib_cq_init_attr*attr,structuverbs_attr_bundle*attrs);int(*destroy_cq)(structib_cq*cq,structib_udata*udata);/* 注册和注销用户 MR */structib_mr*(*reg_user_mr)(structib_pd*pd,u64 start,u64 length,u64 virt_addr,intmr_access_flags,structib_dmah*dmah,structib_udata*udata);int(*dereg_mr)(structib_mr*mr,structib_udata*udata);/* 内核态请求提交和完成处理 */int(*post_send)(structib_qp*qp,conststructib_send_wr*send_wr,conststructib_send_wr**bad_send_wr);int(*post_recv)(structib_qp*qp,conststructib_recv_wr*recv_wr,conststructib_recv_wr**bad_recv_wr);int(*poll_cq)(structib_cq*cq,intnum_entries,structib_wc*wc);};3.2 操作表安装与对象大小
ib_device_ops不是由 mlx5_ib 一次性整体赋值,而是通过多张操作表逐步拼装。ib_set_device_ops()遍历传入操作表:非空回调只在目标槽位尚为空时写入,因此先安装的回调优先,后续操作表只能补齐空槽位,不能覆盖已有实现。owner、driver_id、uverbs_abi_ver和 uverbs 能力标志单独处理。
mlx5_ib 在设备注册前按以下顺序安装操作表:
mlx5_ib_stage_caps_init()根据硬件能力安装 SR-IOV、MW、XRC、DM 等可选操作表,再安装mlx5_ib_dev_ops,由主操作表补齐通用 Verbs 回调。MLX5_IB_STAGE_NON_DEFAULT_CB安装端口操作。普通设备使用mlx5_ib_dev_port_ops;raw Ethernet representor 使用mlx5_ib_dev_port_rep_ops。- RoCE 初始化按需安装 WQ 和 RWQ indirection table 操作。之后
ib_register_device()才检查必选回调并发布设备。
3.3 内核 Verbs 调用路径
内核消费者通过<rdma/ib_verbs.h>提供的 Kernel Verbs 调用 RDMA Core,不经过 uverbs。对外入口通常是宏或static inline函数:宏把KBUILD_MODNAME一并传给 Core,用于标记资源所属的内核模块;对象型接口则从 PD、QP、CQ 等对象的device成员找到ib_device,再访问其中的操作表。
资源管理接口会进入verbs.c的通用实现。以 QP 创建为例,ib_create_qp()调用ib_create_qp_kernel(),后者从pd->device取得设备并进入create_qp()。RDMA Core 按驱动登记的对象大小分配 QP,初始化通用成员和 restrack,再调用dev->ops.create_qp();mlx5_ib 安装在该槽位的mlx5_ib_create_qp()最终创建驱动私有资源和硬件 QP。驱动成功后,Core 再建立安全状态和 PD、CQ、SRQ 等父对象的usecnt依赖。
PD 和 CQ 采用相同结构:ib_alloc_pd()、ib_create_cq()分别进入__ib_alloc_pd()、__ib_create_cq(),完成通用对象初始化后调用device->ops.alloc_pd()、device->ops.create_cq()。
基础收发接口更短。ib_post_send()、ib_post_recv()和ib_poll_cq()在ib_verbs.h中以内联函数实现,直接从 QP 或 CQ 找到device->ops并调用驱动回调,不再进入verbs.c的对象生命周期流程。这样仍保留统一的 Kernel Verbs 和ib_device_ops抽象,同时减少数据路径上的额外调用层次。
4. 核心对象模型
4.1 device 与 ucontext
ib_device是所有资源的根。每次用户进程打开 uverbs 设备并执行 GET_CONTEXT,Core 通过ib_alloc_ucontext()按size_ib_ucontext分配通用/私有上下文,设置设备和 uverbs file,再由ib_init_ucontext()调用ops.alloc_ucontext()。成功后,context 加入 restrack,并以 release store 发布到file->ucontext。
structib_ucontext{/* context 所属 RDMA 设备 */structib_device*device;/* 拥有该 context 的 uverbs 文件 */structib_uverbs_file*ufile;/* 用户 mmap 对象索引 */structxarraymmap_xa;/* CTX 类型的资源跟踪项 */structrdma_restrack_entryres;};内核消费者不创建ib_ucontext,而是直接持有ib_device及其资源对象。ib_ucontext因此表示用户文件与驱动用户上下文之间的关系,不是所有内核 RDMA 资源的共同父对象。
4.2 PD、MR、CQ 与 QP
PD 建立访问保护域。MR 和 QP 引用 PD;QP 还引用 send CQ、recv CQ,以及可选的 SRQ 和 RWQ indirection table。CQ 可以被多个 QP 共享,因此对象关系是带共享边的有向图,不是单一所有权树。
structib_pd{/* PD 所属设备 */structib_device*device;/* 用户 PD 对应的 uverbs 对象;内核 PD 为空 */structib_uobject*uobject;/* MR、QP、AH 等子资源使用计数 */atomic_tusecnt;/* PD 类型的资源跟踪项 */structrdma_restrack_entryres;};structib_cq{/* CQ 所属设备 */structib_device*device;/* 用户 CQ 的 uverbs 包装;内核 CQ 为空 */structib_ucq_object*uobject;/* 引用该 CQ 的工作队列数量 */atomic_tusecnt;/* CQ 类型的资源跟踪项 */structrdma_restrack_entryres;};structib_qp{/* QP 所属设备和保护域 */structib_device*device;structib_pd*pd;/* 发送和接收完成队列,可以指向同一 CQ */structib_cq*send_cq;structib_cq*recv_cq;/* 可选共享接收队列 */structib_srq*srq;/* QP 自身被 open、multicast 或 flow 使用的计数 */atomic_tusecnt;/* 用户 QP 的 uverbs 包装;内核 QP 为空 */structib_uqp_object*uobject;/* 驱动创建后给出的 QP number */u32 qp_num;/* QP 类型的资源跟踪项 */structrdma_restrack_entryres;};structib_mr{/* MR 所属设备和保护域 */structib_device*device;structib_pd*pd;/* WQE 使用的本地和远端 key */u32 lkey;u32 rkey;/* RDMA 地址范围 */u64 iova;u64 length;/* 用户 MR 对应的 uverbs 对象 */union{structib_uobject*uobject;structlist_headqp_entry;};/* MR 类型的资源跟踪项 */structrdma_restrack_entryres;};4.3 用户对象与驱动对象
用户句柄指向通用对象,驱动私有对象内嵌通用对象;句柄事务细节见uverbs 接口(五):
ib_uobject保存用户 handle、uverbs file、context、通用对象指针及句柄访问引用。ib_pd、ib_cq、ib_qp、ib_mr保存跨驱动统一的语义和对象关系。mlx5_ib_pd、mlx5_ib_cq、mlx5_ib_qp、mlx5_ib_mr保存 mlx5 私有状态,并以内嵌的通用对象作为 Core 入口。- PDN、CQN、QPN 和 MKey 等设备标识由 mlx5_ib 回调建立,不能与用户 handle 或内核指针互换。
下图汇总对象之间的关系。实线表示 Core 保存的直接引用,虚线表示包装或索引关系;usecnt沿子对象到父对象增加,销毁时按相反方向解除。
4.4 对象依赖关系
Core 在对象成功创建后建立依赖计数。普通 QP 调用ib_qp_usecnt_inc()时分别增加 PD、send CQ、recv CQ、SRQ 和 RWQ indirection table 的usecnt;成功销毁后由ib_qp_usecnt_dec()减少引用计数。send CQ 与 recv CQ 相同时会分别增加两次,也会在销毁时分别减少两次。
MR 注册成功后增加所属 PD 的usecnt,注销成功后减少。PD 销毁入口要求所有子资源已经同步销毁;用户对象路径中的uverbs_free_pd()检查pd->usecnt,非零时返回-EBUSY。CQ 销毁由ib_destroy_cq_user()检查cq->usecnt。QP 的usecnt则保护 open QP、multicast attachment、flow 等直接依赖,父对象依赖由单独的ib_qp_usecnt_inc()/ib_qp_usecnt_dec()维护。
这些计数只表达对象之间的使用关系,不代替设备注册引用或 restrack 引用:前者保护设备的注册可用状态,后者保证资源观察期间的元数据访问。
5. 总结
RDMA Kernel Core 以ib_device作为设备与资源的根,以ib_device_ops作为通用语义和硬件实现之间的分发边界。mlx5_ib 分配包含ib_device的驱动对象,根据硬件能力分批安装操作表并发起注册;Core 负责建立设备名称、端口、cache、Linux 设备模型和可见状态。注销沿相反方向撤销这些状态,先阻止新的使用,再等待已有访问退出。
内核消费者通过 Kernel Verbs 使用 RDMA Core。PD、CQ、QP 等资源管理接口进入verbs.c完成通用对象分配、成员初始化和依赖建立,再通过ib_device_ops调用 mlx5_ib;ib_post_send()、ib_post_recv()和ib_poll_cq()等基础收发接口则从对象定位设备并直接分发驱动回调。PD、CQ、MR 和 QP 构成带共享关系的依赖图,用户对象由ib_ucontext和ib_uobject包装,mlx5_ib 私有对象以内嵌通用对象接入 Core。用户 handle、内核对象指针与 QPN、CQN、MKey 等硬件标识分别属于不同管理层次。
6. 参考资料
6.1 源码地图
| 功能 | 源码位置 | 重点内容 |
|---|---|---|
| 设备分配、注册与注销 | linux/drivers/infiniband/core/device.c | _ib_alloc_device()、ib_register_device()、enable_device_and_get()、disable_device()、ib_unregister_device()、ib_set_device_ops() |
| 设备缓存与 sysfs 属性 | linux/drivers/infiniband/core/cache.c、linux/drivers/infiniband/core/sysfs.c | GID/P_Key 缓存初始化与清理、设备统计属性、端口与 GID 属性发布 |
| 通用 Verbs 对象 | linux/drivers/infiniband/core/verbs.c | PD、AH、SRQ、QP、CQ、MR 的创建、依赖计数、销毁和回滚 |
| uverbs 对象生命周期 | linux/drivers/infiniband/core/rdma_core.c | ib_uobject的分配、查找、commit、abort、destroy 与文件清理 |
| 用户上下文入口 | linux/drivers/infiniband/core/uverbs_cmd.c | ib_alloc_ucontext()、ib_init_ucontext()与 GET_CONTEXT |
| 标准对象 handler | linux/drivers/infiniband/core/uverbs_std_types.c、uverbs_std_types_qp.c、uverbs_std_types_cq.c | 用户对象创建后的 Core 调用、usecnt检查和销毁入口 |
| 资源跟踪 | linux/drivers/infiniband/core/restrack.c、linux/include/rdma/restrack.h | restrack 类型、XArray、kref、添加、查找、同步与删除 |
| 通用对象与操作表定义 | linux/include/rdma/ib_verbs.h | ib_device、ib_device_ops、ib_ucontext、PD、MR、CQ、QP 及对象大小宏 |
| mlx5_ib 操作表与注册入口 | linux/drivers/infiniband/hw/mlx5/main.c | mlx5_ib_dev_ops、能力操作表拼接、mlx5_ib_stage_ib_reg_init()与清理 stage |
6.2 相关文档
- 《RDMA-Verbs-软件架构(二)》:RDMA Core 在整体软件栈中的位置及四类路径。
- 《RDMA-Verbs-API 和数据结构(三)》:公开 Verbs API、用户态对象及 libibverbs 分发。
- 《RDMA-Verbs-MLX5 用户态库(四)》:mlx5 provider 私有对象、队列内存和用户态数据路径。
- 《RDMA-Verbs-内核 uverbs 接口(五)》:Client 接入、ABI、命令属性、用户句柄和文件关闭处理。