searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

RDMA Verbs API基本使用方法

2023-06-20 03:07:24
904
0

一、  概述

Verbs api是由OpenFabrics推动实现的一组RDMA应用编程接口(API),类似于socket api,它提供了一组函数和数据结构,用于管理RDMA通信和数据传输。verbs api一般由两部分构成:

IB_VERBS

以ibv_xx(用户态)或者ib_xx(内核态)为前缀,主要用于收发数据,当然也可用于连接管理,是基础的底层编程接口,程序可以完全基于IB_VERBS编写。

RDMA_CM

以rdma_xx为前缀,在IB_VERBS基础上封装实现的一组接口,主要用于管理连接,让通信双方可以确定和交换相关信息,RDMA_CM也提供了相应的接口用于收发数据。

二、  使用方法

下文将从基本的server与client连接,client向server发送信息来描述RDMA Verbs API的使用方法

1.  server连接前准备

struct sockaddr_in addr;
struct rdma_cm_event *event = NULL;
struct rdma_cm_id *listener = NULL;
struct rdma_event_channel *ec = NULL;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;

ec = rdma_create_event_channel();
rdma_create_id(ec, &listener, NULL, RDMA_PS_TCP);
rdma_bind_addr(listener, (struct sockaddr *)&addr);
// 第二个参数10代表传入连接请求的积压数为10
rdma_listen(listener, 10);

while (rdma_get_cm_event(ec, &event) == 0) {
    struct rdma_cm_event event_copy;

    memcpy(&event_copy, event, sizeof(*event));
    rdma_ack_cm_event(event);
    // ...
}
  • rdma_create_event_channel()

创建用于报告事件的事件通道,client和server之间通过返回值rdma_event_channel结构体来传递事件信息。

  • rdma_create_id()

创建用于跟踪通信信息的rdma_cm_id,类似于socket,通过关联的事件通道报告 rdma_cm_id 上的通信事件。参数RDMA_PS_TCP表示供可靠的,面向连接的 QP 通信。

  • rdma_bind_addr()

将源地址与 rdma_cm_id 相关联,如果绑定到特定的本地地址,则 rdma_cm_id 也将绑定到本地 RDMA 设备。

  • rdma_listen()

监听client端的连接请求。

  • rdma_get_cm_event()

等待待处理的事件,如果没有事件则阻塞调用,直到收到事件。 必须通过调用 rdma_ack_cm_event来确认报告的所有事件。

  • rdma_ack_cm_event()

释放通信事件,由rdma_get_cm_event分配的所有事件必须释放,成功获取和确认之间应该存在一对一的对应关系。

2.  client连接前准备

struct addrinfo *addr;
struct rdma_cm_event *event = NULL;
struct rdma_cm_id *conn= NULL;
struct rdma_event_channel *ec = NULL;

ec = rdma_create_event_channel();
rdma_create_id(ec, &conn, NULL, RDMA_PS_TCP);
rdma_resolve_addr(conn, NULL, addr->ai_addr, TIMEOUT_IN_MS);

while (rdma_get_cm_event(ec, &event) == 0) {
    struct rdma_cm_event event_copy;

    memcpy(&event_copy, event, sizeof(*event));
    rdma_ack_cm_event(event);
    // ...
}
  • rdma_create_event_channel()

与server端类似,建立事件通道

  • rdma_create_id()

与server端类似,创建用于跟踪通信信息的rdma_cm_id。

  • rdma_resolve_addr()

将目标源地址从 IP 地址解析为 RDMA 地址,rdma_cm_id 绑定到该地址。

  • rdma_get_cm_event()

与server端类似。

  • rdma_ack_cm_event()

与server端类似。

3.  client发起连接请求

解析完IP地址后,事件通道会报告RDMA_CM_EVENT_ADDR_RESOLVED事件,之后还需要进行RDMA上下文创建和QP创建等准备工作,之后再解析RDMA路由。

//自定义的RDMA连接结构体
struct connection {
// 指向该连接使用的rdma id的指针
struct rdma_cm_id *id;
// 指向该连接使用的qp的指针
struct ibv_qp *qp;
// 指向接收队列内存区域的指针
struct ibv_mr *recv_mr;
// 指向发送队列内存区域的指针
struct ibv_mr *send_mr;
// 接收队列内存区域起始地址
char *recv_region;
// 发送队列内存区域起始地址
char *send_region;
// 已完成的请求数
int num_completions;
};

