• [SPDK/NVMe存储技术分析]012


    OFA定义了一组标准的Verbs,并提供了一个标准库libibvers。在用户态实现NVMe over RDMA的Host(i.e. Initiator)和Target, 少不了要跟OFA定义的Verbs打交道。但是,仅仅有libibverbs里的API是不够的,还需要对应的RDMA硬件的用户态驱动支持。在前文中,我们分析了内核态ib_post_send()的实现,理解了内核空间的回调函数post_send()是如何跟mlx5卡的设备驱动函数mlx5_ib_post_send()关联在一起的。本着“知其然更知其所以然”的精神,本文将继续以mlx5卡为例,分析用户态Verb API ibv_post_send()的实现原理。 分析用到的源码包有:

    在用户态的libibverbs中, ibv_post_send()的源代码片段如下:

    /* libibverbs-1.2.1/include/infiniband/verbs.h#1860 */
    
    1860 /**
    1861  * ibv_post_send - Post a list of work requests to a send queue.
    ....
    1865  */
    1866 static inline int ibv_post_send(struct ibv_qp *qp, struct ibv_send_wr *wr,
    1867                                 struct ibv_send_wr **bad_wr)
    1868 {
    1869    return qp->context->ops.post_send(qp, wr, bad_wr);
    1870 }

    从L1869我们可以看出,post_send()是一个回调(callback)函数,跟RDMA硬件驱动密切相关。

    而在mlx5卡的用户态驱动libmlx5的REDME中,我们可以看到libmlx5是一个为libibverbs准备的plug-in模块,允许应用程序在用户空间直接访问Mellanox的硬件mlx5 HCA卡。 当应用程序开发人员使用libibverbs的时候,用户态驱动libmlx5被自动加载。但是,必须首先加载mlx5卡的内核驱动(mlx5_ib.ko)以发现和使用HCA设备。那么,为什么必须率先加载mlx5_ib.ko模块?这是一个值得深究的问题。 (难道libmlx5用户态驱动没有发现HCA卡的能力?)

    $ cat -n libmlx5-1.2.1/README 
         1	Introduction
         2	============
         3	
         4	libmlx5 is a userspace driver for Mellanox ConnectX InfiniBand HCAs.
         5	It is a plug-in module for libibverbs that allows programs to use
         6	Mellanox hardware directly from userspace.  See the libibverbs package
         7	for more information.
         8	
         9	Using libmlx5
        10	==============
        11	
        12	libmlx5 will be loaded and used automatically by programs linked with
        13	libibverbs.  The mlx5_ib kernel module must be loaded for HCA devices
        14	to be detected and used.

    要搞清楚ibv_post_send()是如何将工作请求send_wr发送到mlx5硬件上去的,我们需要搞清楚下面4个问题。

    •  问题1:回调函数post_send()与struct ibv_qp的关系
    •  问题2:回调函数post_send()的初始化
    •  问题3:回调函数post_send()在mlx5用户态驱动中的实现
    •  问题4:为什么使用mlx5卡的用户态驱动还需要内核态驱动mlx5_ib.ko的支持

    问题1:回调函数post_send()与struct ibv_qp的关系

    1.1 struct ibv_qp

    /* libibverbs-1.2.1/include/infiniband/verbs.h#837 */
    837 struct ibv_qp {
    838     struct ibv_context     *context;
    ...
    852 };

    上面的结构体解释了ibv_post_send()函数实现中的qp->context

    1.2 struct ibv_context

    /* libibverbs-1.2.1/include/infiniband/verbs.h#1185 */
    1185 struct ibv_context {
    1186    struct ibv_device      *device;
    1187    struct ibv_context_ops  ops;
    ....
    1193 };

    上面的结构体解释了ibv_post_send()函数实现中的qp->context->ops

    1.3 struct ibv_context_ops

    /* libibverbs-1.2.1/include/infiniband/verbs.h#1127 */
    1127 struct ibv_context_ops {
    ....
    1172    int                     (*post_send)(struct ibv_qp *qp, struct ibv_send_wr *wr,
    1173                                         struct ibv_send_wr **bad_wr);
    ....
    1183 };

    上面的结构体解释了ibv_post_send()函数实现中的qp->context->ops.post_send(...)。 那么,回调函数指针post_send()是什么时候被赋值的(也就是初始化)?这是我们接下来需要探索的问题。

    问题2:回调函数post_send()的初始化

    2.1 注册mlx5用户态驱动的入口函数mlx5_register_driver() 调用verbs_register_driver()

    /* libmlx5-1.2.1/src/mlx5.c#845 */
    845 static __attribute__((constructor)) void mlx5_register_driver(void)
    846 {
    847     verbs_register_driver("mlx5", mlx5_driver_init);
    848 }

    注意: 函数mlx5_register_driver()在main()函数之前被调用,不是很容易理解。那么,有必要先写个demo解释一下__attribute__((constructor))

    • foo.c
     1 #include <stdio.h>
     2 
     3 int main(int argc, char *argv[]) 
     4 {
     5     printf("Enter into %s()
    ", __func__);
     6     return 0;
     7 }
     8 
     9 static __attribute__((constructor)) void mlx5_register_driver(void)
    10 {
    11     printf("Enter into %s()
    ", __func__);
    12 }
    • 编译并运行
    $ gcc -g -Wall -o foo foo.c
    $ ./foo
    Enter into mlx5_register_driver()
    Enter into main()
    $
    $ gdb foo
    GNU gdb (Ubuntu 7.7.1-0ubuntu5~14.04.3) 7.7.1
    ...<snip>...
    (gdb) b _start
    Breakpoint 1 at 0x8048320
    (gdb) b main
    Breakpoint 2 at 0x8048426: file foo.c, line 5.
    (gdb) b mlx5_register_driver
    Breakpoint 3 at 0x8048447: file foo.c, line 11.
    (gdb) info b
    Num     Type           Disp Enb Address    What
    1       breakpoint     keep y   0x08048320 <_start>
    2       breakpoint     keep y   0x08048426 in main at foo.c:5
    3       breakpoint     keep y   0x08048447 in mlx5_register_driver at foo.c:11
    (gdb) r
    Starting program: /tmp/foo
    
    Breakpoint 1, 0x08048320 in _start ()
    (gdb) #
    (gdb) c
    Continuing.
    
    Breakpoint 3, mlx5_register_driver () at foo.c:11
    11              printf("Enter into %s()
    ", __func__);
    (gdb) #
    (gdb) c
    Continuing.
    Enter into mlx5_register_driver()
    
    Breakpoint 2, main (argc=1, argv=0xbffff084) at foo.c:5
    5               printf("Enter into %s()
    ", __func__);
    (gdb) #
    (gdb) c
    Continuing.
    Enter into main()
    [Inferior 1 (process 14542) exited normally]
    (gdb) q

    从上面的输出可以看出,被__attribute__((constructor))限定的函数mlx5_register_driver()在主函数main()之前被调用。 更多解释请阅读__attribute__ ((constructor)) 用法解析

    让我们暂时放下verbs_register_driver()不管,径直分析post_send()是如何被初始化的。

    2.2 mlx5_driver_init()设置mlx5设备dev->verbs_dev.init_context为mlx5_init_context()

    /* libmlx5-1.2.1/src/mlx5.c#791 */
    791 static struct verbs_device *mlx5_driver_init(const char *uverbs_sys_path,
    792                             int abi_version)
    793 {
    794    char            value[8];
    795    struct mlx5_device     *dev;
    796    unsigned        vendor, device;
    797    int            i;
    798
    799    if (ibv_read_sysfs_file(uverbs_sys_path, "device/vendor",
    800                value, sizeof value) < 0)
    801        return NULL;
    802    sscanf(value, "%i", &vendor);
    803
    804    if (ibv_read_sysfs_file(uverbs_sys_path, "device/device",
    805                value, sizeof value) < 0)
    806        return NULL;
    807    sscanf(value, "%i", &device);
    808
    809    for (i = 0; i < sizeof hca_table / sizeof hca_table[0]; ++i)
    810        if (vendor == hca_table[i].vendor &&
    811            device == hca_table[i].device)
    812            goto found;
    813
    814    return NULL;
    815
    816 found:
    817    if (abi_version < MLX5_UVERBS_MIN_ABI_VERSION ||
    818        abi_version > MLX5_UVERBS_MAX_ABI_VERSION) {
    ...
    824        return NULL;
    825    }
    826
    827    dev = malloc(sizeof *dev);
    ...
    834    dev->page_size   = sysconf(_SC_PAGESIZE);
    835    dev->driver_abi_ver = abi_version;
    836    dev->verbs_dev.sz = sizeof(*dev);
    837    dev->verbs_dev.size_of_context = sizeof(struct mlx5_context) -
    838        sizeof(struct ibv_context);
    839    dev->verbs_dev.init_context = mlx5_init_context;
    840    dev->verbs_dev.uninit_context = mlx5_cleanup_context;
    841
    842    return &dev->verbs_dev;
    843 }

    在L839中, dev->verbs_dev.init_context被初始化为函数mlx5_init_context。

    839    dev->verbs_dev.init_context = mlx5_init_context;

    2.3 mlx5_init_context()设置context->ibv_ctx.ops为全局结构体变量mlx5_ctx_ops

    /* libmlx5-1.2.1/src/mlx5.c#588 */
    588 static int mlx5_init_context(struct verbs_device *vdev,
    589                          struct ibv_context *ctx, int cmd_fd)
    590 {
    591     struct mlx5_context            *context;
    ...
    611     context = to_mctx(ctx);
    ...
    734     context->ibv_ctx.ops = mlx5_ctx_ops;
    ...
    771 }

    在L734中, context->ibv_ctx.ops被初始化为全局结构体变量mlx5_ctx_ops,而mlx5_ctx_ops的类型为struct ibv_context_ops。

    2.4 在mlx5_ctx_ops中初始化回调函数post_send()

    /* libmlx5-1.2.1/src/mlx5.c#90 */
     90 static struct ibv_context_ops mlx5_ctx_ops = {
    ...
    116     .post_send     = mlx5_post_send,
    ...
    122 };

    在L116中,回调函数post_send()被静态地初始化为mlx5_post_send。也就是说,对于mlx5卡用户态驱动的消费者来说,调用ibv_post_send(),最终会落到mlx5_post_send()函数调用上

    问题3:回调函数post_send()在mlx5用户态驱动中的实现

    3.1 mlx5_post_send()调用_mlx5_post_send()

    /* libmlx5-1.2.1/src/qp.c#897 */
    897 int mlx5_post_send(struct ibv_qp *ibqp, struct ibv_send_wr *wr,
    898                    struct ibv_send_wr **bad_wr)
    899 {
    ...
    921     return _mlx5_post_send(ibqp, wr, bad_wr);
    922 }

    在L921调用_mlx5_post_send()。

    3.2 _mlx5_post_send()驱动RDMA-Aware硬件(也就是mlx5卡)

    /* libmlx5-1.2.1/src/qp.c#559 */
    559 static inline int _mlx5_post_send(struct ibv_qp *ibqp, struct ibv_send_wr *wr,
    560                                   struct ibv_send_wr **bad_wr)
    561 {
    562     struct mlx5_context *ctx;
    563     struct mlx5_qp *qp = to_mqp(ibqp);
    ...
    589     for (nreq = 0; wr; ++nreq, wr = wr->next) {
    ...
    849     }
    ...
    895 }

    _mlx5_post_send()的代码很长,从上面的代码片段中我们不难发现,用户态驱动函数_mlx5_post_send()就是直接跟mlx5卡(硬件)打交道。 换言之,对mlx5卡的消费者来说,当用户空间的应用程序调用libibverbs中的API ibv_post_send()的时候,本质上就是通过_mlx5_post_send()去直接访问mlx5硬件。

    问题4:为什么使用mlx5卡的用户态驱动还需要内核态驱动mlx5_ib.ko的支持

    我们在一开始就提出了一个疑问:“难道libmlx5用户态驱动没有发现HCA卡的能力?” 这个问题可以问得更具体一些,“难道libmlx5用户态驱动没有直接通过PCIe发现HCA卡的能力?” 在回答这个问题之前,让我们回到2.1看看verbs_register_driver()的实现。libmlx5用户态驱动注册采用的代码如下:

    /* libmlx5-1.2.1/src/mlx5.c#845 */
    845 static __attribute__((constructor)) void mlx5_register_driver(void)
    846 {
    847     verbs_register_driver("mlx5", mlx5_driver_init);
    848 }

    我们在前面沿着mlx5_driver_init()的逻辑分析了post_send()在用户态驱动libmlx5中的具体实现。现在是时候一步一步分析用户态驱动libmlx5是如何注册到libibverbs中去的了。

    4.1 verbs_register_driver()调用register_driver()

    /* libibverbs-1.2.1/src/init.c#188 */
    188 void verbs_register_driver(const char *name, verbs_driver_init_func init_func)
    189 {
    190     register_driver(name, NULL, init_func);
    191 }

    而verbs_dirver_init_func的定义是这样的:

    /* libibverbs-1.2.1/include/infiniband/driver.h#96 */
    96 typedef struct verbs_device *(*verbs_driver_init_func)(const char *uverbs_sys_path,
    97                                                        int abi_version);

    mlx5_driver_init()的函数原型正好是:

    791 static struct verbs_device *mlx5_driver_init(const char *uverbs_sys_path,
    792                                              int abi_version)

    那么,接下来我们看看mlx5_driver_init()被放置到什么地方去了。

    4.2 register_driver()把mlx5_driver_init()放置到一个链表结点上

    /* libibverbs-1.2.1/src/init.c#157 */
    
    157 static void register_driver(const char *name, ibv_driver_init_func init_func,
    158                         verbs_driver_init_func verbs_init_func)
    159 {
    160     struct ibv_driver *driver;
    161
    162     driver = malloc(sizeof *driver);
    ...
    168     driver->name            = name;
    169     driver->init_func       = init_func;
    170     driver->verbs_init_func = verbs_init_func;
    171     driver->next            = NULL;
    172
    173     if (tail_driver)
    174             tail_driver->next = driver;
    175     else
    176             head_driver = driver;
    177     tail_driver = driver;
    178 }

    L160: 定义一个类型为struct ibv_driver的结构体变量driver,该变量将作为一个链表结点。struct ibv_driver的定义如下:

    /* libibverbs-1.2.1/src/init.c#70 */
    70 struct ibv_driver {
    71      const char             *name;
    72      ibv_driver_init_func    init_func;
    73      verbs_driver_init_func  verbs_init_func;
    74      struct ibv_driver      *next;
    75 };

    L162: 为结构体变量driver申请内存空间
    L168: 设置driver->name, e.g. "mlx5"
    L169: 设置driver->init_func, e.g. NULL
    L170: 设置driver->verbs_init_func, e.g. mlx5_driver_init
    L171: 设置driver->next 为 NULL
    L173-177: 维护全局链表head_driver, tail_driver可以理解为指向该链表的尾结点的指针,那么在L162申请的结点driver就是通过尾插法加入到链表head_driver中去的。

    /* libibverbs-1.2.1/src/init.c#79 */
    79 static struct ibv_driver *head_driver, *tail_driver;

    接下来,我们需要去看看究竟是谁在消费全局链表head_driver。

    4.3 消费全局链表head_driver的是try_drivers()函数

    /* libibverbs-1.2.1/src/init.c#408 */
    408 static struct ibv_device *try_drivers(struct ibv_sysfs_dev *sysfs_dev)
    409 {
    410     struct ibv_driver *driver;
    411     struct ibv_device *dev;
    412
    413     for (driver = head_driver; driver; driver = driver->next) {
    414             dev = try_driver(driver, sysfs_dev);
    415             if (dev)
    416                     return dev;
    417     }
    418
    419     return NULL;
    420 }

    在L413-417中,遍历全局链表head_driver, 针对单个结点driver在L414调用try_driver(driver, sysfs_dev)函数。如果匹配成功,则理解返回对应的ibv设备(struct ibv_device)。 接下来,我们从try_drivers()出发,逆向分析一下函数调用栈。

    4.4 调用try_drivers()的是ibvers_init()

    /* libibverbs-1.2.1/src/init.c#480 */
    480 HIDDEN int ibverbs_init(struct ibv_device ***list)
    481 {
    ...
    510     ret = find_sysfs_devs();
    ...
    514     for (sysfs_dev = sysfs_dev_list; sysfs_dev; sysfs_dev = sysfs_dev->next) {
    515             device = try_drivers(sysfs_dev);
    ...
    521     }
    ...
    575 }
    
    /* libibverbs-1.2.1/src/ibverbs.h#55 */
    55 #define HIDDEN               __attribute__((visibility ("hidden")))

    sysfs_dev是链表sysfs_dev_list上的一个结点。而sysfs_dev_list则是由L510调用find_sysfs_devs()创建的。 关于find_sysfs_devs()的实现,暂且不表。

    4.5 调用ibverbs_init()的是count_devices()

    /* libibverbs-1.2.1/src/device.c#56 */
    53 static int num_devices;
    54 static struct ibv_device **device_list;
    55
    56 static void count_devices(void)
    57 {
    58      num_devices = ibverbs_init(&device_list);
    59 }

    4.6 设置count_devices()的是__ibv_get_device_list()

    /* libibverbs-1.2.1/src/device.c#61 */
    52 static pthread_once_t device_list_once = PTHREAD_ONCE_INIT;
    ..
    61 struct ibv_device **__ibv_get_device_list(int *num)
    62 {
    ..
    69      pthread_once(&device_list_once, count_devices);
    ..
    88 }

    在L69中,函数count_devices()被dispatch到一个线程中,当且仅当执行一次。 那么,是谁调用或设置__ibv_get_device_list()呢?

    4.7 __ibv_get_device_list的别名被设置为ibv_get_device_list

    /* libibverbs-1.2.1/src/device.c#89 */
    89 default_symver(__ibv_get_device_list, ibv_get_device_list);

    而default_symver的宏定义是

    /* libibverbs-1.2.1/src/ibverbs.h#69 */
    62 #ifdef HAVE_SYMVER_SUPPORT
    63 #  define symver(name, api, ver) 
    64    asm(".symver " #name "," #api "@" #ver)
    65 #  define default_symver(name, api) 
    66    asm(".symver " #name "," #api "@@" DEFAULT_ABI)
    67 #else
    68 #  define symver(name, api, ver)
    69 #  define default_symver(name, api) 
    70    extern __typeof(name) api __attribute__((alias(#name)))
    71 #endif /* HAVE_SYMVER_SUPPORT */

    于是,L89展开后(假设走#else分支)就是

    extern __typeof(__ibv_get_device_list) ibv_get_device_list __attribute__((alias("__ibv_get_device_list")));

    为了帮助理解 __attribute__((alias("FuncName"))), 下面给出一个demo。

    • foo.c
     1 #include <stdio.h>
     2 
     3 int __ibv_xxx()
     4 {
     5         printf("Enter into %s
    ", __func__);
     6         return 0;
     7 }
     8 
     9 extern __typeof(__ibv_xxx) ibv_xxx __attribute__((alias("__ibv_xxx")));
    10 
    11 int main(int argc, char *argv[])
    12 {
    13         return ibv_xxx();
    14 }
    • 编译并运行
    $ gcc -g -Wall -o foo foo.c
    $ ./foo
    Enter into __ibv_xxx
    $
    $ gdb foo
    ...<snip>...
    (gdb) disas /m main
    Dump of assembler code for function main:
    12      {
       0x0804843e <+0>:     push   %ebp
       0x0804843f <+1>:     mov    %esp,%ebp
       0x08048441 <+3>:     and    $0xfffffff0,%esp
    
    13              return ibv_xxx();
       0x08048444 <+6>:     call   0x804841d <__ibv_xxx>
    
    14      }
       0x08048449 <+11>:    leave
       0x0804844a <+12>:    ret
    
    End of assembler dump.
    (gdb) q

    通过反汇编,虽然在main()调用的是ibv_xxx(),但是本质上是调用__ibv_xxx()。

    4.8 用户应用程序负责调用ibv_get_device_list()

    ibv_get_device_list()是一个verbs API,调用ibv_post_send()之前必须先调用ibv_get_device_list去获取RDMA设备列表。 关于ibv_get_device_list的使用说明,请参见:

    于是,我们可以得到如下函数调用栈:

    0. ibv_get_device_list()        # start by User's Application
            |
            v
    1. cout_devices()               # @libibverbs-1.2.1/src/device.c#56
            |
            v
    2. ibverbs_init()               # @libibverbs-1.2.1/src/init.c#480
            |
            v
    3. try_drivers()                # @libibverbs-1.2.1/src/init.c#408
            |
            v
    4. try_driver()                 # @libibverbs-1.2.1/src/init.c#349

    接下来,我们将分析try_driver(), 搞清楚mlx5设备是如何被发现的。也就是说,接下来将进入最精彩的部分 -- 用户态驱动libmlx5为什么需要内核态驱动mlx5_ib.ko的支持

    4.9 find_sysfs_devs()负责发现所有RDMA设备

    /* libibverbs-1.2.1/src/init.c#81 */
    81 static int find_sysfs_devs(void)
    82 {
    83      char class_path[IBV_SYSFS_PATH_MAX];
    84      DIR *class_dir;
    85      struct dirent *dent;
    86      struct ibv_sysfs_dev *sysfs_dev = NULL;
    87      char value[8];
    88      int ret = 0;
    89
    90      snprintf(class_path, sizeof class_path, "%s/class/infiniband_verbs",
    91               ibv_get_sysfs_path());
    92
    93      class_dir = opendir(class_path);
    94      if (!class_dir)
    95              return ENOSYS;
    96
    97      while ((dent = readdir(class_dir))) {
    98              struct stat buf;
    99
    100             if (dent->d_name[0] == '.')
    101                     continue;
    102
    103             if (!sysfs_dev)
    104                     sysfs_dev = malloc(sizeof *sysfs_dev);
    105             if (!sysfs_dev) {
    106                     ret = ENOMEM;
    107                     goto out;
    108             }
    109
    110             snprintf(sysfs_dev->sysfs_path, sizeof sysfs_dev->sysfs_path,
    111                      "%s/%s", class_path, dent->d_name);
    112
    113             if (stat(sysfs_dev->sysfs_path, &buf)) {
    114                     fprintf(stderr, PFX "Warning: couldn't stat '%s'.
    ",
    115                             sysfs_dev->sysfs_path);
    116                     continue;
    117             }
    118
    119             if (!S_ISDIR(buf.st_mode))
    120                     continue;
    121
    122             snprintf(sysfs_dev->sysfs_name, sizeof sysfs_dev->sysfs_name,
    123                     "%s", dent->d_name);
    124
    125             if (ibv_read_sysfs_file(sysfs_dev->sysfs_path, "ibdev",
    126                                     sysfs_dev->ibdev_name,
    127                                     sizeof sysfs_dev->ibdev_name) < 0) {
    128                     fprintf(stderr, PFX "Warning: no ibdev class attr for '%s'.
    ",
    129                             dent->d_name);
    130                     continue;
    131             }
    132
    133             snprintf(sysfs_dev->ibdev_path, sizeof sysfs_dev->ibdev_path,
    134                      "%s/class/infiniband/%s", ibv_get_sysfs_path(),
    135                      sysfs_dev->ibdev_name);
    136
    137             sysfs_dev->next        = sysfs_dev_list;
    138             sysfs_dev->have_driver = 0;
    139             if (ibv_read_sysfs_file(sysfs_dev->sysfs_path, "abi_version",
    140                                     value, sizeof value) > 0)
    141                     sysfs_dev->abi_ver = strtol(value, NULL, 10);
    142             else
    143                     sysfs_dev->abi_ver = 0;
    144
    145             sysfs_dev_list = sysfs_dev;
    146             sysfs_dev      = NULL;
    147     }
    148
    149 out:
    150     if (sysfs_dev)
    151             free(sysfs_dev);
    152
    153     closedir(class_dir);
    154     return ret;
    155 }

    在L137,138,145,146中,函数finds_sysfs_devs()把发现的所有设备都通过头插法保存在全局链表sysfs_dev_list上。

    /* libibverbs-1.2.1/src/init.c#77 */
    77 static struct ibv_sysfs_dev *sysfs_dev_list;

    而每一个设备的数据类型为:

    /* libibverbs-1.2.1/src/init.c#55 */
    55 struct ibv_sysfs_dev {
    56      char                    sysfs_name[IBV_SYSFS_NAME_MAX];
    57      char                    ibdev_name[IBV_SYSFS_NAME_MAX];
    58      char                    sysfs_path[IBV_SYSFS_PATH_MAX];
    59      char                    ibdev_path[IBV_SYSFS_PATH_MAX];
    60      struct ibv_sysfs_dev   *next;
    61      int                     abi_ver;
    62      int                     have_driver;
    63 };
    

    在find_sysfs_devs()中, 对于mlx5设备来说(假定只有一个mlx5卡),我们不难推导出:

    • L90-91: class_path为/sys/class/infiniband_verbs
    • L110-111: sysfs_dev->sysfs_path为/sys/class/infiniband_verbs/mlx5
    • L122-123: sysfs_dev->sysfs_name为mlx5
    • L125-131: sysfs_dev->ibdev_name为mlx5_0
    • L133-135: sysfs_dev->ibdev_path为/sys/class/infiniband/mlx5_0

    无论是/sys/class/infiniband_verbs还是/sys/class/infiniband路径,都跟sysfs紧密相关。那么,谁有能力在/sys/class/infiniband下面创建设备信息?答案自然是RDMA卡的内核驱动,比如mlx5_ib.ko。因此,我们可以看到,libmlx5和libibverbs紧密配合发现mlx5设备,但是没有直接使用PCIe,而是借助于内核驱动mlx5_ib.ko在加载时创建的sysfs信息。到此为止,我们可以得到如下证据确凿的结论:

    用户态驱libmlx5没有通过PCIe发现mlx5设备的能力,因为基于sysfs信息去发现mlx5设备,所以mlx5的Linux内核驱动是必须的。而mlx5的内核驱动,自然是通过PCIe去sysfs那里注册对应的mlx5设备的。

    The good seaman is known in bad weather. | 惊涛骇浪,方显英雄本色。
  • 相关阅读:
    Java并发专题 带返回结果的批量任务执行
    angualejs
    Java并发编程:Callable、Future和FutureTask
    mybatis
    InitialContext和lookup
    git 常用使用命令
    junit spring 测试
    redis windows
    为何PS出的RSS总和大于实际物理内存
    32位机器的LowMemory
  • 原文地址:https://www.cnblogs.com/vlhn/p/7997457.html
Copyright © 2020-2023  润新知