Linux InfiniBand

Linux InfiniBand

IB网络是RDMA的一种硬件实现,Infiniband是一种专为RDMA设计的网络,从硬件级别保证可靠传输。
Infiniband,支持RDMA的新一代网络协议。 由于这是一种新的网络技术,因此需要支持该技术的NIC和交换机。
因此,如果需要了解Linux下InfiniBand的使用方式,需要先了解RDMA的工作工程。

Linux RDMA core

仓库地址 : Linux RDMA core

libibverbs

可以从构词中发现 lib-ib-verbs,该库是为IB网络定义了RDMA行为。通过IBM提供的手册可以看到对该库的介绍。
Libibverbs库使用户空间进程能够使用远程直接内存访问 (RDMA) 动词。
Libibverbs库在InfiniBand体系结构规范和 RDMA 协议动词规范中进行了描述。

librdmacm

以下是IBM手册对librdmacm的介绍。
librdmacm库提供通信管理器 (CM) 功能和一组通用的远程直接内存访问 (RDMA) CM 接口,这些接口在不同的结构上运行,例如 InfiniBand (IB)、RDMA over Converged Ethernet (RoCE) 或互联网广域网RDMA 协议 (iWARP)。

rc_pingpong源码阅读

rc_pingpong是linux rdma core项目中,libibverbs中的一个案例。基本阐述了InfiniBand Verbs API的使用方法。

