OFA定义了一组标准的Verbs,并在用户态提供了一个标准库libibverbs。例如将一个工作请求(WR)放置到发送队列的Verb API是ibv_post_send(), 但是在Linux内核,对应的API则是ib_post_send()。本文将使用Linux内核提供的mlx5卡(Mellanox公司生产的一种HCA卡)的驱动(mlx5_ib.ko)分析内核Verb API ib_post_send()的实现原理。分析用到的源代码包有:

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

/* libibverbs-1.2.1/include/infiniband/verbs.h#1866 */
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 }

而在Linux内核态,ib_post_send()的源代码片段如下:

/* linux-4.11.3/include/rdma/ib_verbs.h#2859 */
2859 static inline int ib_post_send(struct ib_qp *qp,
2860 struct ib_send_wr *send_wr,
2861 struct ib_send_wr **bad_send_wr)
2862 {
2863 return qp->device->post_send(qp, send_wr, bad_send_wr);
2864 }

由此可见,无论是用户态还是内核态,都离不开回调函数(callback)post_send()的实现。 本文将以mlx5驱动为例进行剖析。要搞清楚ib_post_send()是如何将工作请求send_wr发送到mlx5硬件上去的,我们需要搞清楚下面3个问题。

  • 问题一 : 回调函数post_send()跟struct ib_qp的关系
  • 问题二 : 回调函数post_send()在mlx5驱动中的初始化
  • 问题三 : 回调函数post_send()在mlx5驱动中的实现

问题一 : 回调函数post_send()与struct ib_qp的关系

1.1 struct ib_qp

/* linux-4.11.3/include/rdma/ib_verbs.h#1576 */
1576 struct ib_qp {
1577 struct ib_device *device;
....
1601 };

上面的结构体解释了ib_post_send()函数实现中的qp->device。

1.2 struct ib_device

/* linux-4.11.3/include/rdma/ib_verbs.h#1865 */
1865 struct ib_device {
....
2012 int (*post_send)(struct ib_qp *qp,
2013 struct ib_send_wr *send_wr,
2014 struct ib_send_wr **bad_send_wr);
....
2156 };

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

问题二  : 回调函数post_send()在mlx5驱动中的初始化

2.1 module_init() 调用 mlx5_ib_init()

/* linux-4.11.3/drivers/infiniband/hw/mlx5/main.c#3649 */
3649 module_init(mlx5_ib_init);

内核模块的加载,很好理解,无需多说。

2.2 mlx5_ib_init() 调用 mlx5_register_interface(&mlx5_ib_interface)

/* linux-4.11.3/drivers/infiniband/hw/mlx5/main.c#3633 */
3633 static int __init mlx5_ib_init(void)
3634 {
....
3639 err = mlx5_register_interface(&mlx5_ib_interface);
....
3642 }

注意类型为struct mlx5_interface的全局变量mlx5_ib_interface有一个函数指针add()。

/* linux-4.11.3/drivers/infiniband/hw/mlx5/main.c#3623 */
3623 static struct mlx5_interface mlx5_ib_interface = {
3624 .add = mlx5_ib_add,
....
3630 .protocol = MLX5_INTERFACE_PROTOCOL_IB,
3631 };

在L3624, mlx5_ib_interface的成员add被初始化为函数mlx5_ib_add()。 而struct mlx5_interface的定义如下:

/* linux-4.11.3/include/linux/mlx5/driver.h#1076 */
1076 struct mlx5_interface {
1077 void * (*add)(struct mlx5_core_dev *dev);
1078 void (*remove)(struct mlx5_core_dev *dev, void *context);
....
1088 struct list_head list;
1089 };

2.3 mlx5_register_interface() 调用 mlx5_add_device()

/* linux-4.11.3/drivers/net/ethernet/mellanox/mlx5/core/dev.c#235 */
235 int mlx5_register_interface(struct mlx5_interface *intf)
236 {
...
244 list_for_each_entry(priv, &mlx5_dev_list, dev_list)
245 mlx5_add_device(intf, priv);
...
249 }

在L244,255两行,我们可以看出,mlx5_register_interface()会对每一个mlx5设备都调用mlx5_add_device()。

2.4 mlx5_add_device() 调用 intf->add(dev) (也就是 mlx5_ib_add())

/* linux-4.11.3/drivers/net/ethernet/mellanox/mlx5/core/dev.c#53 */
53 void mlx5_add_device(struct mlx5_interface *intf, struct mlx5_priv *priv)
54 {
55 struct mlx5_device_context *dev_ctx;
..
65 dev_ctx->intf = intf;
66 dev_ctx->context = intf->add(dev);
..
88 }

在L66行,mlx5设备的context被赋值,在调用intf->add(dev)后,也就是调用mlx5_ib_add()后。dev_ctx->context的值为指向一个struct mlx5_ib_dev的指针。 而局部变量dev_ctx的数据类型是struct mlx5_device_context。