int on_addr_resolved(struct rdma_cm_id *id)
{
    struct ibv_qp_init_attr qp_attr;
    struct connection *conn;
    // 建立RDMA上下文
    build_context(id->verbs);
    // 初始化QP属性
    memset(qp_attr, 0, sizeof(*qp_attr));
    qp_attr->send_cq = s_ctx->cq;
    qp_attr->recv_cq = s_ctx->cq;
    qp_attr->qp_type = IBV_QPT_RC;
    qp_attr->cap.max_send_wr = 10;
    qp_attr->cap.max_recv_wr = 10;
    qp_attr->cap.max_send_sge = 1;
    qp_attr->cap.max_recv_sge = 1;
    rdma_create_qp(id, s_ctx->pd, &qp_attr);
    id->context = conn = (struct connection *)malloc(sizeof(struct connection));
    conn->id = id;
    conn->qp = id->qp;
    conn->num_completions = 0;
    // 注册内存区域
    register_memory(conn);
    // 创建接收请求
    post_receives(conn);
    rdma_resolve_route(id, TIMEOUT_IN_MS);
    return 0;
}
  • rdma_create_qp()

分配与指定的 rdma_cm_id 相关联的 QP,并将其转换为发送和 接收。创建的 QP 的实际功能和属性将通过qp_attr 参数返回给用户。

  • rdma_resolve_route()

将 RDMA 路由解析为目标地址以建立连接。

build_context

s_ctx = (struct context *)malloc(sizeof(struct context));
s_ctx->ctx = verbs;
s_ctx->pd = ibv_alloc_pd(s_ctx->ctx);
s_ctx->comp_channel =ibv_create_comp_channel(s_ctx->ctx);
s_ctx->cq = ibv_create_cq(s_ctx->ctx, 10, NULL,s_ctx->comp_channel, 0));
ibv_req_notify_cq(s_ctx->cq, 0);
  • ibv_alloc_pd()

创建一个保护域(PD),返回指向保护域的指针。

  • ibv_create_comp_channel()

创建完成通道,完成信道(Completion channel,CC) 是用户在新的完成队列事件(CQE)已被放置在完成队列(CQ)上时接收通知的机制。

  • ibv_create_cq()

创建完成队列(CQ),返回指向CQ的指针。

  • ibv_req_notify_cq()

为指示的完成队列(CQ)提供通知机制,当完成队列条目 (CQE)被放置在 CQ 上时,完成事件将被发送到与 CQ 相关联的完成信道(CC)。

register_memory

conn->send_region = malloc(BUFFER_SIZE);
conn->recv_region = malloc(BUFFER_SIZE);

// 分别为发送和接收注册内存区域
conn->send_mr = ibv_reg_mr(
s_ctx->pd,
conn->send_region,
BUFFER_SIZE,
0);
conn->recv_mr = ibv_reg_mr(
s_ctx->pd,
conn->recv_region,
BUFFER_SIZE,
IBV_ACCESS_LOCAL_WRITE);
  • ibv_reg_mr()

注册一个内存区域(MR),将其与保护域(PD)相关联,并为其分配本地和远程密钥(lkey,rkey)。IBV_ACCESS_LOCAL_WRITE表示允许本地主机写访问权限。

post_receives

struct ibv_recv_wr wr, *bad_wr = NULL;
struct ibv_sge sge;
// 填充wr和sge
wr.wr_id = (uintptr_t)conn;
wr.next = NULL;
wr.sg_list = &sge;
wr.num_sge = 1;

sge.addr = (uintptr_t)conn->recv_region;
sge.length = BUFFER_SIZE;
sge.lkey = conn->recv_mr->lkey;

ibv_post_recv(conn->qp, &wr, &bad_wr);
  • ibv_post_recv()

post一个receive请求,以便能够顺利接收client端send过来的数据。ibv_post_recv操作最好在链接建立之前进行,以避免通信的另一端在发送数据时出现RNR问题(receiver-not-ready)

建立连接

路由解析完成后,事件通道会报告RDMA_CM_EVENT_ROUTE_RESOLVED事件,然后就可以建立连接了。