程序流程

  • 在操作系统中申请资源
  • 从Verbs API申请资源
  • 使用TCP交换IB连接信息
  • 在两个IB端口中创建一个连接
  • 在连接上传输数据
  • 传输成功

    相关函数说明

    1
    2
    3
    4
    5
    6
    7
    8
    9
    //InfiniBand Devices
    struct ibv_device;
    // ibv_get_device_list会返回一个可用的IB设备列表。
    struct ibv_device **ibv_get_device_list(int *num_devices);
    // 打开一个HCA
    struct ibv_context *ibv_open_device(struct ibv_device *device);
    // 初始化上下文
    static struct pingpong_context *pp_init_ctx(struct ibv_device *ib_dev, int size, int rx_depth, int port,int use_event);

    main函数过程

    变量的定义和初始化
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    struct ibv_device      **dev_list;

        struct ibv_device   *ib_dev;

        struct pingpong_context *ctx;

        struct pingpong_dest     my_dest;

        struct pingpong_dest    *rem_dest;

        struct timeval           start, end;

        char                    *ib_devname = NULL;

        char                    *servername = NULL;

        unsigned int             port = 18515;

        int                      ib_port = 1;

        unsigned int             size = 4096;

        enum ibv_mtu         mtu = IBV_MTU_1024;

        unsigned int             rx_depth = 500;

        unsigned int             iters = 1000;

        int                      use_event = 0;

        int                      routs;

        int                      rcnt, scnt;

        int                      num_cq_events = 0;

        int                      sl = 0;

        int          gidx = -1;

        char             gid[33];

        struct ts_params     ts;
    参数解析
    对命令行输入的参数进行解析
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    while (1) {

            int c;



            static struct option long_options[] = {

                { .name = "port",     .has_arg = 1, .val = 'p' },

                { .name = "ib-dev",   .has_arg = 1, .val = 'd' },

                { .name = "ib-port",  .has_arg = 1, .val = 'i' },

                { .name = "size",     .has_arg = 1, .val = 's' },

                { .name = "mtu",      .has_arg = 1, .val = 'm' },

                { .name = "rx-depth", .has_arg = 1, .val = 'r' },

                { .name = "iters",    .has_arg = 1, .val = 'n' },

                { .name = "sl",       .has_arg = 1, .val = 'l' },

                { .name = "events",   .has_arg = 0, .val = 'e' },

                { .name = "gid-idx",  .has_arg = 1, .val = 'g' },

                { .name = "odp",      .has_arg = 0, .val = 'o' },

                { .name = "iodp",     .has_arg = 0, .val = 'O' },

                { .name = "prefetch", .has_arg = 0, .val = 'P' },

                { .name = "ts",       .has_arg = 0, .val = 't' },

                { .name = "chk",      .has_arg = 0, .val = 'c' },

                { .name = "dm",       .has_arg = 0, .val = 'j' },

                { .name = "new_send", .has_arg = 0, .val = 'N' },

                {}

            };



            c = getopt_long(argc, argv, "p:d:i:s:m:r:n:l:eg:oOPtcjN",

                    long_options, NULL);



            if (c == -1)

                break;



            switch (c) {

            case 'p':

                port = strtoul(optarg, NULL, 0);

                if (port > 65535) {

                    usage(argv[0]);

                    return 1;

                }

                break;



            case 'd':

                ib_devname = strdupa(optarg);

                break;



            case 'i':

                ib_port = strtol(optarg, NULL, 0);

                if (ib_port < 1) {

                    usage(argv[0]);

                    return 1;

                }

                break;



            case 's':

                size = strtoul(optarg, NULL, 0);

                break;



            case 'm':

                mtu = pp_mtu_to_enum(strtol(optarg, NULL, 0));

                if (mtu == 0) {

                    usage(argv[0]);

                    return 1;

                }

                break;



            case 'r':

                rx_depth = strtoul(optarg, NULL, 0);

                break;



            case 'n':

                iters = strtoul(optarg, NULL, 0);

                break;



            case 'l':

                sl = strtol(optarg, NULL, 0);

                break;



            case 'e':

                ++use_event;

                break;



            case 'g':

                gidx = strtol(optarg, NULL, 0);

                break;



            case 'o':

                use_odp = 1;

                break;

            case 'P':

                prefetch_mr = 1;

                break;

            case 'O':

                use_odp = 1;

                implicit_odp = 1;

                break;

            case 't':

                use_ts = 1;

                break;

            case 'c':

                validate_buf = 1;

                break;



            case 'j':

                use_dm = 1;

                break;



            case 'N':

                use_new_send = 1;

                break;



            default:

                usage(argv[0]);

                return 1;

            }

        }
    初始化上下文
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
        page_size = sysconf(_SC_PAGESIZE);
        dev_list = ibv_get_device_list(NULL);
        if (!dev_list) {

            perror("Failed to get IB devices list");

            return 1;

        }
        ...
        ctx = pp_init_ctx(ib_dev, size, rx_depth, ib_port, use_event);
    pp_init_ctx工作过程
    在进行这一步之前,已经找到了所需要的IB设备。
    pp_init_ctx函数原型
    1
    static struct pingpong_context *pp_init_ctx(struct ibv_device *ib_dev, int size, int rx_depth, int port,int use_event);
    分配工作缓冲区
    1
    2
    3
    4
    5
    6
    7
    8
    9
        ctx->buf = memalign(page_size, size);

        if (!ctx->buf) {

            fprintf(stderr, "Couldn't allocate work buf.\n");

            goto clean_ctx;

        }
    打开设备,且创建设备上下文。
    设备上下文包含了设备驱动及相关操作。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
        ctx->context = ibv_open_device(ib_dev);  // 打开设备

        if (!ctx->context) {

            fprintf(stderr, "Couldn't get context for %s\n",

                ibv_get_device_name(ib_dev));

            goto clean_buffer;

        }
    分配保护域(PD, Protection Domain)
    根据InfiniBand规范,保护域允许客户端在InfiniBand发送和接收期间控制哪些远程计算机可以访问其内存区域。
    1
    2
    3
    4
    5
    6
    7
    8
    9
        ctx->pd = ibv_alloc_pd(ctx->context);   // 分配

        if (!ctx->pd) {

            fprintf(stderr, "Couldn't allocate PD\n");

            goto clean_comp_channel;

        }
    分配内存区域(MR,Memory Region),该区域将被硬件访问。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
        if (implicit_odp) {

            ctx->mr = ibv_reg_mr(ctx->pd, NULL, SIZE_MAX, access_flags);

        } else {

            ctx->mr = use_dm ? ibv_reg_dm_mr(ctx->pd, ctx->dm, 0,

                             size, access_flags) :

                ibv_reg_mr(ctx->pd, ctx->buf, size, access_flags);

        }



        if (!ctx->mr) {

            fprintf(stderr, "Couldn't register MR\n");

            goto clean_dm;

        }
    创建CQ
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
        if (use_ts) {

            struct ibv_cq_init_attr_ex attr_ex = {

                .cqe = rx_depth + 1,

                .cq_context = NULL,

                .channel = ctx->channel,

                .comp_vector = 0,

                .wc_flags = IBV_WC_EX_WITH_COMPLETION_TIMESTAMP

            };



            ctx->cq_s.cq_ex = ibv_create_cq_ex(ctx->context, &attr_ex);

        } else {

            ctx->cq_s.cq = ibv_create_cq(ctx->context, rx_depth + 1, NULL,

                             ctx->channel, 0);

        }



        if (!pp_cq(ctx)) {

            fprintf(stderr, "Couldn't create CQ\n");

            goto clean_mr;

        }
    创建QP
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
      ctx->qp = ibv_create_qp_ex(ctx->context, &init_attr_ex);

            } else {

                ctx->qp = ibv_create_qp(ctx->pd, &init_attr);

            }



            if (!ctx->qp)  {

                fprintf(stderr, "Couldn't create QP\n");

                goto clean_cq;

            }
    总结pp_init_ctx
    就是对连接所需的配置,资源进行一个初始化。
    连接
    这部分参考了网上的一些资料,主要是对在远程和本地,对以下三个参数进行配置。
  • QPN : QP number,用于标记QB
  • LID : Local ID,在Link Layer中被使用,类似于本地的局域网IP地址。
  • PSN : Packet Sequence Number,包序列号。用于保证可靠连接。
    在main开头,定义了两个结构体
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    	// 
    struct pingpong_dest {

        int lid;

        int qpn;

        int psn;

        union ibv_gid gid;

    };

    // local
        struct pingpong_dest     my_dest;
    // remote
        struct pingpong_dest    *rem_dest;
    具体细节还未整理清楚,但是总体上是完成了下图所示内容。
    且根据函数中细节可以认为,这段配置是通过TCP/IP完成的。
    建立连接
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    //...
    // 将my_dest传递给remote
    // 似乎是采用了TCP先建立连接。
        if (servername)

            rem_dest = pp_client_exch_dest(servername, port, &my_dest);

        else

            rem_dest = pp_server_exch_dest(ctx, ib_port, mtu, port, sl,

                                    &my_dest, gidx);
    // ...
    // 根据前文的rem_dest配置本地信息
        if (servername)

            if (pp_connect_ctx(ctx, ib_port, my_dest.psn, mtu, sl, rem_dest,

                        gidx))

                return 1;
    发送数据
    发送数据主要是通过对QP的访问完成的。
    1
    2
    3
    4
    5
    6
    7
            if (pp_post_send(ctx)) {

                fprintf(stderr, "Couldn't post send\n");

                return 1;

            }
    而pp_post_send最后是通过ibv_post_send函数完成,ibv_post_send在libibverbs/verbs.h中被定义。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    static inline int ibv_post_send(struct ibv_qp *qp, struct ibv_send_wr *wr,

                    struct ibv_send_wr **bad_wr)

    {

        return qp->context->ops.post_send(qp, wr, bad_wr);

    }
    而这个ops.post_send可能与RDMA的硬件实现有关。

    后续工作

  • 本周就是对Linux RDMA core项目做了一定的认识。通过对案例rc_pingpong的分析,对Libibverbs API的使用有了一定的了解。
  • 在阅读了深入浅出RDMA博客以后,了解到InfiniBand只是RDMA的一种硬件实现,而实现RDMA还有iWARP和RoCE两种方式。如下所示。
    RDMA硬件实现