RDMA API
📡

RDMA API

Created
Dec 9, 2021 07:48 AM
Tags
RDMA
SmartNIC
Category
Networking
Last Edited
Last updated July 15, 2022
Abstract
Related to Reading List (Column)
安装教程:

一、简介

RDMA 目前使用较多方案是 VPI + RDMA_CM 的方案,其中 VPI (Virtual Protocol Interconnect) 是用于程序和网卡交互的接口,而 RDMA_CM 主要是用于建立连接来帮助数据传输的方案。
本文只介绍一些较为实用的接口,同时只会对一些重要的参数和用法进行记录,完整内容建议查看 Mellanox 官方手册

二、VPI 接口 (基础 ib_verbs)

1. 设备操作

1.1. ibv_get_device_list

struct ibv_device **ibv_get_device_list(int *num_devices) void ibv_free_device_list(struct ibv_device **list)
获取当前机器上的 IB 设备列表。

1.2. ibv_get_device_name

const char *ibv_get_device_name(struct ibv_device *device)
获取设备的名称。

1.3. ibv_open_device

struct ibv_context *ibv_open_device(struct ibv_device *device) int ibv_close_device(struct ibv_context *context)
获取设备用于操作的上下文信息,可以使用 rdma_create_id 替代。

2. Verb Context 操作

2.1. ibv_alloc_pd

struct ibv_pd *ibv_alloc_pd(struct ibv_context *context) int ibv_dealloc_pd(struct ibv_pd *pd)
用于创建保护域 (PD),PD 用于限制 MR 和 QP 的之间的访问权限。

2.2. ibv_create_comp_channel

struct ibv_comp_channel *ibv_create_comp_channel(struct ibv_context *context) int ibv_destroy_comp_channel(struct ibv_comp_channel *channel)
用于创建完成通知频道 CC,其用于用户接收有 CQE 被添加到 CQ 的通知。

2.3. ibv_create_cq

struct ibv_cq *ibv_create_cq(struct ibv_context *context, int cqe, void *cq_context, struct ibv_comp_channel *channel, int comp_vector) int ibv_resize_cq(struct ibv_cq *cq, int cqe) int ibv_destroy_cq(struct ibv_cq *cq)
用于创建完成队列,每个完成队列中会包含多个完成实体 CQE,同时一个 CQ 可以被多个 QP 共同使用。
cqe 参数用于定义队列的最小值。
cq_context 是用户可自定义的值,如果在创建时指定了,则会在之后调用 ibv_get_cq_event 通过完成频道 CC 返回。
channel 参数是用于制定 CC 的,因为一个 CQ 只是一个队列,其不包含通知机制,也可使用轮询的模式代替 CC 通知。
CQE 可由Recv、Read、Write产生。

3. Protection Domain 操作

3.1. ibv_reg_mr

struct ibv_mr *ibv_reg_mr(struct ibv_pd *pd, void *addr, size_t length, enum ibv_access_flagsaccess) int ibv_dereg_mr(struct ibv_mr *mr) struct ibv_mr { struct ibv_context *context; struct ibv_pd *pd; struct void *addr; size_t length; uint32_t handle; uint32_t lkey; uint32_t rkey; }
用于将一段内存注册为一个内存域 MR,将其与 PD、lkey、rkey进行关联,同一块内存还可被注册为多个 MR。
access 参数用于指定访问权限,包括:
enum ibv_access_flags { IBV_ACCESS_LOCAL_WRITE IBV_ACCESS_REMOTE_WRITE IBV_ACCESS_REMOTE_READ IBV_ACCESS_REMOTE_ATOMIC IBV_ACCESS_MW_BIND IBV_ACCESS_ZERO_BASED IBV_ACCESS_ON_DEMAND IBV_ACCESS_HUGETLB IBV_ACCESS_RELAXED_ORDERING = IBV_ACCESS_OPTIONAL_FIRST, }

3.2. ibv_create_qp

struct ibv_qp *ibv_create_qp(struct ibv_pd *pd, struct ibv_qp_init_attr *qp_init_attr) int ibv_destroy_qp(struct ibv_qp *qp) struct ibv_qp_init_attr { void *qp_context; // 可选 struct ibv_cq *send_cq; struct ibv_cq *recv_cq; // 可与 send_cq 共用 struct ibv_srq *srq; // 可选 struct ibv_qp_cap cap; // 传输配置 enum ibv_qp_type qp_type; // 传输模式 int sq_sig_all; // 1:所有发送都会产生 CQE;0:仅有标记的会 }; struct ibv_qp_cap { uint32_t max_send_wr; // 未完成的发送工作请求的最大数量 uint32_t max_recv_wr; // 未完成的接收工作请求的最大数量 uint32_t max_send_sge; // 一条工作请求携带 SGE 的最大数量 uint32_t max_recv_sge; uint32_t max_inline_data; // 内联数据最大字节数 };
用于创建 QP,调用前应创建好对应的 CQ 和 SRQ(可选)。

3.3. ibv_create_srq

struct ibv_srq *ibv_create_srq(struct ibv_pd *pd, struct ibv_srq_init_attr *srq_init_attr) int ibv_modify_srq (struct ibv_srq *srq, struct ibv_srq_attr *srq_attr, int srq_attr_mask) int ibv_destroy_srq(struct ibv_srq *srq) struct ibv_srq_init_attr { void *srq_context; // ibv_open_device 的返回值 struct ibv_srq_attr attr; }; struct ibv_srq_attr { uint32_t max_wr; uint32_t max_sge; uint32_t srq_limit; };

3.4. ibv_create_ah

struct ibv_ah *ibv_create_ah(struct ibv_pd *pd, struct ibv_ah_attr *attr) int ibv_destroy_ah(struct ibv_ah *ah) struct ibv_ah_attr { struct ibv_global_route grh; uint16_t dlid; // destination lid uint8_t sl; // service level uint8_t src_path_bits; uint8_t static_rate; uint8_t is_global; 是否是全局地址,是的话需要配合 grh uint8_t port_num; 用于访问目的的物理端口 }; struct ibv_global_route { union ibv_gid dgid; uint32_t flow_label; uint8_t sgid_index; uint8_t hop_limit; uint8_t traffic_class; };

4. QP 状态变化

QP 必须按照一个定义的序列进行状态转化,可选的状态有:
Reset
Init
RTR
RTS
Post receive request
Disallowed
Allowed
Allowed
Allowed
Post send request
Disallowed
Disallowed
Disallowed
Allowed
Outgoing packets
None
None
None
Initiated
Incoming packets
Silently dropped
Silently dropped
Handled
Handled
Send request processing
Not processed
Not processed
Not processed
Processed
Receive Request processing
Not processed
Not processed
Processed
Processed
状态变更需要使用 ibv_modify_qp 函数,该函数不能随意更改 QP 的属性,需要遵循一个严格的限制集,因此建议使用rdma_init_qp_attr来生成修改内容,具体如下:
int ibv_modify_qp(struct ibv_qp *qp, struct ibv_qp_attr *attr, int attr_mask); int rdma_init_qp_attr(struct rdma_cm_id *id, struct ibv_qp_attr *qp_attr, int *qp_attr_mask);

5. 针对活跃 QP 的操作

5.1. ibv_post_recv

int ibv_post_recv(struct ibv_qp *qp, struct ibv_recv_wr *wr, struct ibv_recv_wr **bad_wr) struct ibv_recv_wr { uint64_t wr_id; struct ibv_recv_wr *next; struct ibv_sge *sg_list; int num_sge; };
函数会将一个装有 WR 的链表放到接收队列里,会使用 bad_wr 返回第一个报错的 WR。

5.2. ibv_post_send

int ibv_post_send(struct ibv_qp *qp, struct ibv_send_wr *wr, struct ibv_send_wr **bad_wr) struct ibv_send_wr { uint64_t wr_id; /* User defined WR ID */ struct ibv_send_wr *next; /* Pointer to next WR in list, NULL if last WR */ struct ibv_sge *sg_list; /* Pointer to the s/g array */ int num_sge; /* Size of the s/g array */ enum ibv_wr_opcode opcode; /* Operation type */ int send_flags; /* Flags of the WR properties */ uint32_t imm_data; /* Immediate data (in network byte order) */ union { struct { uint64_t remote_addr; /* Start address of remote memory buffer */ uint32_t rkey; /* Key of the remote Memory Region */ } rdma; struct { uint64_t remote_addr; /* Start address of remote memory buffer */ uint64_t compare_add; /* Compare operand */ uint64_t swap; /* Swap operand */ uint32_t rkey; /* Key of the remote Memory Region */ } atomic; struct { struct ibv_ah *ah; /* Address handle (AH) for the remote node address */ uint32_t remote_qpn; /* QP number of the destination QP */ uint32_t remote_qkey; /* Q_Key number of the destination QP */ } ud; } wr; };
函数会将一个装有 WR 的链表放到发送队列里,会使用 bad_wr 返回第一个报错的 WR。
如果是使用 AH 发送,在 WR 被完全执行完成(产生对应的 CQE)前应保证 AH 的内存不被释放。
对于使用到的缓存区也要在 WR 被完全执行完成前保证不被改变,执行完成后才可以重新利用。

5.3. ibv_post_srq_recv

int ibv_post_srq_recv(struct ibv_srq *srq, struct ibv_recv_wr *recv_wr, struct ibv_recv_wr **bad_recv_wr)
函数会将一个装有 WR 的链表放到共享发送队列里。

5.4. ibv_req_notify_cq

int ibv_req_notify_cq(struct ibv_cq *cq, int solicited_only)
针对特定 CQ 启用通知机制,启用后当一个 CQE 被放到这个 CQ 中后会通过 CC 发送通知,但这个通知只会针对第一个 CQE,后续 CQE 不会再进行通知,需要再次调用这个函数来再次启用。

5.5. ibv_get_cq_event

int ibv_get_cq_event(struct ibv_comp_channel *channel, struct ibv_cq **cq, void **cq_context);
用于阻塞线程等待 CC 通知有新的 CQE 产生,用户需要声明一个指针,并负责释放其中的内存。
同时每一个事件用户都需要主动进行 ACK,否则会阻塞在销毁 CQ 队列的时候。
收到通知后,用户应使用 ibv_poll_cq 操作来拉取队列中的 CQE 进行处理。

5.6. ibv_ack_cq_events

void ibv_ack_cq_events(struct ibv_cq *cq, unsigned int nevents)
用于 ACK 用户获取到的通知事件,虽然每次获取一条通知,但可以一次 ACK 多条通知,因为 ACK 操作使用了互斥锁,因此会比较耗时。

5.7. ibv_poll_cq

int ibv_poll_cq(struct ibv_cq *cq, int num_entries, struct ibv_wc *wc)
函数会从 CQ 中拉取 CQE,用户需要自己分配一个数组用来接收。
必须定期拉取 CQE,不然当 CQ 移除后就会被关闭。

5.8 async_event

TODO: 待补充

三、RDMA_CM

1. 事件频道操作

1.1. rdma_create_event_channel

struct rdma_event_channel * rdma_create_event_channel (void) void rdma_destroy_event_channel (struct rdma_event_channel *channel)
创建一个事件通知的频道,所有相关的事件都会从该频道异步上报上来。
大多数情况下,只需要一个频道就可以处理多个客户端的连接,用户需要通过rdma_get_cm_event来拉取事件。每个事件频道都映射到一个文件描述符上,用户还可以将这个描述符设为非阻塞的,通过 poll 或者 select 来处理。

2. 连接管理(CM) ID 操作

2.1. rdma_create_id

int rdma_create_id(struct rdma_event_channel *channel, struct rdma_cm_id **id, void *context, enum rdma_port_space ps) int rdma_destroy_id (struct rdma_cm_id *id) int rdma_migrate_id(struct rdma_cm_id *id, struct rdma_event_channel *channel)
创建一个标识符用于追踪通信的信息。
cm_id 类似于用于 RDMA 通信的套接字,不同点在于它需要显式的绑定到一个设备上,同时大部分操作都是天生异步的。
ps 参数用于指定连接的端口空间,RDMA_PS_TCP提供一个可靠的、面向连接的 QP 通信,但它并不是基于流的;RDMA_PS_UDP则提供一个无连接、不可靠的 QP 通信,但他支持数据报和组播。

2.2. rdma_resolve_addr

int rdma_resolve_addr(struct rdma_cm_id *id, struct sockaddr *src_addr, struct sockaddr *dst_addr, int timeout_ms)
用于将 cm_id 绑定到一个 RDMA 设备上,一般用于客户端。
如果指定了src_addr,则会绑定到具有该 IP 地址的设备上;如果未制定,则会根据本地路由表进行绑定。

2.3. rdma_resolve_route

int rdma_resolve_route(struct rdma_cm_id *id, int timeout_ms)
绑定完设备后,可通过该函数解析到目的地的 RDMA 路由,用于后续建立连接。

2.4. rdma_connect

int rdma_connect(struct rdma_cm_id *id, struct rdma_conn_param *conn_param) struct rdma_conn_param { const void *private_data; // 用于传递一段内容到服务端 uint8_t private_data_len; uint8_t responder_resources; // 可接受的read或原子操作数量 uint8_t initiator_depth; // 会进行的read或原子操作数量 uint8_t flow_control; uint8_t retry_count; // 超时重传次数 uint8_t rnr_retry_count; // 接收异常重传次数 /* Fields below ignored if a QP is created on the rdma_cm_id. */ uint8_t srq; // 指定连接使用的srq uint32_t qp_num; // 连接使用到的qp数量 };
用于发起连接请求,需在rdma_resolve_addr之后执行。

2.5. rdma_bind_addr

int rdma_bind_addr(struct rdma_cm_id *id, struct sockaddr *addr)
用于将 cm_id 绑定到一个 RDMA 设备上,一般用于服务端。
可以使用通配符进行绑定,如果不指定则会绑定到端口 0 上。

2.6. rdma_listen

int rdma_listen(struct rdma_cm_id *id, int backlog)
绑定完成后用于开始监听连接请求或者数据报服务查询。

2.7. rdma_get_request

int rdma_get_request(struct rdma_cm_id *listen, struct rdma_cm_id **id)
用于同步获取下一个连接请求,如果调用成果会返回一个新的 cm_id,可使用rdma_get_cm_event 代替。

2.8. rdma_accept

int rdma_accept(struct rdma_cm_id *id, struct rdma_conn_param *conn_param)
用于服务端监听连接请求或者数据报服务查询请求。与socket不同,rdma_accept不是针对正在监听的 cm_id,而是需要等待 RDMA_CM_EVENT_CONNECT_REQUEST 事件发生,事件发生后会返回一个新的 cm_id,之后去接受这个新的 cm_id。

2.9. rdma_create_qp

int rdma_create_qp(struct rdma_cm_id *id, struct ibv_pd *pd, struct ibv_qp_init_attr *qp_init_attr) void rdma_destroy_qp(struct rdma_cm_id *id)
用于创建针对一个 cm_id 的 QP 来用于接收和发送消息,同时 QP 的真正能力会通过qp_init_attr返回。

3. 事件处理操作

3.1. rdma_get_cm_event

int rdma_get_cm_event(struct rdma_event_channel *channel, struct rdma_cm_event **event) /* RDMA_CM_EVENT_ADDR_RESOLVED Address resolution (rdma_resolve_addr) completed successfully. RDMA_CM_EVENT_ADDR_ERROR Address resolution (rdma_resolve_addr) failed. RDMA_CM_EVENT_ROUTE_RESOLVED Route resolution (rdma_resolve_route) completed successfully. RDMA_CM_EVENT_ROUTE_ERROR Route resolution (rdma_resolve_route) failed. RDMA_CM_EVENT_CONNECT_REQUEST Generated on the passive side to notify the user of a new connection request. RDMA_CM_EVENT_CONNECT_RESPONSE Generated on the active side to notify the user of a successful response to a connection request. It is only generated on rdma_cm_id's that do not have a QP associated with them. RDMA_CM_EVENT_CONNECT_ERROR Indicates that an error has occurred trying to establish or a connection. May be generated on theactive or passive side of a connection. RDMA_CM_EVENT_UNREACHABLE Generated on the active side to notify the user that the remote server is not reachable or unable torespond to a connection request. RDMA_CM_EVENT_REJECTED Indicates that a connection request or response was rejected by the remote end point. RDMA_CM_EVENT_ESTABLISHED Indicates that a connection has been established with the remote end point. RDMA_CM_EVENT_DISCONNECTED The connection has been disconnected. RDMA_CM_EVENT_DEVICE_REMOVAL The local RDMA device associated with the rdma_cm_id has been removed. Upon receiving thisevent, the user must destroy the related rdma_cm_id. RDMA_CM_EVENT_MULTICAST_JOIN The multicast join operation (rdma_join_multicast) completed successfully. RDMA_CM_EVENT_MULTICAST_ERROR An error either occurred joining a multicast group, or, if the group had already been joined, on anexisting group. The specified multicast group is no longer accessible and should be rejoined, if desired. RDMA_CM_EVENT_ADDR_CHANGE The network device associated with this ID through address resolution changed its HW address,eg following of bonding failover. This event can serve as a hint for applications who want helinks used for their RDMA sessions to align with the network stack. RDMA_CM_EVENT_TIMEWAIT_EXIT The QP associated with a connection has exited its timewait state and is now ready to be re-used.After a QP has been disconnected, it is maintained in a timewait state to allow any in flight packets to exit the network. After the timewait state has completed, the rdma_cm will report this event. */
用于获取一个通信频道的事件,默认会阻塞程序等待事件发生。event 由 cm 负责分配内存并释放,不同的 event 会有不同的参数。

3.2. rdma_ack_cm_event

int rdma_ack_cm_event(struct rdma_cm_event *event)
用于确认 event 被处理了,会释放 event 对应的内存,需和 get 一一对应,不然会阻塞在释放 cm_id 的时候。

3.3. rdma_event_str

char *rdma_event_str (enum rdma_cm_event_type event)
将事件转化为一个字符串。