日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

[SPDK/NVMe存储技术分析]012 - 用户态ibv_post_send()源码分析

發布時間:2025/5/22 编程问答 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 [SPDK/NVMe存储技术分析]012 - 用户态ibv_post_send()源码分析 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

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()的實現原理。 分析用到的源碼包有:

  • libibvers源代碼: libibverbs-1.2.1.tar.gz
  • mlx5用戶態驅動源代碼: libmlx5-1.2.1.tar.gz
  • Linux內核源代碼: linux-4.11.3.tar.xz

在用戶態的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 Introduction2 ============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 use6 Mellanox hardware directly from userspace. See the libibverbs package7 for more information.8 9 Using libmlx510 ==============11 12 libmlx5 will be loaded and used automatically by programs linked with13 libibverbs. The mlx5_ib kernel module must be loaded for HCA devices14 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()\n", __func__); 6 return 0; 7 } 8 9 static __attribute__((constructor)) void mlx5_register_driver(void) 10 { 11 printf("Enter into %s()\n", __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/fooBreakpoint 1, 0x08048320 in _start () (gdb) # (gdb) c Continuing.Breakpoint 3, mlx5_register_driver () at foo.c:11 11 printf("Enter into %s()\n", __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()\n", __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\n", __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 %ebp0x0804843f <+1>: mov %esp,%ebp0x08048441 <+3>: and $0xfffffff0,%esp13 return ibv_xxx();0x08048444 <+6>: call 0x804841d <__ibv_xxx>14 }0x08048449 <+11>: leave0x0804844a <+12>: retEnd 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的使用說明,請參見:

  • ibv_get_device_list() on RDMAmojo

于是,我們可以得到如下函數調用棧:

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'.\n", 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'.\n", 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. | 驚濤駭浪,方顯英雄本色。

?

轉載于:https://www.cnblogs.com/vlhn/p/7997457.html

總結

以上是生活随笔為你收集整理的[SPDK/NVMe存储技术分析]012 - 用户态ibv_post_send()源码分析的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 毛片视频免费播放 | 亚欧在线 | 日本电影一区 | 三级黄网站 | 人妖一区二区三区 | 漂亮人妻被中出中文字幕 | 蜜臀av无码精品人妻色欲 | 强行糟蹋人妻hd中文字幕 | 日韩中文第一页 | 天天视频亚洲 | 高潮流白浆在线观看 | 91在线视频精品 | 亚洲欧美偷拍视频 | 性生交大片免费看女人按摩 | 手机av免费在线 | 欧美中文网 | 国产网址| 色偷偷中文字幕 | 青草超碰 | 国模无码大尺度一区二区三区 | 99福利影院 | 欧美一级在线 | 欧美a天堂 | 天天艹日日干 | 揉我啊嗯~喷水了h视频 | 亚洲精品91在线 | 姐姐的秘密韩剧免费观看全集中文 | 国产精品夜夜 | 亚洲精品三区 | www.黄色免费 | 一级免费毛片 | 欧美性受xxxx黑人猛交88 | 欧美中出 | 久久久久久一 | 波多野结衣黄色片 | av国产一区二区 | 韩日激情视频 | 九九色综合 | 九七av| 日韩美女做爰高潮免费 | 亚洲综合婷婷 | 欧美mv日韩mv国产网站 | 中文字幕久久综合 | 欧美激情在线观看一区 | 日本不卡高字幕在线2019 | 精品无人国产偷自产在线 | 日韩精品一区二区三区在线 | 91天天| 亚洲自偷自偷偷色无码中文 | 麻豆久久久久久久 | 美女网站在线免费观看 | 欧亚成人av| 精品国产av一区二区三区 | 免费国产| 国产伦精品 | 天天在线免费视频 | 国产精品13p | 日韩精品免费观看 | 蜜臀av一区二区三区激情综合 | 青青色在线观看 | 国产三级精品三级 | 男女黄床上色视频免费的软件 | 欧美精品91 | 欧美久久久影院 | 青青青在线视频观看 | 欧美色图另类 | 国产黄色免费看 | 成人aaaaa| 国产一区二区三区视频免费在线观看 | 日日夜夜操av | 久久综合加勒比 | 中文在线a√在线 | c逼视频 | 日韩一片| 亚洲av成人无码网天堂 | 肥臀浪妇太爽了快点再快点 | www.一区| 狠狠夜| 久久伊人成人网 | 中文字幕少妇在线三级hd | 欧美黑人添添高潮a片www | 欧美男人亚洲天堂 | 蜜桃成熟时李丽珍在线观看 | 欧美成人久久久免费播放 | 亚洲一区二区日韩欧美 | 福利视频亚洲 | 裸体裸乳免费看 | 日狠狠| 午夜伦理在线观看 | 乱人伦av| 一色道久久88加勒比一 | 成年人午夜影院 | 九久久久久 | 天天插天天爱 | 亚洲欧美国产另类 | 久久精品视频网 | av免费观| 欧美日韩一区二区久久 | 成年人看的羞羞网站 |