乐于分享
好东西不私藏

RDMA: librdmacm架构及软件接口工作流程和使用方法

RDMA: librdmacm架构及软件接口工作流程和使用方法

knowledge base:

RDMA:  RDMA硬件架构和软件使能流程

RDMA: 连接组件CM架构及工作机制

RDMA: Verbs API 软件架构及使能流程

librdmacm 这是一个用于 RDMA(远程直接内存访问)通信管理的用户态库。

一、librdmacm 整体架构

librdmacm(RDMA Connection Manager)是构建在 libibverbs 之上的高层库,主要提供连接管理和地址解析功能。实际开发中,通常使用 librdmacm 处理连接,然后使用 libibverbs 进行高性能数据传输。

1.1 架构层次

┌─────────────────────────────────────────┐
│         用户应用程序 (User App)          │
├─────────────────────────────────────────┤
│      librdmacm (连接管理/地址解析)       │  ← 您使用的这层
│  - 连接建立/断开、地址解析、事件通知      │
├─────────────────────────────────────────┤
│      libibverbs (底层 Verbs 接口)        │
│  - QP/CQ/MR 管理、实际数据传输           │
├─────────────────────────────────────────┤
│        内核 RDMA 子系统                  │
│  - uverbs、rdma_cm、IB 核心驱动          │
├─────────────────────────────────────────┤
│        RDMA 硬件 (NIC)                  │
│  - Mellanox、Intel、Broadcom 等          │
└─────────────────────────────────────────┘

1.2 核心组件

二、核心工作流程

2.1 服务端工作流程

// 1. 创建事件通道
struct rdma_event_channel *ec =
 rdma_create_event_channel();

// 2. 创建 CM ID

struct rdma_cm_id *listen_id;

rdma_create_id(ec, &listen_id, NULL, RDMA_PS_TCP);

// 3. 绑定地址

struct sockaddr_in addr =
 {...};
rdma_bind_addr(listen_id, (struct sockaddr *)&addr);

// 4. 开始监听

rdma_listen(listen_id, backlog);

// 5. 等待连接请求 (循环处理事件)

struct rdma_cm_event *event;

rdma_get_cm_event(ec, &event);
if
 (event->event == RDMA_CM_EVENT_CONNECT_REQUEST) {
    struct rdma_cm_id *conn_id =
 event->id;  // 新连接ID

    // 6. 创建 QP、注册内存、接受连接

    ibv_create_qp(...);           // 创建 QP
    rdma_accept(conn_id, &param); // 接受连接
}
rdma_ack_cm_event(event);  // 必须确认事件

// 7. 数据传输 (通过 conn_id->qp 使用 libibverbs)

// ...


// 8. 断开连接

rdma_disconnect(conn_id);
rdma_destroy_id(conn_id);
rdma_destroy_id(listen_id);
rdma_destroy_event_channel(ec);

2.2 客户端工作流程

// 1. 创建事件通道
struct rdma_event_channel *ec =
 rdma_create_event_channel();

// 2. 创建 CM ID

struct rdma_cm_id *conn_id;

rdma_create_id(ec, &conn_id, NULL, RDMA_PS_TCP);

// 3. 解析地址 (可选,也可直接连接)

struct rdma_addrinfo *res;

rdma_getaddrinfo("server_ip", "port", NULL, &res);

// 4. 建立连接

rdma_resolve_addr(conn_id, NULL, res->ai_dst_addr, timeout);  // 解析地址
// 等待 RDMA_CM_EVENT_ADDR_RESOLVED 事件

rdma_resolve_route(conn_id, timeout);  // 解析路由
// 等待 RDMA_CM_EVENT_ROUTE_RESOLVED 事件

rdma_connect(conn_id, &conn_param);    // 发送连接请求
// 等待 RDMA_CM_EVENT_ESTABLISHED 事件


// 5. 数据传输

// ...


// 6. 断开连接

rdma_disconnect(conn_id);
rdma_destroy_id(conn_id);
rdma_destroy_event_channel(ec);

2.3 关键事件类型

三、核心 API 详解

3.1 连接管理 API

// 创建/销毁事件通道
struct
 rdma_event_channel *rdma_create_event_channel(void);
void
 rdma_destroy_event_channel(struct rdma_event_channel *channel);

// 创建/销毁 CM ID

int
 rdma_create_id(struct rdma_event_channel *channel,
                   struct
 rdma_cm_id **id,
                   void
 *context,
                   enum
 rdma_port_space ps)
;  // RDMA_PS_TCP / RDMA_PS_UDP

void
 rdma_destroy_id(struct rdma_cm_id *id);

// 地址绑定与解析

int
 rdma_bind_addr(struct rdma_cm_id *id, struct sockaddr *addr);
int
 rdma_resolve_addr(struct rdma_cm_id *id, struct sockaddr *src_addr,
                      struct
 sockaddr *dst_addr, int timeout_ms)
;
int
 rdma_resolve_route(struct rdma_cm_id *id, int timeout_ms);

// 监听/连接

int
 rdma_listen(struct rdma_cm_id *id, int backlog);
int
 rdma_connect(struct rdma_cm_id *id, struct rdma_conn_param *conn_param);
