news 2026/10/22 2:27:46

RDMA-Verbs-API 和数据结构(三)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
RDMA-Verbs-API 和数据结构(三)

1. 概述

libibverbs 向应用提供一套操作 RDMA 设备的 Verbs API。应用先打开设备,创建 PD、MR、CQ 和 QP,再用 WR 描述要提交的工作,用 SGE 指定本地数据范围,最后从 CQ 取得 WC,判断工作结果并回收缓冲区。

从 Verbs API 到 mlx5 provider,可以按职责分为三层:公开 API 层、libibverbs 通用处理与分发层、provider 接口层。公开层包含动态库导出函数、头文件 inline 函数和宏适配入口。通用层处理参数适配、资源公共字段、操作表选择,以及事件计数和确认状态;provider 接口层接收设备相关的资源或收发请求。

下图展示设备与资源、扩展适配、收发与完成三类 API 的库内处理和分发流程。实线表示调用方向。应用创建的属性和 WR 向下传递,创建结果和 WC 返回应用。

2. Verbs API 分类

2.1 设备访问接口

访问设备前要先枚举和打开设备。设备名、端口和 GID index 取决于本机配置。基础查询使用ibv_query_device(),扩展能力使用ibv_query_device_ex()。

接口定义功能
struct ibv_device **ibv_get_device_list(int *num_devices);枚举本地可用 RDMA 设备,返回以 NULL 结尾的设备指针数组,并通过num_devices返回数量。应用从中选择设备;返回 NULL 表示失败,数量为 0 表示没有可用设备。
void ibv_free_device_list(struct ibv_device **list);释放枚举得到的设备列表。应用成功打开所选设备后即可释放列表,已打开的 context 持有设备引用。
struct ibv_context *ibv_open_device(struct ibv_device *device);打开指定设备,返回供资源创建和能力查询使用的 context;失败返回 NULL。
int ibv_close_device(struct ibv_context *context);关闭一次设备打开所得到的context。应用先释放该context下的通信资源,再关闭它,并检查返回值;成功返回 0。
const char *ibv_get_device_name(struct ibv_device *device);返回指定 RDMA 设备的名称,供应用选择本地设备;应用在设备列表有效时读取名称。
int ibv_query_device(struct ibv_context *context, struct ibv_device_attr *device_attr);查询设备属性和资源上限,写入device_attr。应用在调用成功后据此选择 QP 深度、SGE 数量等参数。
int ibv_query_device_ex(struct ibv_context *context, const struct ibv_query_device_ex_input *input, struct ibv_device_attr_ex *attr);查询包含扩展能力的设备属性,写入attr。应用通过input提供查询输入,并根据成功返回的能力决定是否启用扩展功能。
int ibv_query_port(struct ibv_context *context, uint8_t port_num, struct ibv_port_attr *port_attr);查询指定本地端口的状态、链路类型、MTU 和 LID 等属性,写入port_attr。应用据此选择端口并配置通信路径。
int ibv_query_gid(struct ibv_context *context, uint8_t port_num, int index, union ibv_gid *gid);查询本地端口上指定索引的 GID,写入gid。RoCE 应用选择与网络配置匹配的索引,不能把示例索引当成固定值。

2.2 资源管理接口

基础资源按context → PD / CQ → MR / QP创建,QP 分别关联发送与接收 CQ;ibv_alloc_buf()、ibv_reg_buf_mr()是可选的 Buffer 分配与注册接口。