int on_route_resolved(struct rdma_cm_id *id)
{
    struct rdma_conn_param cm_params;
    memset(&cm_params, 0, sizeof(cm_params));
    rdma_connect(id, &cm_params);
    return 0;
}
  • rdma_connect()

启动活动连接请求,对于已连接的rdma_cm_id,会启动对远程目标的连接请求。请求发送后server端会收到RDMA_CM_EVENT_CONNECT_REQUEST事件,事件对象的内部可通过private_data属性来设置一些私有信息。

发送连接请求后,client继续阻塞调用rdma_get_cm_event,等待server端接收连接请求,接收成功会返回RDMA_CM_EVENT_ESTABLISHED事件。

4.  server端accpet连接请求

收到client端的RDMA_CM_EVENT_CONNECT_REQUEST事件后,首先从事件对象中取出rdma_cm_id来作为与对端通信的唯一标识,accpet该请求。

int on_connect_request(struct rdma_cm_id *id)
{
    struct ibv_qp_init_attr qp_attr;
    struct rdma_conn_param cm_params;
    // 自定义RDMA连接结构体,同client
    struct connection *conn;
    // 和client相同的创建RDMA上下文,QP,注册内存,post_receive等操作
    // ...
    memset(&cm_params, 0, sizeof(cm_params));
    rdma_accept(id, &cm_params);
    return 0;
}
  • rdma_accept()

对client的连接请求进行接收

5.  client端发送数据

int on_connection(void *context)
{
    struct connection *conn = (struct connection *)context;
    struct ibv_send_wr wr, *bad_wr = NULL;
    struct ibv_sge sge;
    // 将发送信息写入发送内存区域
    snprintf(conn->send_region, BUFFER_SIZE, "message from active/client side with pid %d", getpid());
    memset(&wr, 0, sizeof(wr));
    // 填充wr和sge
    wr.wr_id = (uintptr_t)conn;
    wr.opcode = IBV_WR_SEND;
    wr.sg_list = &sge;
    wr.num_sge = 1;
    wr.send_flags = IBV_SEND_SIGNALED;
    sge.addr = (uintptr_t)conn->send_region;
    sge.length = BUFFER_SIZE;
    sge.lkey = conn->send_mr->lkey;

    ibv_post_send(conn->qp, &wr, &bad_wr);
    return 0;
}
  • ibv_post_send()

将 WR 的链接列表发布到队列对(QP)的发送队列。此操作用于启动包括RDMA操作的所有通信。 WR列表的处理在第一个错误处停止,并且在 bad_wr 中返回指向错误WR的指针。

ibv_post_send处理结束后会向发送队列(SQ)提交一个工作请求(WQE),然后会通知网卡硬件进行后续发送处理。

6.  sever端接收数据

server端在建立连接前已经执行过ibv_post_recv操作,可以通过ibv_poll_cq函数对CQ进行轮询来感知CQE生成。

struct ibv_cq *cq;
struct ibv_wc wc;

while (1) {
    ibv_get_cq_event(s_ctx->comp_channel, &cq, &ctx);
    // 参数1表示一次确认一个事件
    ibv_ack_cq_events(cq, 1);
    ibv_req_notify_cq(cq, 0);

    while (ibv_poll_cq(cq, 1, &wc)) {
        if (wc->status != IBV_WC_SUCCESS)
        printf("on_completion: status is not IBV_WC_SUCCESS.");
        if (wc->opcode & IBV_WC_RECV) {
            // 该请求为接收请求
            struct connection *conn = (struct connection *)(uintptr_t)wc->wr_id;
            // 从接收内存区域中读取client端发送的信息
            printf("received message: %s\n", conn->recv_region);
        } else if (wc->opcode == IBV_WC_SEND) {
            // 该请求为发送请求
            printf("send completed successfully.\n");
        }
    }
}
  • ibv_get_cq_event()

等待在指示的完成信道(CC)上有发送通知,这是一个阻塞操作,发送的每个通知必须使用 ibv_ack_cq_events 操作进行确认。ctx: 在 ibv_create_cq 中设置的用户提供的上下文对象。

  • ibv_ack_cq_events()

确认从 ibv_get_cq_event 收到的事件。虽然从 ibv_get_cq_event 收到的每个通知都只为一个事件,但用户可以通过一次调用 ibv_ack_cq_events 来确认多个事件。

  • ibv_poll_cq()

从完成队列(CQ)中检索 CQE,将CQE存储在工作完成(Work Complete, WC)数组中,返回WC数组中CQE的数量。

接收完对端传来的信息后,需要再次调用ibv_post_recv,以便接收下一个请求。

7.  断开连接

client端完成发送操作后,断开连接

//...
// conn为前文定义的connection结构体
rdma_disconnect(conn->id);
  • rdma_disconnect()

断开连接,并将任何关联的 QP 转换为错误状态。成功断开连接后,将在连接的两端生成 RDMA_CM_EVENT_DISCONNECTED 事件。

事件通道获取到RDMA_CM_EVENT_DISCONNECTED事件后,执行RDMA内存释放相关操作。

int on_disconnect(struct rdma_cm_id *id)
{
    struct connection *conn = (struct connection *)id->context;

    printf("disconnected.\n");

    rdma_destroy_qp(id);

    ibv_dereg_mr(conn->send_mr);
    ibv_dereg_mr(conn->recv_mr);

    free(conn->send_region);
    free(conn->recv_region);

    free(conn);

    rdma_destroy_id(id);
}
  • rdma_destroy_qp()

销毁在 rdma_cm_id 上分配的 QP。

  • ibv_dereg_mr()

释放内存注册区域(MR)。

  • rdma_destroy_id()

销毁指定的 rdma_cm_id 并取消任何未完成的异步操作,调用前必须使用 rdma_destroy_qp 销毁关联的 QP。

最后client端关闭事件通道

rdma_destroy_event_channel(ec);

  • rdma_destroy_event_channel()

关闭事件通道,释放与事件通道关联的所有资源,并关闭关联的文件描述符。

server端断开连接的方式与client端大致相同,区别在于server端在最后关闭通信通道前需要销毁监听器。

// ...
rdma_destroy_id(listener);
rdma_destroy_event_channel(ec);

三、  总结

上文详细讲述了使用RDMA Verbs API编写基本RDMA Send/Receive操作的方法,介绍了使用到的API及其作用,希望能对准备进行RDMA应用开发工作的人有所帮助。

附:RDMA事件状态表

RDMA_CM_EVENT_ADDR_RESOLVED

地址解析(rdma_resolve_addr)已成功完成

RDMA_CM_EVENT_ADDR_ERROR

地址解析(rdma_resolve_addr)失败

RDMA_CM_EVENT_ROUTE_RESOLVED

路由解析(rdma_resolve_route)已成功完成

RDMA_CM_EVENT_ROUTE_ERROR

路由解析(rdma_resolve_route)失败

RDMA_CM_EVENT_CONNECT_REQUEST

在被动端生成新的连接请求

RDMA_CM_EVENT_CONNECT_RESPONSE

在活动侧生成,对连接请求的成功响应。

RDMA_CM_EVENT_CONNECT_ERROR

表示尝试建立或建立连接时发生错误。可以在连接的主动或被动侧生成

RDMA_CM_EVENT_UNREACHABLE

在活动端生成,远程端点无法访问或无法响应连接请求

RDMA_CM_EVENT_REJECTED

远程端点拒绝连接请求或响应

RDMA_CM_EVENT_ESTABLISHED

已与远程端点建立连接

RDMA_CM_EVENT_DISCONNECTED

断开连接

RDMA_CM_EVENT_DEVICE_REMOVAL

已删除与 rdma_cm_id 关联的本地 RDMA 设备

RDMA_CM_EVENT_MULTICAST_JOIN

多播加入操作(rdma_join_multicast)成功完成

RDMA_CM_EVENT_MULTICAST_ERROR

加入多播组时发生错误,如果已加入该组,则在现有组中发生错误

RDMA_CM_EVENT_ADDR_CHANGE

通过地址解析与该 ID 相关联的网络设备改变了其 HW 地址

RDMA_CM_EVENT_TIMEWAIT_EXIT

与连接关联的 QP 已退出其 timewait 状态,现在可以重新使用

0条评论
0 / 1000
h****n
1文章数
0粉丝数
h****n
1 文章 | 0 粉丝
h****n
1文章数
0粉丝数
h****n
1 文章 | 0 粉丝
原创

RDMA Verbs API基本使用方法

2023-06-20 03:07:24
904
0

一、  概述

Verbs api是由OpenFabrics推动实现的一组RDMA应用编程接口(API),类似于socket api,它提供了一组函数和数据结构,用于管理RDMA通信和数据传输。verbs api一般由两部分构成:

IB_VERBS

以ibv_xx(用户态)或者ib_xx(内核态)为前缀,主要用于收发数据,当然也可用于连接管理,是基础的底层编程接口,程序可以完全基于IB_VERBS编写。

RDMA_CM

以rdma_xx为前缀,在IB_VERBS基础上封装实现的一组接口,主要用于管理连接,让通信双方可以确定和交换相关信息,RDMA_CM也提供了相应的接口用于收发数据。

二、  使用方法

下文将从基本的server与client连接,client向server发送信息来描述RDMA Verbs API的使用方法

1.  server连接前准备

struct sockaddr_in addr;
struct rdma_cm_event *event = NULL;
struct rdma_cm_id *listener = NULL;
struct rdma_event_channel *ec = NULL;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;

ec = rdma_create_event_channel();
rdma_create_id(ec, &listener, NULL, RDMA_PS_TCP);
rdma_bind_addr(listener, (struct sockaddr *)&addr);
// 第二个参数10代表传入连接请求的积压数为10
rdma_listen(listener, 10);

while (rdma_get_cm_event(ec, &event) == 0) {
    struct rdma_cm_event event_copy;

    memcpy(&event_copy, event, sizeof(*event));
    rdma_ack_cm_event(event);
    // ...
}
  • rdma_create_event_channel()

创建用于报告事件的事件通道,client和server之间通过返回值rdma_event_channel结构体来传递事件信息。

  • rdma_create_id()

创建用于跟踪通信信息的rdma_cm_id,类似于socket,通过关联的事件通道报告 rdma_cm_id 上的通信事件。参数RDMA_PS_TCP表示供可靠的,面向连接的 QP 通信。

  • rdma_bind_addr()

将源地址与 rdma_cm_id 相关联,如果绑定到特定的本地地址,则 rdma_cm_id 也将绑定到本地 RDMA 设备。

  • rdma_listen()

监听client端的连接请求。

  • rdma_get_cm_event()

等待待处理的事件,如果没有事件则阻塞调用,直到收到事件。 必须通过调用 rdma_ack_cm_event来确认报告的所有事件。

  • rdma_ack_cm_event()

释放通信事件,由rdma_get_cm_event分配的所有事件必须释放,成功获取和确认之间应该存在一对一的对应关系。

2.  client连接前准备

struct addrinfo *addr;
struct rdma_cm_event *event = NULL;
struct rdma_cm_id *conn= NULL;
struct rdma_event_channel *ec = NULL;

ec = rdma_create_event_channel();
rdma_create_id(ec, &conn, NULL, RDMA_PS_TCP);
rdma_resolve_addr(conn, NULL, addr->ai_addr, TIMEOUT_IN_MS);

while (rdma_get_cm_event(ec, &event) == 0) {
    struct rdma_cm_event event_copy;

    memcpy(&event_copy, event, sizeof(*event));
    rdma_ack_cm_event(event);
    // ...
}
  • rdma_create_event_channel()

与server端类似,建立事件通道

  • rdma_create_id()

与server端类似,创建用于跟踪通信信息的rdma_cm_id。

  • rdma_resolve_addr()

将目标源地址从 IP 地址解析为 RDMA 地址,rdma_cm_id 绑定到该地址。

  • rdma_get_cm_event()

与server端类似。

  • rdma_ack_cm_event()

与server端类似。

3.  client发起连接请求

解析完IP地址后,事件通道会报告RDMA_CM_EVENT_ADDR_RESOLVED事件,之后还需要进行RDMA上下文创建和QP创建等准备工作,之后再解析RDMA路由。

//自定义的RDMA连接结构体
struct connection {
// 指向该连接使用的rdma id的指针
struct rdma_cm_id *id;
// 指向该连接使用的qp的指针
struct ibv_qp *qp;
// 指向接收队列内存区域的指针
struct ibv_mr *recv_mr;
// 指向发送队列内存区域的指针
struct ibv_mr *send_mr;
// 接收队列内存区域起始地址
char *recv_region;
// 发送队列内存区域起始地址
char *send_region;
// 已完成的请求数
int num_completions;
};

