【C语言】InfiniBand内核驱动_mlx4_ib_post_send

一、注释

以下是`_mlx4_ib_post_send`函数的注释,该函数用于处理InfiniBand工作请求(WRs)的发送过程:

static int _mlx4_ib_post_send(struct ib_qp *ibqp, const struct ib_send_wr *wr,
                  const struct ib_send_wr **bad_wr, bool drain)
{
    struct mlx4_ib_qp *qp = to_mqp(ibqp); // 从 ib_qp 结构转换为 mlx4_ib_qp 结构
    void *wqe; // 工作队列元素(WQE)
    struct mlx4_wqe_ctrl_seg *uninitialized_var(ctrl); // 控制段,初始化状态未确定
    struct mlx4_wqe_data_seg *dseg;
    unsigned long flags;
    int nreq; // 请求计数
    int err = 0; // 错误码,默认为0
    unsigned ind; // 索引
    int uninitialized_var(size); // 大小,初始化状态未确定
    unsigned uninitialized_var(seglen); // 数据段长度,初始化状态未确定
    __be32 dummy; // 用于LSO(Large Send Offload)WQE
    __be32 *lso_wqe; // 指向LSO WQE
    __be32 uninitialized_var(lso_hdr_sz); // LSO头部大小,初始化状态未确定
    __be32 blh;
    int i;
    int inl = 0; // 是否是内联
    struct mlx4_ib_dev *mdev = to_mdev(ibqp->device); // 从 ib_device 转换为 mlx4_ib_dev

    if (qp->mlx4_ib_qp_type == MLX4_IB_QPT_GSI) {
        // 处理特定类型的队列对应的特殊情形
    }
    // 锁住发送队列以防止并发访问
    spin_lock_irqsave(&qp->sq.lock, flags);
    if (mdev->dev->persist->state & MLX4_DEVICE_STATE_INTERNAL_ERROR && !drain) {
        // 如果设备内部出错且不是清空队列,则返回错误
        err = -EIO;
        *bad_wr = wr; // 设置出错的WR
        nreq = 0;
        goto out; // 跳出执行
    }

    ind = qp->sq_next_wqe; // 获取下一个WQE的索引

    // 循环处理每个发送WR
    for (nreq = 0; wr; ++nreq, wr = wr->next) {
        // 对每个工作请求(WR)执行发送处理
    }

    // 完成所有请求的处理后,发送通知并更新WQE
out:
    if (nreq == 1 && inl && size > 1 && size < qp->bf.buf_size / 16) {
        // 如果只有一个请求且为内联,则进行优化处理
    } else if (nreq) {
        qp->sq.head += nreq; // 增加头部索引

        // 确保描述符写入完成后更新doorbell记录
        wmb();

        // 写入doorbell以通知硬件可以开始执行
        __raw_writel((__force u32)qp->doorbell_qpn, qp->bf.uar->map + MLX4_SEND_DOORBELL);
    }
    if (likely(nreq)) {
        // 最后,更新WQE并解锁
    }

    spin_unlock_irqrestore(&qp->sq.lock, flags); // 解锁发送队列

    return err; // 返回错误码
}

这个函数的主要工作流程是:
1. 准备发送队列和资源。
2. 对输入的工作请求(WRs)进行循环处理。
3. 处理每一个WR的发送逻辑,并构造WQEs。
4. 更新发送队列的状态和索引。
5. 如果所有WQEs都正确构造,则通知硬件进行处理。
6. 最后,解锁发送队列并返回。
它包括了很多细节,如错误处理、优化路径、数据准备、判断发送的消息类型等。需要注意,这个函数可能修改`qp`结构体内的状态,并在出现错误时适当设置错误代码和`bad_wr`。