接口定义功能
struct ibv_pd *ibv_alloc_pd(struct ibv_context *context);在指定context下分配 PD。应用将该 PD 传给创建 QP 和注册 MR 接口,限定 QP 对注册内存的访问范围。
int ibv_dealloc_pd(struct ibv_pd *pd);释放 PD。应用先释放关联的 QP、MR 等资源,再调用本接口;有资源依赖时可能失败。
struct ibv_mr *ibv_reg_mr(struct ibv_pd *pd, void *addr, size_t length, int access);将应用指定的地址范围按access权限注册到关联 PD 的 MR,返回包含lkey、rkey的 MR 对象。应用随后使用lkey填写本地 SGE,并按需自行向远端交换 RDMA 地址和rkey。
int ibv_dereg_mr(struct ibv_mr *mr);注销 MR。应用先停止提交请求并等待在途请求结束;注销成功后才能释放底层内存,接口本身不释放应用分配的 buffer。
void *ibv_alloc_buf(struct ibv_pd *pd, size_t size, struct ibv_buf **buf);分配指定 PD 下、大小为size的 buffer,返回 CPU 可访问地址,并通过buf返回管理句柄。应用保存地址和句柄,内存注册需另行调用注册接口。
void ibv_free_buf(struct ibv_buf *buf);释放由 buffer 分配接口创建的 buffer。应用先注销关联 MR,再传入分配时取得的句柄。
struct ibv_mr *ibv_reg_buf_mr(struct ibv_pd *pd, struct ibv_buf *buf, void *addr, size_t length, int access);将buf中由addr、length指定的子范围注册为 MR;pd必须与分配 buffer 时一致,access指定访问权限。应用检查返回的 MR 指针并保存 key;等待相关工作结束后,用ibv_dereg_mr()注销 MR,注销成功后再释放对应 buffer。
struct ibv_cq *ibv_create_cq(struct ibv_context *context, int cqe, void *cq_context, struct ibv_comp_channel *channel, int comp_vector);在context下创建完成队列,返回 CQ。应用指定容量cqe、关联指针cq_context、可选channel和 completion vector,再将 CQ 传给 QP 创建接口。
int ibv_destroy_cq(struct ibv_cq *cq);销毁 CQ。应用先销毁引用它的 QP,并处理、确认相关事件;销毁成功后 CQ 指针失效,关联 channel 不再被其他 CQ 引用时释放。扩展 CQ 先用ibv_cq_ex_to_cq()取得公开 CQ 视图,再传给本接口。
struct ibv_qp *ibv_create_qp(struct ibv_pd *pd, struct ibv_qp_init_attr *qp_init_attr);在 PD 下创建 QP。应用通过qp_init_attr指定发送与接收 CQ、类型、容量和完成策略;成功返回 QP,并回填实际容量,随后由应用配置通信状态。
int ibv_destroy_qp(struct ibv_qp *qp);销毁普通或扩展 QP,解除其资源引用;成功后 QP 指针失效。应用在清理连接时先停止新请求并等待在途工作结束,再调用本接口。
int ibv_modify_qp(struct ibv_qp *qp, struct ibv_qp_attr *attr, int attr_mask);按attr_mask选中的字段修改 QP 属性。RC 应用依次配置 INIT、RTR、RTS,每次返回 0 后才进行下一步;失败返回错误码。
int ibv_query_qp(struct ibv_qp *qp, struct ibv_qp_attr *attr, int attr_mask, struct ibv_qp_init_attr *init_attr);查询 QP,将attr_mask指定的属性写入attr,并将初始属性写入init_attr。应用检查返回值后读取查询结果。

2.3 请求提交接口

发送接口向 SQ 提交工作,接收接口向 RQ 发布可接收的缓冲区。

接口定义功能
int ibv_post_send(struct ibv_qp *qp, struct ibv_send_wr *wr, struct ibv_send_wr **bad_wr);提交应用准备的 Send WR 链表,描述 SEND、RDMA READ/WRITE 等工作。返回 0 表示提交成功;失败时bad_wr指向第一条未提交的 WR。
int ibv_post_recv(struct ibv_qp *qp, struct ibv_recv_wr *wr, struct ibv_recv_wr **bad_wr);提交应用准备的 Receive WR 链表,指定允许接收写入的本地缓冲区。应用在开始通信前提交本地缓冲区;失败时通过bad_wr找到第一条处理的 WR。

普通 RC QP 支持的典型操作有以下差异,表中本地 SGE 均描述发起端的本地内存。

发送 opcode本地 SGE 的角色远端需要提供什么远端 RQ 与完成事件
IBV_WR_SEND数据源可用的 Receive WR消耗 Receive WR,数据进入其 SGE,产生接收完成事件
IBV_WR_RDMA_WRITE数据源目标 RDMA 地址、rkey和写权限普通 WRITE 不消耗 Receive WR,也不产生远端接收 WC
IBV_WR_RDMA_READ数据目的地源 RDMA 地址、rkey和读权限不消耗 Receive WR;发起端根据完成情况读取结果
IBV_WR_RDMA_WRITE_WITH_IMM写入远端 MR 的数据源WRITE 所需参数,以及可用的 Receive WR消耗 Receive WR 来报告 immediate 通知;数据写入远端 MR 指定范围

