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. 帆软报表(finereport) 组合地图 保持系列名和值居中

    自定义JavaScript代码,使用HTML解析 function(){ var name = this.name; var total = '<div style="width:10 ...

  2. Java中this和super的使用

    区别 区别点 this super 访问属性 访问本类中的属性,如果本类没有,则从父类继续查找 访问父类中的属性 调用方法 访问本类中的方法,如果本类没有,则从父类继续查找 直接访问父类的方法 调用构 ...

  3. 我们一起来学grep

    文章目录 grep 介绍 grep 命令格式 grep 命令选项 grep 实例 查找指定进程 查找指定进程个数 从文件中读取关键词进行搜索 从多个文件中查找关键字 输出以u开头的行 输出非u开头的行 ...

  4. Python+selenium自动循环送贺卡

    Python源代码如下: # coding=utf-8 from selenium import webdriver from time import sleep from random import ...

  5. volatile关键字的相关学习记录

    1:volatile是什么? Volatile是java虚拟机提供的一种轻量级的同步机制,具有 三大特性,分别是:保证可见性.不保证原子性.禁止指令重排 可见性: 概念:每一个线程都有自己的工作内存, ...

  6. react 也就这么回事 04 —— 元素渲染

    为了便于后续理解,我们再来回顾和总结前面几个章节的内容 1 元素及其创建 元素是构成 React 应用的最小砖块. 元素描述了你在屏幕上想看到的内容. const element = <h1&g ...

  7. 使用ensp模拟器中的防火墙(USG6000V)配置NAT(网页版)

    使用ensp模拟器中的防火墙(USG6000V)配置NAT(网页版)一.NAT介绍NAT(Network Address Translation,网络地址转换):简单来说就是将内部私有地址转换成公网地 ...

  8. 自助式bi工具为什么这么受欢迎?

    ​目前比较流行的一种BI形式,当属于自助式BI分析,也就是自助分析平台,即在这个倡导凡事自助的社会中,BI也要以这种形式来呈现.自助式的BI分析相比较于传统的形式,是有很多优点的,我为大家整理了一版. ...

  9. Spark on Yarn出现hadoop.compression.lzo.LzoCodec not found问题发现及解决

    问题描述: spark.SparkContext: Created broadcast 0 from textFile at WordCount.scala:37 Exception in threa ...

  10. 修改Ehcache缓存中取到的值,缓存中的值也被修改了

    问题现象 我们从Ehcache中取出缓存的对象,之后将对象中的属性进行了修改使用.等再次从缓存中拿到对象后,发现对象的值变成了上一次调用修改后的对象了. 原因 Ehcache中缓存的是原对象的引用,所 ...