int on_addr_resolved(struct rdma_cm_id *id)
{
    struct ibv_qp_init_attr qp_attr;
    struct connection *conn;
    // 建立RDMA上下文
    build_context(id->verbs);
    // 初始化QP属性
    memset(qp_attr, 0, sizeof(*qp_attr));
    qp_attr->send_cq = s_ctx->cq;
    qp_attr->recv_cq = s_ctx->cq;
    qp_attr->qp_type = IBV_QPT_RC;
    qp_attr->cap.max_send_wr = 10;
    qp_attr->cap.max_recv_wr = 10;
    qp_attr->cap.max_send_sge = 1;
    qp_attr->cap.max_recv_sge = 1;
    rdma_create_qp(id, s_ctx->pd, &qp_attr);
    id->context = conn = (struct connection *)malloc(sizeof(struct connection));
    conn->id = id;
    conn->qp = id->qp;
    conn->num_completions = 0;
    // 注册内存区域
    register_memory(conn);
    // 创建接收请求
    post_receives(conn);
    rdma_resolve_route(id, TIMEOUT_IN_MS);
    return 0;
}
  • rdma_create_qp()

分配与指定的 rdma_cm_id 相关联的 QP,并将其转换为发送和 接收。创建的 QP 的实际功能和属性将通过qp_attr 参数返回给用户。

  • rdma_resolve_route()

将 RDMA 路由解析为目标地址以建立连接。

build_context

s_ctx = (struct context *)malloc(sizeof(struct context));
s_ctx->ctx = verbs;
s_ctx->pd = ibv_alloc_pd(s_ctx->ctx);
s_ctx->comp_channel =ibv_create_comp_channel(s_ctx->ctx);
s_ctx->cq = ibv_create_cq(s_ctx->ctx, 10, NULL,s_ctx->comp_channel, 0));
ibv_req_notify_cq(s_ctx->cq, 0);
  • ibv_alloc_pd()

创建一个保护域(PD),返回指向保护域的指针。

  • ibv_create_comp_channel()

创建完成通道,完成信道(Completion channel,CC) 是用户在新的完成队列事件(CQE)已被放置在完成队列(CQ)上时接收通知的机制。

  • ibv_create_cq()

创建完成队列(CQ),返回指向CQ的指针。

  • ibv_req_notify_cq()

为指示的完成队列(CQ)提供通知机制,当完成队列条目 (CQE)被放置在 CQ 上时,完成事件将被发送到与 CQ 相关联的完成信道(CC)。

register_memory

conn->send_region = malloc(BUFFER_SIZE);
conn->recv_region = malloc(BUFFER_SIZE);

// 分别为发送和接收注册内存区域
conn->send_mr = ibv_reg_mr(
s_ctx->pd,
conn->send_region,
BUFFER_SIZE,
0);
conn->recv_mr = ibv_reg_mr(
s_ctx->pd,
conn->recv_region,
BUFFER_SIZE,
IBV_ACCESS_LOCAL_WRITE);
  • ibv_reg_mr()

注册一个内存区域(MR),将其与保护域(PD)相关联,并为其分配本地和远程密钥(lkey,rkey)。IBV_ACCESS_LOCAL_WRITE表示允许本地主机写访问权限。

post_receives

struct ibv_recv_wr wr, *bad_wr = NULL;
struct ibv_sge sge;
// 填充wr和sge
wr.wr_id = (uintptr_t)conn;
wr.next = NULL;
wr.sg_list = &sge;
wr.num_sge = 1;

sge.addr = (uintptr_t)conn->recv_region;
sge.length = BUFFER_SIZE;
sge.lkey = conn->recv_mr->lkey;

ibv_post_recv(conn->qp, &wr, &bad_wr);
  • ibv_post_recv()

post一个receive请求,以便能够顺利接收client端send过来的数据。ibv_post_recv操作最好在链接建立之前进行,以避免通信的另一端在发送数据时出现RNR问题(receiver-not-ready)

建立连接

路由解析完成后,事件通道会报告RDMA_CM_EVENT_ROUTE_RESOLVED事件,然后就可以建立连接了。