创建 QP 时,若将sq_sig_all设为非零(设置 WQE Ctrl Segment 的 CE字段),发送队列中的每条请求成功执行后都会在发送 CQ 中产生完成记录;若设为 0,则只有send_flags包含IBV_SEND_SIGNALED的 Send WR 在成功执行后产生完成记录。应用通过ibv_poll_cq()获取对应的 WC。接收请求不使用这一标志,完成后会在接收 CQ 中产生完成记录。

2.4 完成事件接口

完成事件通知通道(completion channel)为应用提供获取 CQ 完成事件的通道;完成事件的具体信息则通过轮询 CQ 获取,以 WC 返回。

事件模式下,应用先创建 channel,在创建 CQ 时关联该 channel,再调用ibv_req_notify_cq(cq, 0)设置一次性通知请求。首次等待前,先调用ibv_poll_cq()取出已有完成事件,直到返回 0。随后调用ibv_get_cq_event()等待事件;获取事件后,确认事件并重新请求通知,再循环调用ibv_poll_cq()处理所有完成事件,直到返回 0,然后等待下一次事件。一次完成事件可能对应多条 WC,也可能在轮询时没有 WC。

异步事件通过 context 的async_fd获取,用于报告端口、QP 和 CQ 等资源的状态变化。收到 CQ 错误等异常事件后,应用应执行恢复或退出流程,不能将尚未确认完成的请求视为成功并回收其缓冲区。

接口定义功能
struct ibv_comp_channel *ibv_create_comp_channel(struct ibv_context *context);在context下创建完成通知通道,成功返回 channel,失败返回 NULL。应用在创建 CQ 时关联该 channel,以便等待 CQ 通知。
int ibv_destroy_comp_channel(struct ibv_comp_channel *channel);销毁完成通知通道,成功返回 0;仍有关联 CQ 时返回EBUSY。应用先销毁关联 CQ,再销毁channel。
int ibv_req_notify_cq(struct ibv_cq *cq, int solicited_only);为 CQ 设置一次性通知请求,成功返回 0,失败返回错误码。应用通常将solicited_only设为 0;每次获取事件后,重新请求通知,再轮询 CQ。
int ibv_get_cq_event(struct ibv_comp_channel *channel, struct ibv_cq **cq, void **cq_context);从channel取得一个完成事件,返回关联的 CQ 和应用指针cq_context;成功返回 0,失败返回 -1。应用记录并确认该事件,重新请求通知,再轮询返回的 CQ。
void ibv_ack_cq_events(struct ibv_cq *cq, unsigned int nevents);确认已经成功取得的 CQ 事件。应用按实际获取次数填写nevents,可以累计后批量确认;事件数与 WC 数量没有一一对应关系。
int ibv_poll_cq(struct ibv_cq *cq, int num_entries, struct ibv_wc *wc);读取 CQ 中的 CQE,最多向应用数组写入num_entries个 WC。返回正数为数量,0 表示当前没有完成,负数表示失败;应用逐条先检查status,再处理成功完成的其他字段,并继续轮询直到返回 0。
int ibv_get_async_event(struct ibv_context *context, struct ibv_async_event *event);取得context上的异步事件并写入event,成功返回 0,失败返回 -1。应用根据事件类型处理端口、QP 或 CQ 状态变化。
void ibv_ack_async_event(struct ibv_async_event *event);确认已取得的异步事件。应用处理每个事件后传入对应event,完成确认,再按依赖关系清理资源。

3. 核心数据结构

3.1 device 与 context

以下结构体仅列关键字段,非完整 ABI 布局。对象地址、内核句柄、硬件编号和访问 key 不可混用。

ibv_device描述可供打开的本地 RDMA 设备,由枚举接口返回。

structibv_device{/* 设备节点类型,例如 IBV_NODE_CA。 */enumibv_node_typenode_type;/* 设备使用的传输类型。 */enumibv_transport_typetransport_type;/* 本地 RDMA 设备名称,可通过 ibv_get_device_name() 读取。 */charname[IBV_SYSFS_NAME_MAX];};

ibv_context表示设备上下文,打开设备时获取,多次打开时,对应的设备上下文独立。ibv_context提供资源创建和调用分发所需的设备上下文。libibverbs/provider 管理其分配和内部状态,应用通过 API 使用并关闭它,不能自行修改。

