0%

Simple usage of RDMA ibv

记录一些 ibv 函数的用法

Refer to:


ibv functions

ibv_poll_cq()

1
int ibv_poll_cq(struct ibv_cq *cq, int num_entries, struct ibv_wc *wc);

用于从 Completion Queue 中查询已完成的 Work Request。

所有的 Receive Request、signaled Send Request 和出错的 Send Request 在完成之后都会产生一个 Work Completion,Work Completion 就被放入完成队列(Completion Queue)中。

完成队列是 FIFO 的,ibv_poll_cq() 检查是否有 Work Completion 在完成队列中,如果是那么就将队首弹出,并返回那个 Work Completion 到 *wc 中。

ibv_wc 的结构如下,描述了一个 Work Completion 的情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct ibv_wc {
uint64_t wr_id;
enum ibv_wc_status status;
enum ibv_wc_opcode opcode;
uint32_t vendor_err;
uint32_t byte_len;
uint32_t imm_data;
uint32_t qp_num;
uint32_t src_qp;
int wc_flags;
uint16_t pkey_index;
uint16_t slid;
uint8_t sl;
uint8_t dlid_path_bits;
};
  • wr_id 由产生 Work Completion 的 Request 决定

  • status 是操作的状态,通常为 IBV_WC_SUCCESS 表示 Work Completion 成功完成,其他还有一些错误信息

  • opcode 表示当前的 Work Competition 是怎么产生的

  • bute_len 表示传输的字节数

参数说明:

|Name|Direction|Description|
|-|
|*cq|in|用于存放 Work Completion 的完成队列|
|num_entries|in|表示最大从完成队列中读取多少个 Work Completion|
|*wc|out|将读取到的 Work Completion 返回出来,如果有多个则返回的是数组|

函数的返回值:成功则返回读取到的 Work Completion 数量,为 0 表示未读取到 Work Completion,可认为是完成队列为空,为负值则表示读取出错。

ibv_req_notify_cq()

1
int ibv_req_notify_cq(struct ibv_cq *cq, int solicited_only);

用于在完成队列中请求一个完成通知。

调用 ibv_req_notify_cq() 之后,下一个被加到 CQ 中的请求(发送请求或者接收请求)会被加上通知标记,当请求完成产生一个 Work Completion 之后就会产生通知,完成通知将被 ibv_get_cq_event() 函数读取出来。

传入的参数中:

  • solicited_only 为 0 时表示无论下一个加入 CQ 的请求是哪种类型的都会产生通知,否则只有 Solicited 或者出错的 Work Completion 才会产生通知

ibv_get_cq_event()

1
2
int ibv_get_cq_event(struct ibv_comp_channel *channel,
struct ibv_cq **cq, void **cq_context);

用于等待某一 channel 中的下一个通知产生。

ibv_get_cq_event() 默认是一个阻塞函数,调用之后会将当前程序阻塞在这里,直到下一个通知事件产生。

当 ibv_get_cq_event() 收到完成事件的通知之后,需要调用 ibv_get_cq_event() 来确认事件。

典型用法:

  • Stage I:准备阶段

创建一个 CQ,并且将它与一个 Completion Event Channel 相关联;

用 ibv_req_notify_cq() 对一个 Completion Work 调用通知请求;

  • Stage II:运行中

等待事件产生;

产生之后处理事件,并且调用 ibv_ack_cq_events() 来确认事件;

对下一个 Completion Work 调用 ibv_req_notify_cq() 的通知请求;

参数说明:

|Name|Direction|Description|
|-|
|*channel|in|关联在 CQ 上的 Completion Event Channel|
|**cq|out|从 Completion 事件中得到的一个 CQ|
|**cq_context|out|从 Completion 事件中得到的 CQ context|

返回值:为 0 表示成功,-1 在非阻塞模式下表示当前没有事件可读,阻塞模式则表示出错。

这种机制用于避免 CPU 反复读取 Work Completion ,若不采用事件的方法则只能通过不断地 ibv_poll_cq() 来轮询是否有事件完成

ibv_ack_cq_events()

1
void ibv_ack_cq_events(struct ibv_cq *cq, unsigned int nevents);

用于确认已完成 Completion events。


Errors

IBV_WC_WR_FLUSH_ERR(5/0x5)

Work Request Flushed Error

当 QP 的传送状态处于 Error 状态时,任何操作都会引发该错误。

IBV_WC_RNR_RETRY_EXC_ERR(13/0xd)

Receiver-Not-Ready Retry Error

当接收端没有准备好 Recv Request 时发送端产生了一个 Send Request 就会发生 RNR_RETRY 错误。

要求 ibv_post_recv() 必须在 ibv_post_send 之前完成,所以一种基本的思路就是一开始就 Post 一堆 Recv Request 到队列中去,然后检查当队列中的 Recv Request 少于一定数量时补充,保证不管发送端什么时候 Post Send Request 时,接收端都有足够的 Recv Request 来接收。

问题是如果发送端毫无顾忌地可以任意发送数据,尤其是在 RDMA_WRITE 方式,接收端这边会不会来不及取走数据,就被发送端传过来的新数据覆盖掉了?

或者设置 ibv_modify_qp() 参数中的 min_rnr_timer 以及 rnr_retry,前者是重试间隔时间,后者是重试次数,当 rnr_retry 为 7 时表示重试无限次。这种方法可用于重试直到接收端确认取走数据,并且准备好下一次的 Recv Request,然后发送端再进行发送。

当发送端发生 RNR_RETRY 错误时,重新调用 ibv_post_send() 是没用的,因为此时 QP 已经进入错误状态,接下来不管什么样的操作都会继续引发 IBV_WC_WR_FLUSH_ERR 错误。

除非另外使用一种流控制的方式,不然上面的两种解决方案都总会存在一定的局限性。