// _mlx4_ib_post_send函数是处理Infiniband Work Request发送操作的函数
// 该函数以Mellanox的mlx4网卡为例展示了如何将IB操作转换为HW可以理解的WQE(Work Queue Element)格式
static int _mlx4_ib_post_send(struct ib_qp *ibqp, const struct ib_send_wr *wr,
                  const struct ib_send_wr **bad_wr, bool drain)
{
    // 将通用的ib_qp指针转换为mlx4_ib_qp结构指针
    struct mlx4_ib_qp *qp = to_mqp(ibqp);
    void *wqe;
    // 未初始化的控制段指针
    struct mlx4_wqe_ctrl_seg *uninitialized_var(ctrl);
    struct mlx4_wqe_data_seg *dseg;
    unsigned long flags;
    int nreq;
    // 错误码初始为0
    int err = 0;
    unsigned ind;
    // 未初始化的变量,用于存储WQE大小
    int uninitialized_var(size);
    // 未初始化的变量,用于存储段长度
    unsigned uninitialized_var(seglen);
    __be32 dummy;
    __be32 *lso_wqe;
    // 未初始化的变量,用于存储LSO头部大小
    __be32 uninitialized_var(lso_hdr_sz);
    __be32 blh;
    int i;
    // 是否执行内存内发送
    int inl = 0;
    // 获取mlx4_ib设备指针
    struct mlx4_ib_dev *mdev = to_mdev(ibqp->device);
    // 如果QP类型为GSI(Global Service Interface),则执行特殊处理
    if (qp->mlx4_ib_qp_type == MLX4_IB_QPT_GSI) {
        struct mlx4_ib_sqp *sqp = to_msqp(qp);
        // 处理RoCE v2 GSI特殊情况
        if (sqp->roce_v2_gsi) {
            struct mlx4_ib_ah *ah = to_mah(ud_wr(wr)->ah);
            enum ib_gid_type gid_type;
            union ib_gid gid;
            // 对多功能设备执行特殊处理
            if (mlx4_is_mfunc(mdev->dev)) {
                enum mlx4_roce_gid_type roce_gid_type;
                union ib_gid sgid;
                // 从缓存中获取RoCE GID信息,而不是直接使用硬件
                err = mlx4_get_roce_gid_from_slave(mdev->dev,
                                   be32_to_cpu(ah->av.ib.port_pd) >> 24,
                                   ah->av.ib.gid_index, &sgid.raw[0], &roce_gid_type);
                gid_type = mlx4_gid_type_to_ib_gid_type(roce_gid_type);
            } else  {
                err = fill_gid_by_hw_index(mdev, sqp->qp.port,
                               ah->av.ib.gid_index,
                               &gid, &gid_type);
            }
            // 如果是RoCE v2,则切换到roce_v2_gsi QP
            qp = (gid_type == IB_GID_TYPE_ROCE_UDP_ENCAP) ?
                    to_mqp(sqp->roce_v2_gsi) : qp;
        }
    }
    // 上锁以保护发送队列修改操作
    spin_lock_irqsave(&qp->sq.lock, flags);
    // 如果设备处于内部错误状态,并且非排空模式,则直接返回输入/输出错误
    if (mdev->dev->persist->state & MLX4_DEVICE_STATE_INTERNAL_ERROR && !drain) {
        err = -EIO;
        // 设置bad_wr指向出错的WR
        *bad_wr = wr;
        nreq = 0;
        goto out;
    }
    // 获取下一个WQE索引
    ind = qp->sq_next_wqe;
    // 遍历所有的WR,构造WQE并更新到硬件
    for (nreq = 0; wr; ++nreq, wr = wr->next) {
        // 储存相关信息,稍后用于构建LSO WQE
        lso_wqe = &dummy;
        blh = 0;
        // 如果发送队列溢出,则返回空间不足错误
        if (mlx4_wq_overflow(&qp->sq, nreq, qp->ibqp.send_cq)) {
            err = -ENOMEM;
            *bad_wr = wr;
            goto out; 
}
        // 如果发送段的数量超过了队列的最大允许值,返回错误
        if (unlikely(wr->num_sge > qp->sq.max_gs)) {
            err = -EINVAL;
            *bad_wr = wr;
            goto out;
        }
        // 获取当前WQE在队列中的位置
        ctrl = wqe = get_send_wqe(qp, ind & (qp->sq.wqe_cnt - 1));
        // 初始化vlan标签为0
        ((u32 )(&ctrl->qpn_vlan.vlan_tag)) = 0;
        // 记录当前工作请求的ID
        qp->sq.wrid[(qp->sq.head + nreq) & (qp->sq.wqe_cnt - 1)] = wr->wr_id;
        // 设置控制段的标志位,指示是否需要成帧和Solicited事件
        ctrl->srcrb_flags =
            (wr->send_flags & IB_SEND_SIGNALED ?
             cpu_to_be32(MLX4_WQE_CTRL_CQ_UPDATE) : 0) |
            (wr->send_flags & IB_SEND_SOLICITED ?
             cpu_to_be32(MLX4_WQE_CTRL_SOLICITED) : 0) |
            ((wr->send_flags & IB_SEND_IP_CSUM) ?
             cpu_to_be32(MLX4_WQE_CTRL_IP_CSUM |
                     MLX4_WQE_CTRL_TCP_UDP_CSUM) : 0) |
            qp->sq_signal_bits;
        // 设置立即数,如果包含在wr发送标志中
        ctrl->imm = send_ieth(wr);
        // 准备后续段所需的空间
        wqe += sizeof *ctrl;
        size = sizeof *ctrl / 16;
        // 根据QP类型和操作码,修改WQE以适配不同类型的请求
        switch (qp->mlx4_ib_qp_type) {
        // RC和UC类型QP的处理
        case MLX4_IB_QPT_RC:
        case MLX4_IB_QPT_UC:
            switch (wr->opcode) {
            case IB_WR_ATOMIC_CMP_AND_SWP:
            case IB_WR_ATOMIC_FETCH_AND_ADD:
            case IB_WR_MASKED_ATOMIC_FETCH_AND_ADD:
                // 为原子操作设置远程地址段和原子段
                set_raddr_seg(wqe, atomic_wr(wr)->remote_addr,
                          atomic_wr(wr)->rkey);
                wqe  += sizeof(struct mlx4_wqe_raddr_seg);
                set_atomic_seg(wqe, atomic_wr(wr));
                wqe  += sizeof(struct mlx4_wqe_atomic_seg);
                size += (sizeof(struct mlx4_wqe_raddr_seg) +
                     sizeof(struct mlx4_wqe_atomic_seg)) / 16;
                break;
            case IB_WR_MASKED_ATOMIC_CMP_AND_SWP:
                // 类似地,为掩码原子操作设置段
                set_raddr_seg(wqe, atomic_wr(wr)->remote_addr,
                          atomic_wr(wr)->rkey);
                wqe  += sizeof(struct mlx4_wqe_raddr_seg);
                set_masked_atomic_seg(wqe, atomic_wr(wr));
                wqe  += sizeof(struct mlx4_wqe_masked_atomic_seg);
                size += (sizeof(struct mlx4_wqe_raddr_seg) +
                     sizeof(struct mlx4_wqe_masked_atomic_seg)) / 16;
                break;
            case IB_WR_RDMA_READ:
            case IB_WR_RDMA_WRITE:
            case IB_WR_RDMA_WRITE_WITH_IMM:
                // 为RDMA Read/Write设置远程地址段
                set_raddr_seg(wqe, rdma_wr(wr)->remote_addr,
                          rdma_wr(wr)->rkey);
                wqe  += sizeof(struct mlx4_wqe_raddr_seg);
                size += sizeof(struct mlx4_wqe_raddr_seg) / 16;
                break;
            case IB_WR_LOCAL_INV:
                // 本地无效操作的控制段设置
                ctrl->srcrb_flags |=
                    cpu_to_be32(MLX4_WQE_CTRL_STRONG_ORDER);
                set_local_inv_seg(wqe, wr->ex.invalidate_rkey);
                wqe  += sizeof(struct mlx4_wqe_local_inval_seg);
                size += sizeof(struct mlx4_wqe_local_inval_seg) / 16;
                break;
            case IB_WR_REG_MR:
                // 用于注册内存区域的操作
                ctrl->srcrb_flags |=
                    cpu_to_be32(MLX4_WQE_CTRL_STRONG_ORDER);
                set_reg_seg(wqe, reg_wr(wr));
                wqe  += sizeof(struct mlx4_wqe_fmr_seg);
                size += sizeof(struct mlx4_wqe_fmr_seg) / 16;
                break;
            default:
                // 其他情况不需要额外的段
                break;
            }
            break;
        // 所有单播和多播数据包都通过特定于设备的QPs进行路由
        // QP0处理SMI(子管理器接口),而QP1处理GSI(全局服务接口))
        // 下面是特殊的QP类型的处理
        // 在MLX4_IB_QPT_TUN_SMI_OWNER和以下类型中,需要构建不同类型的WQE
        // 省略其他特定处理...(请根据需要增补代码注释)
        // 提交WQE后的一些尾部处理
        // 写入数据段,逆序写入,从而最后一个写操作会覆写每个cache line内的时间戳
        // 这种写入顺序是为了避免WQE预取导致的问题
        dseg = wqe;
        dseg += wr->num_sge - 1;
        // 对于MLX类型的发送,还需要添加一个用于ICRC 
        // 根据QP的不同类型,处理不同的WR(Work Request)操作
        switch (qp->mlx4_ib_qp_type) {
        // 略过前面的case...

        case MLX4_IB_QPT_TUN_SMI_OWNER:
            // 如果QP是用于管理和配置子虚拟机(SVM)的特殊QP
            // 构建用于代理SMI所有者的Infiniband消息头
            err =  build_sriov_qp0_header(to_msqp(qp), ud_wr(wr),
                    ctrl, &seglen);
            if (unlikely(err)) {
                // 如果构建消息头失败,记录错误的Work Request然后退出
                *bad_wr = wr;
                goto out;
            }
            // 更新wqe指针和size到正确的位置
            wqe  += seglen;
            size += seglen / 16;
            break;
        
        case MLX4_IB_QPT_TUN_SMI:
        case MLX4_IB_QPT_TUN_GSI:
            // 当这个QP是用于子虚拟机(SVM)回复MADs(管理数据)时的User Datagram(QP类型为UD),
            // 对一个数据报的Segment执行设置
            set_datagram_seg(wqe, ud_wr(wr));
            // 在数据段的AV中设置强制环回位
            *(__be32 *) wqe |= cpu_to_be32(0x80000000);
            // 更新wqe指针和size到正确的位置
            wqe  += sizeof (struct mlx4_wqe_datagram_seg);
            size += sizeof (struct mlx4_wqe_datagram_seg) / 16;
            break;
        
        case MLX4_IB_QPT_UD:
            // 对UD类型的QP设置数据报Segment
            set_datagram_seg(wqe, ud_wr(wr));
            // 更新wqe指针和size到正确的位置
            wqe  += sizeof (struct mlx4_wqe_datagram_seg);
            size += sizeof (struct mlx4_wqe_datagram_seg) / 16;
            
            // 如果WR的操作码是LSO,则构建LSO Segment
            if (wr->opcode == IB_WR_LSO) {
                err = build_lso_seg(wqe, ud_wr(wr), qp, &seglen,
                        &lso_hdr_sz, &blh);
                if (unlikely(err)) {
                    // 如果构建LSO Segment失败,记录错误的Work Request然后退出
                    *bad_wr = wr;
                    goto out;
                }
                lso_wqe = (__be32 *) wqe;
                // 更新wqe指针和size到正确的位置
                wqe  += seglen;
                size += seglen / 16;
            }
            break;

        case MLX4_IB_QPT_PROXY_SMI_OWNER:
            // 对于代理SMI所有者的QP构建Infiniband消息头
            err = build_sriov_qp0_header(to_msqp(qp), ud_wr(wr),
                    ctrl, &seglen);
            if (unlikely(err)) {
                // 如果构建消息头失败,记录错误的Work Request然后退出
                *bad_wr = wr;
                goto out;
            }
            // 更新wqe指针和size到正确的位置
            wqe  += seglen;
            size += seglen / 16;
            // 在高速缓存线的边界上开始隧道头部,添加16字节的空inline数据以实现对齐
            add_zero_len_inline(wqe);
            wqe += 16;
            size++;
            // 构建隧道头部
            build_tunnel_header(ud_wr(wr), wqe, &seglen);
            // 更新wqe指针和size到正确的位置
            wqe  += seglen;
            size += seglen / 16;
            break;
        
        case MLX4_IB_QPT_PROXY_SMI:
        case MLX4_IB_QPT_PROXY_GSI:
            // 如果这是代理特殊QP类型的QP,首先添加指向隧道QP的UD Segment
            set_tunnel_datagram_seg(to_mdev(ibqp->device), wqe,
                        ud_wr(wr),
                        qp->mlx4_ib_qp_type);
            // 更新wqe指针和size到正确的位置
            wqe  += sizeof (struct mlx4_wqe_datagram_seg);
            size += sizeof (struct mlx4_wqe_datagram_seg) / 16;
            // 然后添加带有地址信息的头部
            build_tunnel_header(ud_wr(wr), wqe, &seglen);
            // 更新wqe指针和size到正确的位置
            wqe  += seglen;
            size += seglen / 16;
            break;

        case MLX4_IB_QPT_SMI:
        case MLX4_IB_QPT_GSI:
            // 对于SMI和GSI类型的QP,构建mlx消息头
            err = build_mlx_header(to_msqp(qp), ud_wr(wr), ctrl,
                    &seglen);
            if (unlikely(err)) {
                // 如果构建消息头失败,记录错误的Work Request然后退出
                *bad_wr = wr;
                goto out;
            }
            // 更新wqe指针和size到正确的位置
            wqe  += seglen;
            size += seglen / 16;
            break;

        // 之后的default省略...
        }

        // 略过对数据段写入的部分代码...
        
out:
        // 错误处理和退出逻辑,略...
        
    return err;
}
        /*
         * 以相反的顺序写入数据段,以便在每个缓存行内最后覆盖缓存行标记。
         * 这样可以避免WQE预取时出现的问题。
         */
        dseg = wqe;
        dseg += wr->num_sge - 1;

        /* 添加额外的内联数据段,用于MLX发送的ICRC */
        if (unlikely(qp->mlx4_ib_qp_type == MLX4_IB_QPT_SMI ||
                 qp->mlx4_ib_qp_type == MLX4_IB_QPT_GSI ||
                 qp->mlx4_ib_qp_type &
                 (MLX4_IB_QPT_PROXY_SMI_OWNER | MLX4_IB_QPT_TUN_SMI_OWNER))) {
            set_mlx_icrc_seg(dseg + 1);
            size += sizeof (struct mlx4_wqe_data_seg) / 16;
        }

        if (wr->send_flags & IB_SEND_INLINE && wr->num_sge) {
            int sz;

            // 如果指定了使用内联发送,并且有SGE(Scatter/Gather元素),则安置内联数据
            err = lay_inline_data(qp, wr, wqe, &sz);
            if (err) {
                *bad_wr = wr;
                goto out;
            }
            inl = 1;
            size += sz;
        } else {
            // 根据SGE数量更新WQE大小
            size += wr->num_sge *
                (sizeof(struct mlx4_wqe_data_seg) / 16);
            // 以相反顺序设置数据段
            for (i = wr->num_sge - 1; i >= 0; --i, --dseg)
                set_data_seg(dseg, wr->sg_list + i);
        }

        // 强制内存屏障,确保所有的数据段都被写入之后再写入LSO段
        wmb();
        // 可能会用LSO段覆盖之前的缓存行标记
        *lso_wqe = lso_hdr_sz;

        // 设置WQE控制段的大小和栅栏位标志
        ctrl->qpn_vlan.fence_size = (wr->send_flags & IB_SEND_FENCE ?
                         MLX4_WQE_CTRL_FENCE : 0) | size;

        /*
         * 在设置所有权位之前,确保描述符完全写入到内存。
         * 因为硬件可能在我们设置所有权位之后就开始执行描述符。
         */
        wmb();

        if (wr->opcode < 0 || wr->opcode >= ARRAY_SIZE(mlx4_ib_opcode)) {
            *bad_wr = wr;
            err = -EINVAL;
            goto out;
        }

        ctrl->owner_opcode = mlx4_ib_opcode[wr->opcode] |
            (ind & qp->sq.wqe_cnt ? cpu_to_be32(1 << 31) : 0) | blh;

        /*
         * 通过在响铃门铃之后才标记最后一个发送队列WQE,我们可以提高延迟性能,
         * 因此,仅当还有更多WQE要发布时才能在这里标记。
         */
        if (wr->next)
            stamp_send_wqe(qp, ind + qp->sq_spare_wqes);
        ind++;
    }
out:
    // 如果只发送了一个请求,并且使用内联方式且WQE大小小于缓冲区,
    // 可以直接把控制段写入BlueFlame页来提高吞吐量
    if (nreq == 1 && inl && size > 1 && size < qp->bf.buf_size / 16) {
        // 设置门铃QPN,用于在硬件上标志这个QP是准备发送的
        ctrl->owner_opcode |= htonl((qp->sq_next_wqe & 0xffff) << 8);
        // 设置VLAN标签,门铃QPN低位是之前已经设置为0的,所以直接用或赋值是正确的
        *(__be32 *)(&ctrl->qpn_vlan.vlan_tag) |= qp->doorbell_qpn;
        // 确保描述符已经刷新到内存,然后才写入蓝色火焰页
        wmb();

        // 在发送队列中移动head指针
        ++qp->sq.head;

        // 将控制段复制到注册寄存器中,在64字节对齐
        mlx4_bf_copy(qp->bf.reg + qp->bf.offset, (unsigned long *)ctrl,
                 ALIGN(size * 16, 64));
        // 再次确保写操作已完成
        wc_wmb();

        // 反转BlueFlame页的偏移值,因为有可能有两个缓冲区按顺序使用
        qp->bf.offset ^= qp->bf.buf_size;

    } else if (nreq) {
        // 如果有多个请求,更新发送队列头部,准备发送下一批请求
        qp->sq.head += nreq;

        // 确保描述符写在门铃记录前
        wmb();

        // 写门铃以通知硬件可以发送数据了
        __raw_writel((__force u32)qp->doorbell_qpn,
                 qp->bf.uar->map + MLX4_SEND_DOORBELL);

    }
    // 如果发送了请求,给发送队列最后一个WQE打上时间戳,并更新下一个WQE索引
    if (likely(nreq)) {
        stamp_send_wqe(qp, ind + qp->sq_spare_wqes - 1);

        qp->sq_next_wqe = ind;
    }

    // 解锁,发送操作完成
    spin_unlock_irqrestore(&qp->sq.lock, flags);

    // 返回错误码,如果没有发送错误,将返回0,意味着成功
    return err;
}