structibv_context{/* 本次打开对应的设备描述。 */structibv_device*device;/* 按值保存的公开回调表,收发与轮询入口通过它分发。 */structibv_context_opsops;/* 访问 uverbs 设备的命令文件描述符。 */intcmd_fd;/* 获取端口、QP、CQ 等异步事件的文件描述符。 */intasync_fd;/* 设备可用的 completion vector 数量,创建 CQ 时据此选择向量。 */intnum_comp_vectors;};

3.2 PD、MR、QP 与 CQ

ibv_pd表达资源的保护域。普通 RC 应用在同一 PD 下创建 QP 和供它使用的本地 MR。

structibv_pd{/* 所属设备上下文。 */structibv_context*context;/* 内部资源命令使用的 PD 句柄。 */uint32_thandle;};

ibv_mr表示已注册内存范围及其访问凭据。注册不会转移业务内存的分配所有权;应用在所有相关工作结束并成功注销 MR 后,才能释放底层内存。用malloc()分配的内存仍由应用释放;用ibv_alloc_buf()获得的 buffer 则由ibv_free_buf()释放。

structibv_mr{/* MR 所属的保护域。 */structibv_pd*pd;/* 地址注册时保存 CPU 虚拟起始地址;DMA-BUF fd 注册时保存字节偏移,不可直接解引用。 */void*addr;/* 注册范围的字节数。 */size_tlength;/* 本地访问 key,填写到使用该 MR 的 SGE 中。 */uint32_tlkey;/* 远端访问 key,按需与 RDMA 地址一起交换给对端。 */uint32_trkey;};

ibv_qp是应用提交发送和接收工作的资源入口。它关联保护域和两个完成队列;多个 QP 可以共享 CQ,同一 QP 的发送和接收也可以使用同一个 CQ。应用通过创建、修改、查询和销毁 API 管理 QP。

structibv_qp{/* 所属设备上下文,提交 API 从中取得回调。 */structibv_context*context;/* 应用提供的关联指针,库不管理其指向对象的寿命。 */void*qp_context;/* QP 所属的保护域。 */structibv_pd*pd;/* 汇集该 QP 发送侧完成的 CQ。 */structibv_cq*send_cq;/* 汇集该 QP 接收完成的 CQ。 */structibv_cq*recv_cq;/* 本地 QP 编号,建立 RC 连接时交换给对端。 */uint32_tqp_num;/* 公开 QP 状态,通过修改或查询 API 管理。 */enumibv_qp_statestate;/* QP 传输类型;普通 RC 使用 IBV_QPT_RC。 */enumibv_qp_typeqp_type;};

ibv_cq汇集一个或多个 QP 的工作完成,按 context 创建,不归属于 PD。应用持续轮询 CQ;启用事件通知时,还需确认每次成功取得的 completion channel 事件。

structibv_cq{/* CQ 所属的设备上下文。 */structibv_context*context;/* 可选的完成通知通道,NULL 表示未关联 channel。 */structibv_comp_channel*channel;/* 应用关联指针,ibv_get_cq_event() 会返回该值。 */void*cq_context;/* 创建成功后返回的实际 CQ 容量。 */intcqe;/* 已通过 ibv_ack_cq_events() 确认的完成事件累计数,由库维护。 */uint32_tcomp_events_completed;};

ibv_comp_channel提供阻塞等待 CQ 通知的入口,可被同一 context 下的多个 CQ 共享。channel 事件提示应用轮询相应 CQ,具体工作结果仍从 WC 读取;销毁 channel 前先释放关联 CQ。

structibv_comp_channel{/* 通知通道所属的设备上下文。 */structibv_context*context;/* ibv_get_cq_event() 读取完成事件的文件描述符。 */intfd;/* 关联 CQ 的引用计数,由库在 CQ 创建和销毁时维护。 */intrefcnt;};

3.3 属性

ibv_qp_init_attr描述普通 QP 的 CQ、类型和队列容量。应用调用创建接口前清零并填写属性,创建成功后保存返回的实际容量。

structibv_qp_init_attr{/* 发送侧完成使用的 CQ。 */structibv_cq*send_cq;/* 接收完成使用的 CQ。 */structibv_cq*recv_cq;/* 共享接收队列;使用 QP 自有 RQ 时设置为 NULL。 */structibv_srq*srq;/* 请求的队列容量,创建成功后回填实际支持值。 */structibv_qp_capcap;/* QP 类型,RC 应用设置为 IBV_QPT_RC。 */enumibv_qp_typeqp_type;/* 非零时为所有 Send WR 请求成功完成;为零时由每条 WR 决定。 */intsq_sig_all;};

