RDMA API
RDMA API
晨茗安装教程:
https://docs.nvidia.com/networking/display/MLNXOFEDv494170/Installing+Mellanox+OFED
一、简介
RDMA 目前使用较多方案是 VPI + RDMA_CM 的方案,其中 VPI (Virtual Protocol Interconnect) 是用于程序和网卡交互的接口,而 RDMA_CM 主要是用于建立连接来帮助数据传输的方案。
本文只介绍一些较为实用的接口,同时只会对一些重要的参数和用法进行记录,完整内容建议查看 Mellanox 官方手册。
二、VPI 接口 (基础 ib_verbs)
1. 设备操作
1.1. ibv_get_device_list
1 | struct ibv_device **ibv_get_device_list(int *num_devices) |
获取当前机器上的 IB 设备列表。
1.2. ibv_get_device_name
const char *ibv_get_device_name(struct ibv_device *device)
获取设备的名称。
1.3. ibv_open_device
1 | struct ibv_context *ibv_open_device(struct ibv_device *device) |
获取设备用于操作的上下文信息,可以使用 rdma_create_id 替代。
2. Verb Context 操作
2.1. ibv_alloc_pd
1 | struct ibv_pd *ibv_alloc_pd(struct ibv_context *context) |
用于创建保护域 (PD),PD 用于限制 MR 和 QP 的之间的访问权限。
2.2. ibv_create_comp_channel
1 | struct ibv_comp_channel *ibv_create_comp_channel(struct ibv_context *context) |
用于创建完成通知频道 CC,其用于用户接收有 CQE 被添加到 CQ 的通知。
2.3. ibv_create_cq
1 | struct ibv_cq *ibv_create_cq(struct ibv_context *context, int cqe, void *cq_context, struct ibv_comp_channel *channel, int comp_vector) |
用于创建完成队列,每个完成队列中会包含多个完成实体 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
1 | struct ibv_mr *ibv_reg_mr(struct ibv_pd *pd, void *addr, size_t length, enum ibv_access_flagsaccess) |
用于将一段内存注册为一个内存域 MR,将其与 PD、lkey、rkey进行关联,同一块内存还可被注册为多个 MR。
access 参数用于指定访问权限,包括:
1 | enum ibv_access_flags { |
3.2. ibv_create_qp
1 | struct ibv_qp *ibv_create_qp(struct ibv_pd *pd, struct ibv_qp_init_attr *qp_init_attr) |
用于创建 QP,调用前应创建好对应的 CQ 和 SRQ(可选)。
3.3. ibv_create_srq
1 | struct ibv_srq *ibv_create_srq(struct ibv_pd *pd, struct ibv_srq_init_attr *srq_init_attr) |
3.4. ibv_create_ah
1 | struct ibv_ah *ibv_create_ah(struct ibv_pd *pd, struct ibv_ah_attr *attr) |
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来生成修改内容,具体如下:
1 | int ibv_modify_qp(struct ibv_qp *qp, struct ibv_qp_attr *attr, int attr_mask); |
5. 针对活跃 QP 的操作
5.1. ibv_post_recv
1 | int ibv_post_recv(struct ibv_qp *qp, struct ibv_recv_wr *wr, struct ibv_recv_wr **bad_wr) |
函数会将一个装有 WR 的链表放到接收队列里,会使用 bad_wr 返回第一个报错的 WR。
5.2. ibv_post_send
1 | int ibv_post_send(struct ibv_qp *qp, struct ibv_send_wr *wr, struct ibv_send_wr **bad_wr) |
函数会将一个装有 WR 的链表放到发送队列里,会使用 bad_wr 返回第一个报错的 WR。
如果是使用 AH 发送,在 WR 被完全执行完成(产生对应的 CQE)前应保证 AH 的内存不被释放。
对于使用到的缓存区也要在 WR 被完全执行完成前保证不被改变,执行完成后才可以重新利用。
5.3. ibv_post_srq_recv
1 | int ibv_post_srq_recv(struct ibv_srq *srq, struct ibv_recv_wr *recv_wr, |
函数会将一个装有 WR 的链表放到共享发送队列里。
5.4. ibv_req_notify_cq
1 | int ibv_req_notify_cq(struct ibv_cq *cq, int solicited_only) |
针对特定 CQ 启用通知机制,启用后当一个 CQE 被放到这个 CQ 中后会通过 CC 发送通知,但这个通知只会针对第一个 CQE,后续 CQE 不会再进行通知,需要再次调用这个函数来再次启用。
5.5. ibv_get_cq_event
1 | int ibv_get_cq_event(struct ibv_comp_channel *channel, |
用于阻塞线程等待 CC 通知有新的 CQE 产生,用户需要声明一个指针,并负责释放其中的内存。
同时每一个事件用户都需要主动进行 ACK,否则会阻塞在销毁 CQ 队列的时候。
收到通知后,用户应使用 ibv_poll_cq 操作来拉取队列中的 CQE 进行处理。
5.6. ibv_ack_cq_events
1 | void ibv_ack_cq_events(struct ibv_cq *cq, unsigned int nevents) |
用于 ACK 用户获取到的通知事件,虽然每次获取一条通知,但可以一次 ACK 多条通知,因为 ACK 操作使用了互斥锁,因此会比较耗时。
5.7. ibv_poll_cq
1 | 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
1 | struct rdma_event_channel * rdma_create_event_channel (void) |
创建一个事件通知的频道,所有相关的事件都会从该频道异步上报上来。
大多数情况下,只需要一个频道就可以处理多个客户端的连接,用户需要通过rdma_get_cm_event来拉取事件。每个事件频道都映射到一个文件描述符上,用户还可以将这个描述符设为非阻塞的,通过 poll 或者 select 来处理。
2. 连接管理(CM) ID 操作
2.1. rdma_create_id
1 | int rdma_create_id(struct rdma_event_channel *channel, |
创建一个标识符用于追踪通信的信息。
cm_id 类似于用于 RDMA 通信的套接字,不同点在于它需要显式的绑定到一个设备上,同时大部分操作都是天生异步的。
ps 参数用于指定连接的端口空间,RDMA_PS_TCP提供一个可靠的、面向连接的 QP 通信,但它并不是基于流的;RDMA_PS_UDP则提供一个无连接、不可靠的 QP 通信,但他支持数据报和组播。
2.2. rdma_resolve_addr
1 | int rdma_resolve_addr(struct rdma_cm_id *id, struct sockaddr *src_addr, |
用于将 cm_id 绑定到一个 RDMA 设备上,一般用于客户端。
如果指定了src_addr,则会绑定到具有该 IP 地址的设备上;如果未制定,则会根据本地路由表进行绑定。
2.3. rdma_resolve_route
1 | int rdma_resolve_route(struct rdma_cm_id *id, int timeout_ms) |
绑定完设备后,可通过该函数解析到目的地的 RDMA 路由,用于后续建立连接。
2.4. rdma_connect
1 | int rdma_connect(struct rdma_cm_id *id, struct rdma_conn_param *conn_param) |
用于发起连接请求,需在rdma_resolve_addr之后执行。
2.5. rdma_bind_addr
1 | int rdma_bind_addr(struct rdma_cm_id *id, struct sockaddr *addr) |
用于将 cm_id 绑定到一个 RDMA 设备上,一般用于服务端。
可以使用通配符进行绑定,如果不指定则会绑定到端口 0 上。
2.6. rdma_listen
1 | int rdma_listen(struct rdma_cm_id *id, int backlog) |
绑定完成后用于开始监听连接请求或者数据报服务查询。
2.7. rdma_get_request
1 | int rdma_get_request(struct rdma_cm_id *listen, struct rdma_cm_id **id) |
用于同步获取下一个连接请求,如果调用成果会返回一个新的 cm_id,可使用rdma_get_cm_event 代替。
2.8. rdma_accept
1 | 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
1 | int rdma_create_qp(struct rdma_cm_id *id, struct ibv_pd *pd, |
用于创建针对一个 cm_id 的 QP 来用于接收和发送消息,同时 QP 的真正能力会通过qp_init_attr返回。
3. 事件处理操作
3.1. rdma_get_cm_event
1 | int rdma_get_cm_event(struct rdma_event_channel *channel, |
用于获取一个通信频道的事件,默认会阻塞程序等待事件发生。event 由 cm 负责分配内存并释放,不同的 event 会有不同的参数。
3.2. rdma_ack_cm_event
1 | int rdma_ack_cm_event(struct rdma_cm_event *event) |
用于确认 event 被处理了,会释放 event 对应的内存,需和 get 一一对应,不然会阻塞在释放 cm_id 的时候。
3.3. rdma_event_str
1 | char *rdma_event_str (enum rdma_cm_event_type event) |
将事件转化为一个字符串。








%20%E6%9C%8D%E5%8A%A1.jpeg)