/* linux-4.11.3/drivers/net/ethernet/mellanox/mlx5/core/dev.c#41 */
41 struct mlx5_device_context {
42 struct list_head list;
43 struct mlx5_interface *intf;
44 void *context;
..
46 };

与此同时, intf->add(dev)的返回值为void *。然而, mlx5_ib_add()在调用成功后,对应的返回值类型为struct mlx5_ib_dev *。 于是自动做了强制转换,本质上void * 跟struct mlx5_ib_dev *没有区别,都是内存地址。struct mlx5_ib_dev的定义如下:

/* linux-4.11.3/drivers/infiniband/hw/mlx5/mlx5_ib.h#619 */
619 struct mlx5_ib_dev {
620 struct ib_device ib_dev;
...
655 };

而L620的成员变量ib_dev的数据类型struct ib_device定义如下:

/* linux-4.11.3/include/rdma/ib_verbs.h#1865 */
1865 struct ib_device {
....
2012 int (*post_send)(struct ib_qp *qp,
2013 struct ib_send_wr *send_wr,
2014 struct ib_send_wr **bad_send_wr);
....
2156 };

在L2012-2014, 定义了一个成员变量post_send。 而post_send的初始化就是在mlx5_ib_add()函数中实现的,继续往下看。

2.5 mlx5_ib_add()设置回调函数指针post_send

/* linux-4.11.3/drivers/infiniband/hw/mlx5/main.c#3322 */
3322 static void *mlx5_ib_add(struct mlx5_core_dev *mdev)
3323 {
3324 struct mlx5_ib_dev *dev;
....
3336 dev = (struct mlx5_ib_dev *)ib_alloc_device(sizeof(*dev));
....
3340 dev->mdev = mdev;
....
3360 strlcpy(dev->ib_dev.name, name, IB_DEVICE_NAME_MAX);
3361 dev->ib_dev.owner = THIS_MODULE;
3362 dev->ib_dev.node_type = RDMA_NODE_IB_CA;
....
3432 dev->ib_dev.post_send = mlx5_ib_post_send;
3433 dev->ib_dev.post_recv = mlx5_ib_post_recv;
....
3560 return dev;
3561
3562 err_umrc:
3563 destroy_umrc_res(dev);
....
3599 return NULL;
3600 }

在L3336,分配了一个类型为struct mlx5_ib_dev的ib设备。该设备dev包括一个类型为struct ib_device的结构体ib_dev。ib_dev包含一个成员变量post_send。

在L3422,将dev->ib_dev.post_send设置为mlx5_ib_post_send。 一旦对dev完成初始化,那么对mlx5卡的消费者来说,调用ib_post_send()最终必然落到mlx5_ib_post_send()上,因为qp中包含了对应的设备

问题三 :  回调函数post_send()在mlx5驱动中的实现

3.1 mlx5_ib_post_send()驱动RDMA-Aware硬件(也就是mlx5卡)