ibv_qp_cap是 QP 的能力。应用依据设备能力提出需求,再以创建接口回填的值管理队列深度、SGE 数量和 inline 长度。

structibv_qp_cap{/* SQ 可容纳的最大未完成 WR 数量。 */uint32_tmax_send_wr;/* RQ 可容纳的最大未完成 WR 数量。 */uint32_tmax_recv_wr;/* 每条 Send WR 允许的最大 SGE 数量。 */uint32_tmax_send_sge;/* 每条 Receive WR 允许的最大 SGE 数量。 */uint32_tmax_recv_sge;/* 单条 inline 发送允许的最大数据字节数。 */uint32_tmax_inline_data;};

ibv_qp_attr承载 QP 的动态属性。RC 建连时,应用可以动态修改 QP 状态、地址路径与包序号;每次调用时,由attr_mask决定修改哪些属性。

structibv_qp_attr{/* 本次修改的目标 QP 状态,或查询得到的状态。 */enumibv_qp_stateqp_state;/* 连接路径使用的 MTU。 */enumibv_mtupath_mtu;/* 本地接收队列期望的起始 PSN,取自对端发送 PSN。 */uint32_trq_psn;/* 本地发送队列的起始 PSN。 */uint32_tsq_psn;/* 对端 QP 编号。 */uint32_tdest_qp_num;/* 允许远端通过该 QP 执行的内存访问类型。 */unsignedintqp_access_flags;/* 地址路径属性,包含 LID 或 RoCE 使用的 GRH/GID 信息。 */structibv_ah_attrah_attr;/* QP 使用的本地设备端口号。 */uint8_tport_num;};

ibv_mr_init_attr将普通地址、显式 IOVA、DMA-BUF 和 buffer 注册统一成输入的属性。应用用有效位指明输入来源,libibverbs 按这些位校验并转换属性,再传给 provider。

structibv_mr_init_attr{/* 待注册范围的字节数。 */size_tlength;/* MR 访问权限,使用 enum ibv_access_flags。 */intaccess;/* 有效输入字段的位掩码,使用 enum ibv_mr_init_attr_mask。 */uint64_tcomp_mask;/* 显式指定的 RDMA 访问起始地址,由 IOVA 位标记。 */uint64_tiova;/* 待注册的 CPU 地址或 buffer 子范围起点,由 ADDR 位标记。 */void*addr;/* DMA-BUF 文件描述符,由 FD 位标记。 */intfd;/* 注册范围在 DMA-BUF 中的字节偏移,由 FD_OFFSET 位标记。 */uint64_tfd_offset;/* ibv_alloc_buf() 返回的句柄,使用 BUF 位时还需提供 ADDR。 */structibv_buf*buf;};

3.4 WR、SGE 与 WC

WR 表示工作,SGE 表示本地地址范围,WC 表示完成结果。它们由应用分配,常见做法是在栈上准备 WR/SGE,在轮询线程中复用 WC 数组。

ibv_send_wr描述提交到 SQ 的工作。SEND 读取本地 SGE 并发送给对端接收队列;RDMA READ/WRITE 还使用远端地址与 key。应用可以串联多条 WR 批量提交,并用返回的bad_wr区分已接受的前缀与未提交的部分。

structibv_send_wr{/* 应用定义的请求标识,对应完成通过 WC 回传。 */uint64_twr_id;/* 下一条 Send WR,链表末尾为 NULL。 */structibv_send_wr*next;/* 本条工作使用的本地 SGE 数组。 */structibv_sge*sg_list;/* sg_list 中的 SGE 数量。 */intnum_sge;/* 工作类型,例如 SEND、RDMA READ 或 RDMA WRITE。 */enumibv_wr_opcodeopcode;/* 单条发送的修饰位,例如 SIGNALED、INLINE。 */unsignedintsend_flags;union{/* WITH_IMM 操作附带的数据,使用网络字节序。 */__be32 imm_data;};/* 操作专用参数,READ/WRITE 使用 wr.rdma。 */union{/* RDMA READ/WRITE 的远端内存描述。 */struct{/* 对端交换的 RDMA 访问地址。 */uint64_tremote_addr;/* 对端 MR 的远端访问 key。 */uint32_trkey;}rdma;}wr;};