在上面这段代码中,`out`标签之后的逻辑是在所有的请求被处理完毕之后执行的清理和终结操作。这部分代码主要是负责处理数据发送后的一些状态更新,如合理地更新队列头指针、撤销锁及通知硬件可以进行数据发送等。同时,根据不同情况(如是否只有一个请求,请求是否使用了内联),代码对性能的优化手段也有所不同。返回值`err`代表整个发送操作的结果,如果整个操作无误,则返回0,表示成功执行。

二、讲解

该函数 _mlx4_ib_post_send() 是在 Mellanox 的 IB (InfiniBand) 驱动 mlx4_ib 模块中实现的,主要负责将 IB (InfiniBand) QP (Queue Pair) 的发送工作请求 (Work Request) 提交到硬件层。这个过程包括构建 WQE (Work Queue Element) 的各种段并将其放入发送队列中。在硬件处理完成后,关联的 CQ (Completion Queue) 将得到通知。
下面是对代码的中文逐步解读:
1. 首先,函数接受几个参数,包括:
- ibqp:一个指向所涉及的 IB (InfiniBand) QP (Queue Pair) 的指针。
- wr:指向用户提交的发送工作请求(Work Request)的指针。
- bad_wr:如果发送过程中遇到错误,这个参数会指向发生错误的工作请求。
- drain:一个布尔值,用来标记是否应该“排空”(清除)QP中的所有工作请求。
2. 函数体内部,定义了多个局部变量来构建 WQE (Work Queue Element),同时获取 IB 设备和 QP 的内部表示。
3. spin_lock_irqsave(&qp->sq.lock, flags); 用来获取发送队列的自旋锁以保护共享数据结构。
4. 函数遍历由 wr 指定的工作请求列表,为每个工作请求构建一个或多个 WQE 段。不同类型的操作,如原子操作 (ATOMIC)、RDMA 读写、发送带有 IMM 数据等将需要不同的 WQE 段来描述。
5. 每次循环的内部逻辑主要包括:
- 检查工作请求的有效性。
- 构建控制段(`ctrl` segment),它包括请求的类型,是否需要生成完成通知等信息。
- 根据操作类型 (wr->opcode) 添加特定的段,例如寻址信息 (raddr segment) 对于 RDMA 和原子操作、数据段 (dseg segment) 对于包含数据的请求。
6. 对于数据包含的发送请求,可能会选择以内联方式(`IB_SEND_INLINE` 标志)发送数据,这表示将数据直接放在 WQE 中而不通过外部的内存缓冲区。
7. 一旦 WQE 构建完成,会执行内存障碍操作以确保 WQE 写入内存后再更新硬件。
8. 然后,根据是否使用 BlueFlame (BF) 门铃机制来决定如何通知硬件处理新的 WQE。如果使用 BF 机制,它将 WQE 直接拷贝到一特定内存区域中,不使用 BF 则是通过写入相应的门铃记录来通知硬件。
9. 最后,函数释放自旋锁并返回执行结果。如果函数在处理 WRs 时遇到错误,则 err 变量会记录错误代码,`bad_wr` 会指向出错的 WR。
请注意,这个函数是 Mellanox InfiniBand 驱动的内部实现细节之一,它依赖于特定的硬件和驱动程序架构,非常复杂且难以理解,除非您熟悉 RDMA 编程和 InfiniBand 协议。上述解释提供了该函数的高级概述。

