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


    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. | 主动性就是在没有人告诉你时做正确的事情。
  • 相关阅读:
    react实现转盘动画
    a标签做锚点定位,有部分内容被置顶头部遮挡的解决方法
    react复制文案到剪切板
    MySQL Windows安装配置
    Qt编译MySQL驱动
    P8375 [APIO2022] 游戏 解题报告
    CF1687E Become Big For Me 出题报告
    解决 windows 10 WSL 安装Ubuntu后 屏幕亮度飙至最高 且屏幕亮度无法调节 外接显示器无法显示 的问题
    Dubbo3 源码系列 Dubbo“纠葛”(入门篇)
    windows监控进程死亡并拉起
  • 原文地址:https://www.cnblogs.com/vlhn/p/7992741.html
Copyright © 2020-2023  润新知