ibv_recv_wr发布未来可供接收的本地缓冲区。它没有发送 opcode,也不指定远端地址;普通 RC 应用在开始接收前发布 WR。

structibv_recv_wr{/* 应用定义的接收槽位标识,由接收 WC 回传。 */uint64_twr_id;/* 下一条 Receive WR,链表末尾为 NULL。 */structibv_recv_wr*next;/* 允许接收写入的本地 SGE 数组。 */structibv_sge*sg_list;/* sg_list 中的 SGE 数量。 */intnum_sge;};

ibv_sge描述一段本地内存,ibv_sge数组可以保存多段不连续的本地内存。SEND/WRITE 的本地 SGE 是数据源,READ 和 RECV 的本地 SGE 是目的地;普通地址注册时,地址与长度应位于对应 MR 范围内。

structibv_sge{/* 本地访问地址;普通地址注册使用业务指针对应的地址值。 */uint64_taddr;/* 本段内存的字节数。 */uint32_tlength;/* 授权本地访问的 MR key。 */uint32_tlkey;};

ibv_wc是轮询接口写入应用数组的完成结果。应用先检查完成状态,成功后再按操作类型和有效标志处理数据;错误的完成状态只保证请求标识、状态、QP 编号和厂商错误码有效。

structibv_wc{/* 原请求的 wr_id,用于找回应用槽位,不是 WR 指针。 */uint64_twr_id;/* 工作完成状态;IBV_WC_SUCCESS 表示成功。 */enumibv_wc_statusstatus;/* 完成类型,成功时用于区分发送、接收或 RDMA 操作。 */enumibv_wc_opcodeopcode;/* 厂商错误码,用于诊断失败的完成。 */uint32_tvendor_err;/* 本次完成报告的数据字节数,含义取决于完成类型。 */uint32_tbyte_len;union{/* 带 immediate 的成功完成数据,网络字节序,需检查 IBV_WC_WITH_IMM。 */__be32 imm_data;};/* 产生该完成的本地 QP 编号。 */uint32_tqp_num;/* 完成字段的有效标志,例如 IBV_WC_WITH_IMM。 */unsignedintwc_flags;};

3.5 数据结构关系

下图展示公开资源的归属与引用关系。箭头表示指针引用,数量表示每个源实例引用的目标数量;例如一个 QP 引用一个 PD,而一个 PD 可以被多个 QP 引用。send_cq与recv_cq是两个独立关联,可以指向同一 CQ。图中保留主要资源依赖;到 context 的关联可通过公开字段或所属 PD 追溯。

下面将请求和结果单独展开。next是同类型链表关联,sg_list指向由num_sge指定长度的数组。图中的 WC 单独存在,避免把应用自定义的wr_id对应关系误画成 WR 指针。

4. API 分析

4.1 设备与 provider 绑定

mlx5 通过PROVIDER_DRIVER(mlx5, mlx5_dev_ops)注册设备操作表。libibverbs 根据内核报告的设备信息选择 provider,匹配逻辑位于 rdma-core/libibverbs/init.c。

  1. 优先将 RDMA netlink 返回的driver_id与匹配表比较;未匹配时,依次尝试设备名称和 sysfsdevice/modalias模式匹配。mlx5 使用RDMA_DRIVER_MLX5,并保留按 PCI vendor ID、device ID 匹配的条目。
  2. provider 若提供.match_device,由该回调决定是否接受设备;否则必须命中匹配表。随后检查 uverbs ABI 版本是否在支持范围内。mlx5 未提供此回调。
  3. 匹配通过后,try_driver()调用.alloc_device;mlx5 对应mlx5_device_alloc()。成功后保存vdev->ops,供设备打开使用。

设备打开流程位于 rdma-core/libibverbs/device.c:

  1. ibv_open_device()进入verbs_open_device(),取得设备已绑定的 provider 操作表。
  2. 有 sysfs 设备时,open_cdev()打开 uverbs 设备;随后调用.alloc_context对应的mlx5_alloc_context()创建 context。
  3. set_lib_ops()安装库包装入口;有 sysfs 设备且尚无async_fd时,分配异步事件 fd。失败时通过ibv_close_device()回滚到mlx5_free_context()。
  4. 成功返回公开成员&context_ex->context,应用随后用它创建资源。