int
 rdma_accept(struct rdma_cm_id *id, struct rdma_conn_param *conn_param);
int
 rdma_reject(struct rdma_cm_id *id, const void *private_data, uint8_t private_data_len);
int
 rdma_disconnect(struct rdma_cm_id *id);

3.2 事件处理 API

// 获取/确认事件 (阻塞式)
int
 rdma_get_cm_event(struct rdma_event_channel *channel,
                      struct
 rdma_cm_event **event)
;

// 必须调用以释放事件资源

int
 rdma_ack_cm_event(struct rdma_cm_event *event);

// 事件结构体关键字段

struct rdma_cm_event {

    enum rdma_cm_event_type event;
  // 事件类型
    int
 status;                     // 状态码 (0=成功)
    struct rdma_cm_id *id;
          // 关联的 CM ID
    void
 *context;                  // 用户上下文
    union {

        struct rdma_conn_param conn;
      // 连接参数
        struct rdma_ud_param   ud;
        // UD 服务参数
    } param;
};

3.3 数据传输准备 API

// 创建 QP (通常在收到连接请求后或地址解析后)
struct
 ibv_qp *ibv_create_qp(struct ibv_pd *pd, struct ibv_qp_init_attr *qp_init_attr);

// 注册内存区域

struct
 ibv_mr *ibv_reg_mr(struct ibv_pd *pd, void *addr,
                          size_t
 length, int access)
;

// 创建完成队列

struct
 ibv_cq *ibv_create_cq(struct ibv_context *context, int cqe,
                             void
 *cq_context, struct ibv_comp_channel *channel,
                             int
 comp_vector)
;

四、完整使用示例

4.1 服务端示例

SW arch

#include <rdma/rdma_cma.h>
#include <stdio.h>

#include <stdlib.h>

#include <string.h>


#define TEST_NZ(x) do { if ( (x)) die("error: " #x " failed."); } while (0)

#define TEST_Z(x)  do { if (!(x)) die("error: " #x " failed."); } while (0)


void
 die(const char *reason) {
    fprintf
(stderr, "%s\n", reason);
    exit
(EXIT_FAILURE);
}

// 连接上下文

struct conn_context {

    struct ibv_qp *qp;

    struct ibv_mr *mr;

    char
 *buf;
};

void
 on_connect_request(struct rdma_cm_id *id) {
    struct conn_context *ctx =
 calloc(1, sizeof(struct conn_context));

    // 1. 创建保护域

    struct ibv_pd *pd =
 ibv_alloc_pd(id->verbs);
    TEST_Z(pd);

    // 2. 创建完成队列

    struct ibv_cq *cq =
 ibv_create_cq(id->verbs, 10, NULL, NULL, 0);
    TEST_Z(cq);

    // 3. 创建 QP

    struct ibv_qp_init_attr qp_attr =
 {
        .send_cq = cq,
        .recv_cq = cq,
        .qp_type = IBV_QPT_RC,  // Reliable Connection
        .cap = {
            .max_send_wr = 10,
            .max_recv_wr = 10,
            .max_send_sge = 1,
            .max_recv_sge = 1
        }
    };
    TEST_NZ(rdma_create_qp(id, pd, &qp_attr));

    // 4. 注册内存

    ctx->buf = malloc(256);
    ctx->mr = ibv_reg_mr(pd, ctx->buf, 256,
                         IBV_ACCESS_LOCAL_WRITE | IBV_ACCESS_REMOTE_READ);
    TEST_Z(ctx->mr);

    id->context = ctx;

    // 5. 接受连接

    struct rdma_conn_param cm_params =
 {0};
    TEST_NZ(rdma_accept(id, &cm_params));
    printf
("Connection accepted\n");
}

void
 on_disconnect(struct rdma_cm_id *id) {
    struct conn_context *ctx =
 id->context;
    rdma_destroy_qp(id);
    ibv_dereg_mr(ctx->mr);
    free
(ctx->buf);
    free
(ctx);
    printf
("Connection closed\n");
}

int
 main(int argc, char **argv) {
    struct rdma_event_channel *ec =
 rdma_create_event_channel();
    TEST_Z(ec);

    struct rdma_cm_id *listener;

    TEST_NZ(rdma_create_id(ec, &listener, NULL, RDMA_PS_TCP));

    struct sockaddr_in addr =
 {
        .sin_family = AF_INET,
        .sin_port = htons(12345),
        .sin_addr.s_addr = INADDR_ANY
    };
    TEST_NZ(rdma_bind_addr(listener, (struct sockaddr *)&addr));
    TEST_NZ(rdma_listen(listener, 10));
    printf
("Listening on port 12345...\n");

    struct rdma_cm_event *event;

    while
 (rdma_get_cm_event(ec, &event) == 0) {
        struct rdma_cm_id *id =
 event->id;

        switch
 (event->event) {
            case
 RDMA_CM_EVENT_CONNECT_REQUEST:
                on_connect_request(id);
                break
;
            case
 RDMA_CM_EVENT_ESTABLISHED:
                printf
("Connection established\n");
                break
;
            case
 RDMA_CM_EVENT_DISCONNECTED:
                on_disconnect(id);
                break
;
            default
:
                printf
("Event: %s\n", rdma_event_str(event->event));
                break
;
        }
        rdma_ack_cm_event(event);  // 必须确认!
    }

    rdma_destroy_id(listener);
    rdma_destroy_event_channel(ec);
    return
 0;
}