int on_route_resolved(struct rdma_cm_id *id)
{
    struct rdma_conn_param cm_params;
    memset(&cm_params, 0, sizeof(cm_params));
    rdma_connect(id, &cm_params);
    return 0;
}
  • rdma_connect()

启动活动连接请求,对于已连接的rdma_cm_id,会启动对远程目标的连接请求。请求发送后server端会收到RDMA_CM_EVENT_CONNECT_REQUEST事件,事件对象的内部可通过private_data属性来设置一些私有信息。

发送连接请求后,client继续阻塞调用rdma_get_cm_event,等待server端接收连接请求,接收成功会返回RDMA_CM_EVENT_ESTABLISHED事件。

4.  server端accpet连接请求

收到client端的RDMA_CM_EVENT_CONNECT_REQUEST事件后,首先从事件对象中取出rdma_cm_id来作为与对端通信的唯一标识,accpet该请求。

int on_connect_request(struct rdma_cm_id *id)
{
    struct ibv_qp_init_attr qp_attr;
    struct rdma_conn_param cm_params;
    // 自定义RDMA连接结构体,同client
    struct connection *conn;
    // 和client相同的创建RDMA上下文,QP,注册内存,post_receive等操作
    // ...
    memset(&cm_params, 0, sizeof(cm_params));
    rdma_accept(id, &cm_params);
    return 0;
}
  • rdma_accept()

对client的连接请求进行接收

5.  client端发送数据

int on_connection(void *context)
{
    struct connection *conn = (struct connection *)context;
    struct ibv_send_wr wr, *bad_wr = NULL;
    struct ibv_sge sge;
    // 将发送信息写入发送内存区域
    snprintf(conn->send_region, BUFFER_SIZE, "message from active/client side with pid %d", getpid());
    memset(&wr, 0, sizeof(wr));
    // 填充wr和sge
    wr.wr_id = (uintptr_t)conn;
    wr.opcode = IBV_WR_SEND;
    wr.sg_list = &sge;
    wr.num_sge = 1;
    wr.send_flags = IBV_SEND_SIGNALED;
    sge.addr = (uintptr_t)conn->send_region;
    sge.length = BUFFER_SIZE;
    sge.lkey = conn->send_mr->lkey;

    ibv_post_send(conn->qp, &wr, &bad_wr);
    return 0;
}
  • ibv_post_send()

将 WR 的链接列表发布到队列对(QP)的发送队列。此操作用于启动包括RDMA操作的所有通信。 WR列表的处理在第一个错误处停止,并且在 bad_wr 中返回指向错误WR的指针。

ibv_post_send处理结束后会向发送队列(SQ)提交一个工作请求(WQE),然后会通知网卡硬件进行后续发送处理。

6.  sever端接收数据

server端在建立连接前已经执行过ibv_post_recv操作,可以通过ibv_poll_cq函数对CQ进行轮询来感知CQE生成。

struct ibv_cq *cq;
struct ibv_wc wc;

while (1) {
    ibv_get_cq_event(s_ctx->comp_channel, &cq, &ctx);
    // 参数1表示一次确认一个事件
    ibv_ack_cq_events(cq, 1);
    ibv_req_notify_cq(cq, 0);

    while (ibv_poll_cq(cq, 1, &wc)) {
        if (wc->status != IBV_WC_SUCCESS)
        printf("on_completion: status is not IBV_WC_SUCCESS.");
        if (wc->opcode & IBV_WC_RECV) {
            // 该请求为接收请求
            struct connection *conn = (struct connection *)(uintptr_t)wc->wr_id;
            // 从接收内存区域中读取client端发送的信息
            printf("received message: %s\n", conn->recv_region);
        } else if (wc->opcode == IBV_WC_SEND) {
            // 该请求为发送请求
            printf("send completed successfully.\n");
        }
    }
}
  • ibv_get_cq_event()

等待在指示的完成信道(CC)上有发送通知,这是一个阻塞操作,发送的每个通知必须使用 ibv_ack_cq_events 操作进行确认。ctx: 在 ibv_create_cq 中设置的用户提供的上下文对象。

  • ibv_ack_cq_events()