4.2 context 与操作表关系

ibv_context是应用使用的设备上下文;verbs_context包含这个公开结构,并保存扩展回调和私有状态指针priv。库需要访问扩展入口或priv->ops时,从应用传入的ibv_context *找到所属的verbs_context。

操作表中的每个成员是一个函数指针。verbs_set_ops()将 provider 提供的函数地址写入对应成员,例如将mlx5_post_send保存到ibv_context.ops.post_send。应用调用ibv_post_send()时,接口通过qp->context->ops.post_send调用该函数。

不同 API 使用的回调位置如下:

API 类型回调位置与调用方式
普通资源 APIibv_alloc_pd()、ibv_create_qp()等通过get_ops()取得私有表priv->ops,再调用对应 provider 回调。
收发、轮询与通知 APIibv_post_send()、ibv_post_recv()、ibv_poll_cq()、ibv_req_notify_cq()直接使用ibv_context.ops中的回调。
扩展 QP 创建 APIibv_create_qp_ex()在扩展分支中通过verbs_get_ctx_op()检查并调用verbs_context.create_qp_ex。
扩展 CQ 创建 APIverbs_context.create_cq_ex指向库包装函数;包装函数再调用priv->ops.create_cq_ex中的 provider 回调。

回调赋值逻辑位于 rdma-core/libibverbs/dummy_ops.c,mlx5 提供的回调表位于 rdma-core/providers/mlx5/mlx5.c。下图展示结构与操作表的关系,实心菱形表示按值嵌入,箭头表示指针引用。

4.3 普通资源 API 分发

普通资源 API 从输入对象的 context 取得私有操作表,再调用 provider 接口。实现位于 rdma-core/libibverbs/verbs.c 和device.c。

  1. ibv_alloc_pd()经get_ops()调用mlx5_alloc_pd(),成功后填写pd->context。
  2. ibv_create_cq()调用mlx5_create_cq(),成功后由verbs_init_cq()填写公共字段、增加 channel 引用计数,并初始化事件计数和同步状态。
  3. ibv_create_qp()调用mlx5_create_qp(),直接返回公开 QP 指针。

释放资源时,ibv_dealloc_pd()调用mlx5_free_pd(),ibv_destroy_cq()调用mlx5_destroy_cq()。CQ 销毁成功后减少 channel 引用计数;仍有 CQ 引用时,销毁 channel 返回EBUSY。

4.4 收发与完成 API 分发

收发、轮询和通知入口通过公开 context 操作表直接分发;事件读取和确认由库单独处理。对应流程如下:

Verbs 接口provider 接口功能
ibv_post_sendmlx5_post_send提交发送 WR 链表,通过qp->context->ops.post_send分发。
ibv_post_recvmlx5_post_recv发布接收缓冲区,通过qp->context->ops.post_recv分发。
ibv_poll_cqmlx5_poll_cq或mlx5_poll_cq_v1读取 CQ 完成并填写 WC 数组;cq->context->ops.poll_cq决定实际回调,CQE v1 使用后者。
ibv_req_notify_cqmlx5_arm_cq设置一次性完成通知,通过cq->context->ops.req_notify_cq分发。
ibv_get_cq_eventmlx5_cq_event读取 channel 事件,返回 CQ 和cq_context,再通过私有.cq_event调用 provider 回调。
ibv_ack_cq_events无按nevents更新已获取事件的确认计数,并唤醒等待者。

5. MR 注册分析

普通地址注册调用ibv_reg_mr(pd, addr, length, access):addr/length指定应用已分配的内存范围,pd指定 MR 所属保护域,access指定访问权限。

使用场景MR 权限与 WR 参数
本地 SEND 数据源本地读隐含允许;非 inline 使用注册范围内的地址与lkey。
本地 RECV 或 RDMA READ 目的缓冲区需要IBV_ACCESS_LOCAL_WRITE。
允许远端 WRITE目标 MR 设置IBV_ACCESS_REMOTE_WRITE和IBV_ACCESS_LOCAL_WRITE,QP 允许远端写;把目标地址、长度约定和rkey交给远端。
允许远端 READ源 MR 设置IBV_ACCESS_REMOTE_READ,QP 允许远端读;发起方用可本地写入的 MR 接收结果。
inline SEND/WRITE 数据源单次请求长度不超过实际 inline 能力;provider 在提交调用内读取数据,源数据无需依靠lkey注册检查。远端权限仍按操作要求设置。