/* linux-4.11.3/drivers/infiniband/hw/mlx5/qp.c#3805 */
3805 int mlx5_ib_post_send(struct ib_qp *ibqp, struct ib_send_wr *wr,
3806 struct ib_send_wr **bad_wr)
3807 {
....
3845 for (nreq = 0; wr; nreq++, wr = wr->next) {
....
3854 num_sge = wr->num_sge;
....
4124 }

函数mlx5_ib_post_send()的实现很长,当看到wr->num_sge的值被取出来的时候,我们就能很快发现这就是在跟mlx5卡硬件打交道啊。到了硬件驱动这一层,就不用再往下看了。换句话说,从ib_post_send()函数出发,在一个工作请求WR中,存放在SGL上的消息被发送到mlx5卡上去,必然最后交给mlx5卡的内核驱动mlx5_ib_post_send()去完成。

小结:

  • 01 - 当内核驱动模块mlx5_ib.ko被加载的时候,每一个mlx5设备dev->ib_dev.post_send就被初始化为mlx5_ib_post_send();
  • 02 - 当mlx5设备的内核消费者尝试从mlx5硬件那里获取一个QP的时候,对应的qp->device->post_send就已经确定,那就是mlx5_ib_post_send();
  • 03 - 当mlx5设备的内核消费者使用ib_post_send()函数调用的时候,工作请求send_wr最终被mlx5设备驱动函数mlx5_ib_post_send()所处理。
Initiative is doing the right thing without being told. | 主动性就是在没有人告诉你时做正确的事情。

[SPDK/NVMe存储技术分析]011 - 内核态ib_post_send()源码剖析的更多相关文章

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

    OFA定义了一组标准的Verbs,并提供了一个标准库libibvers.在用户态实现NVMe over RDMA的Host(i.e. Initiator)和Target, 少不了要跟OFA定义的Ver ...

  2. [SPDK/NVMe存储技术分析]015 - 理解内存注册(Memory Registration)

    使用RDMA, 必然关系到内存区域(Memory Region)的注册问题.在本文中,我们将以mlx5 HCA卡为例回答如下几个问题: 为什么需要注册内存区域? 注册内存区域有嘛好处? 注册内存区域的 ...

  3. [SPDK/NVMe存储技术分析]008 - RDMA概述

    毫无疑问地,用来取代iSCSI/iSER(iSCSI Extensions for RDMA)技术的NVMe over Fabrics着实让RDMA又火了一把.在介绍NVMe over Fabrics ...

  4. [SPDK/NVMe存储技术分析]003 - NVMeDirect论文

    说明: 之所以要翻译这篇论文,是因为参考此论文可以很好地理解SPDK/NVMe的设计思想. NVMeDirect: A User-space I/O Framework for Application ...

  5. [SPDK/NVMe存储技术分析]002 - SPDK官方介绍

    Introduction to the Storage Performance Development Kit (SPDK) | SPDK概述 By Jonathan S. (Intel), Upda ...

  6. [SPDK/NVMe存储技术分析]005 - DPDK概述

    注: 之所以要中英文对照翻译下面的文章,是因为SPDK严重依赖于DPDK的实现. Introduction to DPDK: Architecture and PrinciplesDPDK概论:体系结 ...

  7. [SPDK/NVMe存储技术分析]004 - SSD设备的发现

    源代码及NVMe协议版本 SPDK : spdk-17.07.1 DPDK : dpdk-17.08 NVMe Spec: 1.2.1 基本分析方法 01 - 到官网http://www.spdk.i ...

  8. [SPDK/NVMe存储技术分析]001 - SPDK/NVMe概述

    1. NVMe概述 NVMe是一个针对基于PCIe的固态硬盘的高性能的.可扩展的主机控制器接口. NVMe的显著特征是提供多个队列来处理I/O命令.单个NVMe设备支持多达64K个I/O 队列,每个I ...

  9. [SPDK/NVMe存储技术分析]006 - 内存屏障(MB)

    在多核(SMP)多线程的情况下,如果不知道CPU乱序执行的话,将会是一场噩梦,因为无论怎么进行代码Review也不可能发现跟内存屏障(MB)相关的Bug.内存屏障分为两类: 跟编译有关的内存屏障: 告 ...

随机推荐

  1. 30天自制操作系统day2汇编语言

    <30天自制操作系统>一书中第1天和第二天中有关汇编语言的程序的理解 ; hello-os ; TAB=4 ORG 0x7c00 ; 指明程序的装载地址 ; 以下的记述用于标准FAT12格 ...

  2. 虫师Selenium2+Python_5、自动化测试模型

    P138--模块化驱动测试实例 P142--参数化搜索关键字 from selenium import webdriver search_text = ['python','中文','text'] # ...

  3. 基于 Kintex-7 XC7K325T的半高PCIe x4双路万兆光纤收发卡

    一.板卡概述 板卡采用Xilinx公司的XC7K325T-2FFG900I芯片作为主处理器,可应用于万兆网络.高速数据采集.存储:光纤隔离网闸等领域. 二.功能和技术指标: 板卡功能 参数内容 主处理 ...

  4. Vue中组件通信的几种方法(Vue3的7种和Vue2的12种组件通信)

    Vue3组件通信方式: props $emit expose / ref $attrs v-model provide / inject Vuex 使用方法: props 用 props 传数据给子组 ...

  5. 【高频Java面试题】简单说说JVM堆的内存结构和GC回收流程

    目录 前言 JVM堆内存结构简述 JVM堆内存结构图 堆初体验 结构详情 新生代 老年代 永久代/元空间 GC回收流程 GC回收流程图 GC回收详细流程 查看JDK自带可视化堆空间图 总结 前言 我们 ...

  6. Spring cloud是什么? 核心总结

    Spring Cloud 是一套完整的微服务解决方案,基于 Spring Boot 框架,准确的说,它不是一个框架,而是一个大的容器,它将市面上较好的微服务框架集成进来,从而简化了开发者的代码量. S ...

  7. 分享几个你可能不知道的交互式Git 命令

    摘要:本文中讲述的几个交互式 Git 命令可以帮助你将文件的特定部分组合成提交. 本文分享自华为云社区<Git你有可能不知道交互式暂存>,作者:龙哥手记. 本节中的几个交互式 Git 命令 ...

  8. 📚 选择排序和插入排序区别-DS笔记

    选择排序法 A[i...n)未排序,A[0...i)已排序 A[i...n]中最小值要放到A[i]的位置 复杂度 \(O(n^2)\) 第一层循环n次 第二层循环:i=0,n次:i=1,n-1次... ...

  9. vs2022 如何让.net库文件参与程序调试【可以.net库文件的源代码中设置断点,单步跟踪】

    由于.net core 是开源的.所以可以让.net库文件参与程序调试.具体vs2022配置如下 1.设置VS2022 加载程序数据文件(.pdb俗称符号文件) 1)选择工具>选项>调试& ...

  10. 【C# 线程】Thread类 以及使用案例

    System.Threading.Thread类 涉及到的类和枚举 Volatile 类Interlocked 类SpinLock 类SpinWait类Barrier 类ThreadLocal< ...