【C语言】InfiniBand内核驱动_mlx4_ib_post_send

news2025/2/2 0:50:38

一、注释

以下是`_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设备特性,实际的函数签名和内部逻辑可能会有所不同。但上述概述描绘了这类函数的核心功能和作用。如果你有具体的代码片段或者更详细的问题,可以进一步讨论。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

再见 mysql_upgrade

在数据库管理的世界里&#xff0c;随着技术的不断进步和业务的不断发展&#xff0c;数据库的版本升级成为了一个不可避免的过程。 MySQL 作为业界领先的开源关系型数据库管理系统&#xff0c;其版本迭代与功能优化同样不容忽视。 而在这个过程中&#xff0c;升级工具就显得尤为…

vsqt更改ui,cpp报错(唯二)解决方法,及ui界面布局在cpp文件的运用基本流程

qt的ui布局界面如下 点cpp文件->编译 此时就会自动生成ui_xxx.h 这里是ui文件里面就有类名&#xff1a;Ui_文件名字 下面就有一个类继承于这个类 你所使用的这个ui指针&#xff0c;就这么来的 ***报错解决方法有两种&#xff1a;***第一种&#xff1a;如果改了ui&#x…

括号生成(回溯+剪枝)

22. 括号生成 - 力扣&#xff08;LeetCode&#xff09; 题目描述 数字 n 代表生成括号的对数&#xff0c;请你设计一个函数&#xff0c;用于能够生成所有可能的并且 有效的 括号组合。 样例输入 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff1a;["((()))&q…

基于SSM的百货中心供应链管理系统设计与实现(论文+源码)_kaic

摘 要 社会发展日新月异&#xff0c;用计算机应用实现数据管理功能已经算是很完善的了&#xff0c;但是随着移动互联网的到来&#xff0c;处理信息不再受制于地理位置的限制&#xff0c;处理信息及时高效&#xff0c;备受人们的喜爱。本次开发一套百货中心供应链管理系统有管理…

一篇复现Dockerfile指令

华子目录 制作镜像基于dockerfile制作镜像dockerfile介绍注意 格式PATH上下文路径URL- Dockerfile指令-FROM指令格式示例 Dockerfile指令-MAINTAINER指令介绍示例 Dockerfile指令-COPY指令介绍示例1示例2 Dockerfile指令-ADD指令介绍示例 Dockerfile指令-WORKDIR指令介绍示例 D…

Vue2版本封装公共echarts的监听方法

#注意 &#xff1a; 因为一个页面有多个图表&#xff0c;所以封装一个公共的js文件&#xff0c;方便后续使用。 适用于Vue2版本&#xff0c;粘贴即用即可。 1、echartsMixin.js文件如下 // echartsMixin.js import echarts from echartsexport default {data() {return {myC…

2024年EDM邮件营销群发平台怎么选?

在2024年选择适合的EDM&#xff08;电子邮件直接营销&#xff09;邮件营销群发平台时&#xff0c;需要考虑以下几个关键要素来评估云衔科技以及其他供应商的产品或服务是否符合您的需求&#xff1a; 一、功能完备性&#xff1a; 1、智能自动化&#xff1a;确保云衔科技提供的…

JAVA 并发编程之AQS排队同步框架

AQS 框架简介 AQS&#xff08;Abstract Queued Synchronizer&#xff09;抽象队列同步框架。 比如&#xff0c;ReentrantLock、ReentrantReadWriteLock、Semaphore、CountDownLatch、ThreadPoolExcutor&#xff08;JDK 1.8&#xff09;&#xff0c;这些类的底层原理都是AQS&a…

(十一)图像的罗伯特梯度锐化

环境&#xff1a;Windows10专业版 IDEA2021.2.3 jdk11.0.1 OpenCV-460.jar 系列文章&#xff1a; &#xff08;一&#xff09;PythonGDAL实现BSQ&#xff0c;BIP&#xff0c;BIL格式的相互转换 &#xff08;二&#xff09;BSQ,BIL,BIP存储格式的相互转换算法 &#xff08;三…

AWTK 开源串口屏开发(15) - 通过 MODBUS 访问远程设备数据

在 AWTK 串口屏中&#xff0c;内置了 MODBUS Client 的模型&#xff0c;支持用 MODBUS 协议从远程设备获取数据。不用编写一行代码即可实现对远程设备数据的显示和修改。 1. 功能 不用编写代码&#xff0c;实现对远程设备数据的显示和修改。 2. 创建项目 从模板创建项目&am…

【生活】相机/图像各参数

文章目录 专业模式图片编辑-滤镜实体滤镜软件模拟滤镜 图片编辑-增强曝光亮度对比度饱和度自然饱和度色温色调高光阴影HSL色调分离褪色颗粒锐化晕影清晰度暗角 参考 专业模式 第一个参数WB是白平衡&#xff0c;调节色彩的。 第二个是对焦F&#xff0c;近距离拍摄物体&#xf…

67、yolov8目标检测和旋转目标检测算法部署Atlas 200I DK A2开发板上

基本思想&#xff1a;需求部署yolov8目标检测和旋转目标检测算法部署atlas 200dk 开发板上 一、转换模型 链接: https://pan.baidu.com/s/1hJPX2QvybI4AGgeJKO6QgQ?pwdq2s5 提取码: q2s5 from ultralytics import YOLO# Load a model model YOLO("yolov8s.yaml")…

夏季水域安全管理,AI智能识别算法防溺水视频监控方案

随着夏季的到来&#xff0c;不少人为了一时的痛快凉爽就私自下水游泳&#xff0c;特别是在野外池塘&#xff0c;由于长期无人监管&#xff0c;极易发生人员溺亡事件&#xff0c;如何对池塘水域进行全天候无人值守智能监管&#xff0c;并实现发生人员闯入就立即告警&#xff1f;…

网安播报 | GitHub遭遇严重的供应链“投毒”攻击,影响GG平台

1、GitHub遭遇严重的供应链“投毒”攻击&#xff0c;影响GG平台 多年来&#xff0c;威胁行为者一直在使用多种策略、技术和程序 &#xff08;TTP&#xff09;&#xff0c;包括劫持 GitHub账户、分发恶意 Python 包、使用虚假的 Python 基础设施以及社会工程进行攻击&#xff0c…

Django屏蔽Server响应头信息

一、背景 最近我们被安全部门的漏洞扫描工具扫出了一个服务端口的漏洞。这个服务本身是一个Django启动的web服务&#xff0c;并且除了登录页面&#xff0c;其它页面或者接口都需要进行登录授权才能进行访问。 漏洞扫描信息和提示修复信息如下: 自然这些漏洞如何修复&#xff0c…

物理寻址和功能寻址,服务器不同的应答策略和NRC回复策略

1&#xff1a;功能寻址&#xff0c;服务器应答与NRC回复策略 详细策略上&#xff0c;又分为服务有子功能&#xff0c;和不存在子功能。 1.1功能寻址&#xff0c;存在子功能 存在子功能的情况下&#xff0c;又分为supress postive response &#xff08;即子功能字节的bit7&a…

Ollama+WebUI+AnythingLLM,构建安全可靠的个人/企业知识库

ChatGPT狂飙160天&#xff0c;世界已经不是之前的样子。 新建了人工智能中文站https://ai.weoknow.com 每天给大家更新可用的国内可用chatGPT资源 ​ 发布在https://it.weoknow.com 我有一名AI助手&#xff0c;名字叫"老六"。为什么呢&#xff1f;是因为它有时候会…

Java运算符-三元运算符,这你必须得会!

哈喽&#xff0c;各位小伙伴们&#xff0c;你们好呀&#xff0c;我是喵手。 今天我要给大家分享一些自己日常学习到的一些知识点&#xff0c;并以文字的形式跟大家一起交流&#xff0c;互相学习&#xff0c;一个人虽可以走的更快&#xff0c;但一群人可以走的更远。 我是一名后…

【edge浏览器无法登录某些网站,以及迅雷插件无法生效的解决办法】

edge浏览器无法登录某些网站&#xff0c;以及迅雷插件无法生效的解决办法 edge浏览器无法登录某些网站&#xff0c;但chrome浏览器可以登录浏览器插件无法使用&#xff0c;比如迅雷如果重装插件重装浏览器重装迅雷后仍然出现问题 edge浏览器无法登录某些网站&#xff0c;但chro…

第十二章:预处理命令

文章目录 第十二章&#xff1a;预处理命令宏定义无参宏定义带参数的宏定义 文件包含处理 第十二章&#xff1a;预处理命令 作用&#xff1a;由编译预处理程序对程序中的特殊命令作出解释&#xff0c;以产生新的源程序对其进行正式编译 C语言与其他语言的重要区别就是可以使用预…