下图以普通 CPU 内存、非 inline 访问且启用 IOMMU DMA 转换为例,A为 CPU 注册地址,B为 MR 访问地址基址,δ为 MR 内的字节偏移;普通注册不设置IBV_ACCESS_ZERO_BASED。

  1. 注册内存。两个接口的addr都是 CPU 虚拟地址。ibv_reg_mr的 MR 基址B = A;ibv_reg_mr_iova2的 MR 基址B = iova,可与A不同。
  2. 填写请求。本地 SGE 使用B + δ和lkey;远端 RDMA READ/WRITE 访问本机 MR 使用B + δ和rkey,地址和rkey由本机应用自行提供给远端。CPU 仍以A + δ访问 buffer。
  3. 访问 DRAM。RNIC 将 MR 地址B + δ转换为 DMA 地址D,经 PCIe Switch、Root Complex 到达 IOMMU;IOMMU 将D转换为物理地址P,访问 Host DRAM。

这里的D是内核 DMA 映射产生的 IOMMU IOVA,与注册接口的 MR 基址iova属于不同地址空间,不要求数值相同。两个注册接口都可以在有 IOMMU 或没有 IOMMU 的系统上使用。

6. 总结

libibverbs 向应用提供统一的 Verbs API,并根据设备信息绑定 provider。应用使用ibv_context,库通过包含它的verbs_context访问扩展入口和私有状态。普通资源 API 经私有操作表分发,收发、轮询和通知 API 使用ibv_context.ops,最终调用对应的 mlx5 provider 接口。

数据结构表达资源依赖和请求内容:PD 限定资源访问范围,MR 描述注册内存及访问 key,QP 关联 PD 和发送、接收 CQ。应用用属性配置资源,用 WR 描述操作,用 SGE 指定本地内存范围,通过轮询 CQ 获取 WC。completion channel 提供 CQ 事件,事件确认与获取具体完成结果分别由对应 API 处理。

MR 注册确定内存范围、权限和 RDMA 访问地址。普通注册不设置IBV_ACCESS_ZERO_BASED时,ibv_reg_mr使用 CPU 虚拟地址作为 MR 基址;ibv_reg_mr_iova2可单独指定该基址。请求使用 MR 地址与lkey或rkey,远端地址和rkey由应用自行交换。启用 IOMMU DMA 转换时,RNIC 将 MR 地址转换为 DMA 地址,IOMMU 再将其转换为物理地址;MR 基址iova与 IOMMU IOVA 属于不同地址空间。

7. 参考资料

  • 本仓库 libibverbs 公开接口头文件和实现:rdma-core/libibverbs/verbs.h、rdma-core/libibverbs/verbs.c。
  • rdma-core/libibverbs/man/ibv_create_qp.3、rdma-core/libibverbs/man/ibv_modify_qp.3、rdma-core/libibverbs/man/ibv_create_cq.3:资源参数、返回容量与 QP 状态修改约束。
  • rdma-core/libibverbs/man/ibv_reg_mr.3、rdma-core/libibverbs/man/ibv_alloc_buf.3.md、rdma-core/libibverbs/verbs.c 中的ibv_reg_buf_mr():内存注册、buffer 使用与释放约定。
  • rdma-core/libibverbs/man/ibv_post_send.3、rdma-core/libibverbs/man/ibv_post_recv.3、rdma-core/libibverbs/man/ibv_poll_cq.3:WR、SGE、部分提交失败和 WC 字段有效性。
  • rdma-core/libibverbs/man/ibv_get_cq_event.3、rdma-core/libibverbs/man/ibv_req_notify_cq.3.md、rdma-core/libibverbs/man/ibv_get_async_event.3:通知、事件读取与确认。
  • rdma-core/libibverbs/examples/rc_pingpong.c:本篇使用的完整 RC SEND/RECV 示例。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/22 2:26:45

SQL 联合注入与双写绕过靶场练习

[极客大挑战 2019]LoveSQL 1.经过上次的教训,看到SQL注入先进行判断类型,是字符型注入,然后输入or 11#,发现告诉了password。 2.输入admin和password之后发现页面还是不变,这个方法行不通。万能密码登录方式不适合&…

作者头像 李华