ChatGPT

在InfiniBand架构下,MLX4是指Mellanox Technologies提供的一种网络适配器硬件,在Linux内核中,`mlx4_ib`是Mellanox InfiniBand协议驱动的一部分,它主要用于管理和配置Mellanox网络设备。
mlx4_ib_post_send函数是该驱动中用于处理发送操作的函数之一。这个函数的作用是将一个或多个发送请求(通常是WR,Work Request)放入一个指定的发送队列(Send Queue)。发送请求一旦被加入队列,硬件就会处理它,并最终将数据发送出去。
这个函数的原型可能如下所示(根据不同版本的Linux内核和Mellanox驱动,原型可能略有不同):

int mlx4_ib_post_send(struct ib_qp *qp, const struct ib_send_wr *wr,
                      const struct ib_send_wr **bad_wr);

参数说明:
- qp:指向`ib_qp`结构的指针,它代表一个队列对(Queue Pair),在InfiniBand通信中用来表示连接的一个端点。
- wr:指向`ib_send_wr`结构的指针,即要发送的工作请求。它可以是一个单独的请求,也可以是多个请求的链表。
- bad_wr:如果函数失败,这个参数会指向中断处理的那个工作请求。
如果函数成功执行,它会返回0;如果失败,则返回负的错误码,并且`bad_wr`将会指向导致问题的工作请求。
对`mlx4_ib_post_send`函数的修改通常需要深入的内核编程经验,以及对InfiniBand架构和具体的硬件实现有深入理解。如果你需要对这个函数进行调试或修改,应该参考具体的Mellanox文档及内核源码,以便了解该函数的具体实现细节以及与其他内核组件的交互方式。

通义灵码

在InfiniBand硬件架构中,mlx4_ib_post_send 函数通常是 Mellanox Technologies 所开发的 InfiniBand 驱动(mlx4 驱动)的一部分,用于处理发送数据包到远程节点的过程。这个函数通常位于 mlx4_ib.c 文件中,它是 Linux 内核模块实现的一部分,负责将用户空间的数据缓冲区映射到硬件传输队列,并通过 InfiniBand HCA(Host Channel Adapter)硬件进行实际的数据发送操作。

mlx4_ib_post_send 函数的主要任务包括但不限于以下几点:

  1. 解析工作请求(Work Request, WR):根据IB verbs接口规范接收来自上层协议栈或应用程序的工作请求结构体,比如RDMA Send/Write/Read请求。

  2. 设置发送描述符(Work Queue Entry, WQE):基于接收到的请求信息,填充硬件传输队列中的一个或多个描述符,这些描述符包含了诸如目标QPN(Queue Pair Number)、传输长度、LKey(Local Key)等相关信息。

  3. 内存映射与同步:如果必要的话,确保用户空间的数据已经被正确地映射到HCA可访问的地址空间,并完成必要的内存一致性同步。

  4. 向硬件提交发送:最后,函数会将填充好的WQE提交到HCA的发送队列中,触发硬件开始执行数据传输。

  5. 错误处理:检查潜在的错误条件,并在发生错误时采取适当的动作,如更新状态、返回错误代码等。

由于具体实现细节依赖于驱动的具体版本以及InfiniBand设备特性,实际的函数签名和内部逻辑可能会有所不同。但上述概述描绘了这类函数的核心功能和作用。如果你有具体的代码片段或者更详细的问题,可以进一步讨论。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/509622.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

备考ICA----Istio实验15---开启 mTLS 自动双向认证实验

备考ICA----Istio实验15—开启mTLS自动双向认证实验 在某些生成环境下,我们希望微服务和微服务之间使用加密通讯方式来确保不被中间人代理. 默认情况下Istio 使用 PERMISSIVE模式配置目标工作负载,PERMISSIVE模式时,服务可以使用明文通讯.为了只允许双向 TLS 流量&#xff0c;…

XGB回归预测

关键代码 import numpy as np import matplotlib.pyplot as plt from xgboost import XGBRegressor #pip install xgboost -i https://pypi.tuna.tsinghua.edu.cn/simple import pandas as pd import joblib#处理中文字体 plt.rcParams[font.family] [sans-serif] plt.rcPar…

XMind 2024 下载地址及安装教程

XMind是一款流行的思维导图软件&#xff0c;它帮助用户以图形化的方式组织和呈现思维、概念和信息。XMind可以应用于各个领域&#xff0c;如项目管理、思维导图、会议记录、学习笔记等。 XMind提供了直观和易于使用的界面&#xff0c;用户可以通过拖放和连线来创建思维导图。它…

String、StringBuffer、StringBuilder类

最近在复习 Java 基础的时候&#xff0c;看到了 String 这块的内容&#xff0c;我突发奇想&#xff0c;可以将 String、StringBuffer、StringBuilder 这些知识点整合在一起记忆。我之前背的那个答案其实有点琐碎&#xff0c;而且不太好理解&#xff0c;还繁杂&#xff0c;所以我…

PS从入门到精通视频各类教程整理全集,包含素材、作业等(5)

PS从入门到精通视频各类教程整理全集&#xff0c;包含素材、作业等 最新PS以及插件合集&#xff0c;可在我以往文章中找到 由于阿里云盘有分享次受限制和文件大小限制&#xff0c;今天先分享到这里&#xff0c;后续持续更新 初寒调色案例及练习图 等文件 https://www.alipan.…

Android Studio 识别不到物理机设备

问题 Android Studio 识别不到物理机设备 详细问题 笔者进行Android 项目开发&#xff0c;之前一直可以连接上物理机设备&#xff0c;可能由于笔者对于驱动程序进行更新修改的原因&#xff0c;突然无法连接物理机设备。搜索无数资料&#xff0c;使用无数解决方案&#xff08…

src挖掘技巧总结分享

src挖洞技术分享 src推荐刚入门的新手首选公益src如漏洞盒子、补天src&#xff0c;因为漏洞盒子收录范围广&#xff0c;只要是国内的站点都收入&#xff0c;相比其它src平台挖掘难度非常适合新手。后续可以尝试先从一些小的src厂商入手。 首先是熟能生巧&#xff0c;我一开始挖…

spring(3)

spring6 1、bean生命周期1.1 bean生命周期之五步1.2bean生命周期之七步1.3 bean生命周期之十步1.4 bean作用域与管理周期 2、把自己new的对象交给spring管理3、Bean循环依赖3.1 setsingleton3.2 构造singleton3.3 propotypeset注入3.4 bean循环依赖源码分析&#xff1a;3.5 常见…

