记录一些 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 | struct ibv_wc { |
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 | int ibv_get_cq_event(struct ibv_comp_channel *channel, |
用于等待某一 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 错误。
除非另外使用一种流控制的方式,不然上面的两种解决方案都总会存在一定的局限性。