4.2 客户端示例

#include <rdma/rdma_cma.h>
#include <stdio.h>

#include <stdlib.h>

#include <string.h>


#define TEST_NZ(x) do { if ( (x)) die("error: " #x " failed."); } while (0)

#define TEST_Z(x)  do { if (!(x)) die("error: " #x " failed."); } while (0)


void
 die(const char *reason) {
    fprintf
(stderr, "%s\n", reason);
    exit
(EXIT_FAILURE);
}

int
 main(int argc, char **argv) {
    if
 (argc != 2) {
        fprintf
(stderr, "usage: %s <server_ip>\n", argv[0]);
        return
 1;
    }

    struct rdma_event_channel *ec =
 rdma_create_event_channel();
    TEST_Z(ec);

    struct rdma_cm_id *conn_id;

    TEST_NZ(rdma_create_id(ec, &conn_id, NULL, RDMA_PS_TCP));

    // 解析服务器地址

    struct rdma_addrinfo *res;

    TEST_NZ(rdma_getaddrinfo(argv[1], "12345", NULL, &res));

    // 解析地址

    TEST_NZ(rdma_resolve_addr(conn_id, NULL, res->ai_dst_addr, 2000));

    struct rdma_cm_event *event;

    while
 (rdma_get_cm_event(ec, &event) == 0) {
        if
 (event->event == RDMA_CM_EVENT_ADDR_RESOLVED) {
            printf
("Address resolved\n");
            rdma_resolve_route(conn_id, 2000);
        } else if (event->event == RDMA_CM_EVENT_ROUTE_RESOLVED) {
            printf
("Route resolved\n");

            // 创建 QP (类似服务端)

            struct ibv_pd *pd =
 ibv_alloc_pd(conn_id->verbs);
            struct ibv_cq *cq =
 ibv_create_cq(conn_id->verbs, 10, NULL, NULL, 0);
            struct ibv_qp_init_attr qp_attr =
 {
                .send_cq = cq, .recv_cq = cq,
                .qp_type = IBV_QPT_RC,
                .cap = { .max_send_wr = 10, .max_recv_wr = 10,
                         .max_send_sge = 1, .max_recv_sge = 1 }
            };
            rdma_create_qp(conn_id, pd, &qp_attr);

            // 注册内存

            char
 *buf = malloc(256);
            struct ibv_mr *mr =
 ibv_reg_mr(pd, buf, 256,
                IBV_ACCESS_LOCAL_WRITE | IBV_ACCESS_REMOTE_READ);

            // 发送连接请求

            struct rdma_conn_param cm_params =
 {0};
            rdma_connect(conn_id, &cm_params);
        } else if (event->event == RDMA_CM_EVENT_ESTABLISHED) {
            printf
("Connected! Ready for data transfer.\n");
            // 这里可以进行 RDMA 操作 (Send/Recv/Read/Write)

            // ...

            break
;
        } else if (event->event == RDMA_CM_EVENT_REJECTED) {
            die("Connection rejected");
        }
        rdma_ack_cm_event(event);
    }

    // 断开连接

    rdma_disconnect(conn_id);
    rdma_destroy_id(conn_id);
    rdma_destroy_event_channel(ec);
    return
 0;
}

五、关键使用要点

5.1 必须遵守的规则

  1. 1. 事件确认:每次 rdma_get_cm_event() 后必须调用 rdma_ack_cm_event(),否则会导致资源泄漏
  2. 2. QP 创建时机:服务端在 CONNECT_REQUEST 事件中创建,客户端在 ROUTE_RESOLVED 后创建
  3. 3. 资源释放顺序:先 rdma_destroy_qp() → 再 ibv_dereg_mr() → 最后 rdma_destroy_id()

5.2 两种使用模式

5.3 与 libibverbs 的关系

  • • librdmacm 负责:连接建立、地址解析、事件通知
  • • libibverbs 负责:QP/CQ/MR 管理、实际 RDMA 操作 (post_send, post_recv, poll_cq)
  • • 协同工作:librdmacm 创建的 rdma_cm_id 包含 ibv_qp 指针,数据传输需通过 verbs API 操作

    5.4 编译与运行

# 编译
gcc server.c -o server -lrdmacm -libverbs
gcc client.c -o client -lrdmacm -libverbs

# 运行 (需要 RDMA 网卡,如 Mellanox ConnectX 系列)

./server
./client <server_ip>

六、总结

librdmacm 简化了 RDMA 编程中最复杂的连接管理部分,其核心优势在于:

  1. 1. 抽象连接流程:将复杂的 RDMA 连接建立过程抽象为事件驱动模型
  2. 2. 统一地址解析:支持 IP 地址和 RDMA 特定地址的解析
  3. 3. 与 verbs 解耦:连接管理独立于数据传输,便于分层设计