图论模板详解

目录 Floyd算法 例题&#xff1a;蓝桥公园 Dijkstra算法 例题&#xff1a;蓝桥王国 SPFA算法 例题&#xff1a;随机数据下的最短路问题 总结 最小生成树MST Prim算法 Kruskal算法 例题&#xff1a;聪明的猴子 Floyd算法 最简单的最短路径算法&#xff0c;使用邻接…

BGP联盟、对等体组、按组打包

BGP联盟 将大的AS划分为几个子AS&#xff08;成员AS&#xff09;&#xff0c;每个子AS内部建立全连接的IBGP邻居&#xff0c;子AS之间建立EBGP邻接关系。 联盟AS&#xff1a;大AS&#xff0c;就是常说的AS号&#xff0c;一般使用公有AS号。 成员AS&#xff1a;小AS&#xff…

MongoDB 启动异常

Failed to start up WiredTiger under any compatibility version. 解决方案: 删除WiredTiger.lock 和 mongod.lock两个文件&#xff0c;在重新启动。回重新生成新的文件。

Unicode在线编码和解码工具推荐(实用)

Unicode在线编码 - Unicode编码工具 - Unicode在线生成 - Unicode在线解码 - WGCLOUD

研发效能·创享大会—IDCF五周年专场

时光流转&#xff0c;IDCF即将迎来五周年的庆典。在这个意义非凡的时刻&#xff0c;我们精心筹备了一场盛大的聚会【研发效能创享大会—IDCF五周年专场】。 IDCF自2019年成立以来&#xff0c;携手百余位技术领头人共同打造DevOps技术学习平台&#xff0c;与30万社群伙伴联动&a…

数据类型和类型检测

Data Type And Type Checking 1.编程语言中的数据类型 类型和变量 一个类型是一系列值的集合&#xff0c;这些集合可以抽象出一个相同的特点&#xff0c;并且可以相互实现计算 例如&#xff1a; 布尔类型&#xff1a;true or false整形&#xff1a;1,2,3…浮点数类型&#xf…

OM6650AM支持蓝牙5.1协议栈与2.4GHz私有协议的双模无线连接SoC芯片

OM6650AM是一款超低功耗、同时支持蓝牙5.1协议栈与2.4GHz私有协议的双模无线连接SoC芯片&#xff0c;采用4.0 mm x 4.0 mm QFN32封装&#xff0c;具有丰富的资源&#xff0c;极低的功耗&#xff0c;优异的射频性能&#xff0c;可广泛应用于车载数字钥匙模组、胎压检测、PKE钥匙…

【教程】Flutter 应用混淆

在移动应用开发中&#xff0c;保护应用代码安全至关重要。Flutter 提供了简单易用的混淆工具&#xff0c;帮助开发者在构建 release 版本应用时有效保护代码。本文将介绍如何在 Flutter 应用中使用混淆&#xff0c;并提供了相关的操作步骤和注意事项。 &#x1f4dd; 摘要 本…

YOLOv9改进策略 :主干优化 | 无需TokenMixer也能达成SOTA性能的极简ViT架构 | CVPR2023 RIFormer

💡💡💡本文改进内容: token mixer被验证能够大幅度提升性能,但典型的token mixer为自注意力机制,推理耗时长,计算代价大,而RIFormers是无需TokenMixer也能达成SOTA性能的极简ViT架构 ,在保证性能的同时足够轻量化。 💡💡💡RIFormerBlock引入到YOLOv9,多个数…

C语言第三十八弹---编译和链接

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】 编译和链接 1、翻译环境和运行环境 2、翻译环境 2.1、预处理&#xff08;预编译&#xff09; 2.2、编译 2.2.1、词法分析 2.2.2、语法分析 2.2.3、语义分…

【Linux】自定义协议+序列化+反序列化

自定义协议序列化反序列化 1.再谈 "协议"2.Cal TCP服务端2.Cal TCP客户端4.Json 喜欢的点赞&#xff0c;收藏&#xff0c;关注一下把&#xff01; 1.再谈 “协议” 协议是一种 “约定”。在前面我们说过父亲和儿子约定打电话的例子&#xff0c;不过这是感性的认识&a…

YoloV8改进策略:BackBone改进|GCNet(独家原创)|附结构图

摘要 本文使用GCNet注意力改进YoloV8,在YoloV8的主干中加入GCNet实现涨点。改进方法简单易用&#xff0c;欢迎大家使用&#xff01; 论文:《GCNet: Non-local Networks Meet Squeeze-Excitation Networks and Beyond》 非局部网络&#xff08;NLNet&#xff09;通过为每个查…