安装教程:
一、简介
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)
将事件转化为一个字符串。