确认从 ibv_get_cq_event 收到的事件。虽然从 ibv_get_cq_event 收到的每个通知都只为一个事件,但用户可以通过一次调用 ibv_ack_cq_events 来确认多个事件。

  • ibv_poll_cq()

从完成队列(CQ)中检索 CQE,将CQE存储在工作完成(Work Complete, WC)数组中,返回WC数组中CQE的数量。

接收完对端传来的信息后,需要再次调用ibv_post_recv,以便接收下一个请求。

7.  断开连接

client端完成发送操作后,断开连接

//...
// conn为前文定义的connection结构体
rdma_disconnect(conn->id);
  • rdma_disconnect()

断开连接,并将任何关联的 QP 转换为错误状态。成功断开连接后,将在连接的两端生成 RDMA_CM_EVENT_DISCONNECTED 事件。

事件通道获取到RDMA_CM_EVENT_DISCONNECTED事件后,执行RDMA内存释放相关操作。

int on_disconnect(struct rdma_cm_id *id)
{
    struct connection *conn = (struct connection *)id->context;

    printf("disconnected.\n");

    rdma_destroy_qp(id);

    ibv_dereg_mr(conn->send_mr);
    ibv_dereg_mr(conn->recv_mr);

    free(conn->send_region);
    free(conn->recv_region);

    free(conn);

    rdma_destroy_id(id);
}
  • rdma_destroy_qp()

销毁在 rdma_cm_id 上分配的 QP。

  • ibv_dereg_mr()

释放内存注册区域(MR)。

  • rdma_destroy_id()

销毁指定的 rdma_cm_id 并取消任何未完成的异步操作,调用前必须使用 rdma_destroy_qp 销毁关联的 QP。

最后client端关闭事件通道

rdma_destroy_event_channel(ec);

  • rdma_destroy_event_channel()

关闭事件通道,释放与事件通道关联的所有资源,并关闭关联的文件描述符。

server端断开连接的方式与client端大致相同,区别在于server端在最后关闭通信通道前需要销毁监听器。

// ...
rdma_destroy_id(listener);
rdma_destroy_event_channel(ec);

三、  总结

上文详细讲述了使用RDMA Verbs API编写基本RDMA Send/Receive操作的方法,介绍了使用到的API及其作用,希望能对准备进行RDMA应用开发工作的人有所帮助。

附:RDMA事件状态表

RDMA_CM_EVENT_ADDR_RESOLVED

地址解析(rdma_resolve_addr)已成功完成

RDMA_CM_EVENT_ADDR_ERROR

地址解析(rdma_resolve_addr)失败

RDMA_CM_EVENT_ROUTE_RESOLVED

路由解析(rdma_resolve_route)已成功完成

RDMA_CM_EVENT_ROUTE_ERROR

路由解析(rdma_resolve_route)失败

RDMA_CM_EVENT_CONNECT_REQUEST

在被动端生成新的连接请求

RDMA_CM_EVENT_CONNECT_RESPONSE

在活动侧生成,对连接请求的成功响应。

RDMA_CM_EVENT_CONNECT_ERROR

表示尝试建立或建立连接时发生错误。可以在连接的主动或被动侧生成

RDMA_CM_EVENT_UNREACHABLE

在活动端生成,远程端点无法访问或无法响应连接请求

RDMA_CM_EVENT_REJECTED

远程端点拒绝连接请求或响应

RDMA_CM_EVENT_ESTABLISHED

已与远程端点建立连接

RDMA_CM_EVENT_DISCONNECTED

断开连接

RDMA_CM_EVENT_DEVICE_REMOVAL

已删除与 rdma_cm_id 关联的本地 RDMA 设备

RDMA_CM_EVENT_MULTICAST_JOIN

多播加入操作(rdma_join_multicast)成功完成

RDMA_CM_EVENT_MULTICAST_ERROR

加入多播组时发生错误,如果已加入该组,则在现有组中发生错误

RDMA_CM_EVENT_ADDR_CHANGE

通过地址解析与该 ID 相关联的网络设备改变了其 HW 地址

RDMA_CM_EVENT_TIMEWAIT_EXIT

与连接关联的 QP 已退出其 timewait 状态,现在可以重新使用

文章来自个人专栏
文章 | 订阅
0条评论
0 / 1000
请输入你的评论
0
0