[SPDK/NVMe存儲技術分析]011 - 內核態ib_post_send()源碼剖析


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. | 主動性就是在沒有人告訴你時做正確的事情。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM