)
一、关于wq今天聊聊rdma的重要的wq的概念。在 RDMARemote Direct Memory Access和 Mellanox MLX5 网卡驱动架构中WQ 是 Work Queue工作队列的缩写。它是软件应用程序或驱动与硬件网卡之间进行任务交互的核心数据结构。简单来说WQ 是存放“待执行任务指令”的地方网卡硬件会从这个队列中读取指令并执行相应的数据收发操作。以下是关于 WQ 的详细解析1.1. WQ 的核心组成一个完整的通信通道通常由一对 WQ组成称为 QPQueue Pair队列对SQ (Send Queue发送队列)作用存储发送任务的指令。生产者软件应用通过 Verbs API 提交 Send/Write/Read 请求。消费者网卡硬件从 SQ 中取出指令构建数据包并发送。RQ (Receive Queue接收队列)作用存储接收缓冲区的描述信息告诉网卡收到数据后存放到内存的哪个位置。生产者软件应用预先 Post Receive Buffer即“预留收货地址”。消费者网卡硬件收到数据后根据 RQ 中的描述将数据 DMA 写入指定内存。注意还有一种特殊的 SRQ (Shared Receive Queue共享接收队列)允许多个 QP 共享同一个接收队列以节省内存资源常用于大规模连接场景。1.2. WQ 的基本单元WQEWQ 是一个环形缓冲区Ring Buffer其中包含一个个最小的执行单元称为 WQE (Work Queue Element工作队列元素)。WQE 是什么它是软件向硬件提交的一个“任务单”。内容包含控制信息操作类型如 SEND, WRITE, READ、消息长度、签名等。数据指针本地内存的地址VA、长度、LKey内存注册密钥。传输信息目标 QP 号、PSN包序列号、远程内存地址对于 RDMA Write/Read等。对齐要求在 MLX5 硬件中为了优化 PCIe 传输效率WQE 通常需要对齐到 64 字节Cache Line 大小不足部分会填充 Padding这种对齐后的块有时被称为 WQEB (Work Queue Element Block)。1.3. WQ 的工作流程生产者-消费者模型WQ 机制遵循典型的异步生产者-消费者模型软件生产Post应用程序调用ibv_post_send或ibv_post_recv。驱动将这些请求翻译成硬件能理解的 WQE 格式写入到 WQ 的环形缓冲区中。更新 WQ 的 Tail 指针指向下一个可用位置。通知硬件Doorbell软件写一次“门铃寄存器”Doorbell Register通知网卡“我有新任务了快来处理”。硬件消费Fetch Execute网卡硬件收到门铃信号后通过 DMA 从内存中读取 WQE。硬件解析 WQE执行具体的数据搬运或发送操作。更新 WQ 的 Head 指针表示已处理到的位置。完成通知Completion任务执行完毕后硬件向 CQ (Completion Queue完成队列) 写入一个 CQE (Completion Queue Element)告知软件任务已完成及结果状态。1.4. 为什么需要 WQ解耦与异步软件只需下发指令即可返回无需等待数据真正发送完毕极大提高了 CPU 效率。批量处理软件可以一次性提交多个 WQEBatching减少 Doorbell 触发次数降低 PCIe 开销。硬件卸载复杂的协议封装如 InfiniBand BTH 头、RoCEv2 UDP/IP 头由硬件根据 WQE 中的信息自动完成CPU 不参与数据拷贝和协议栈处理。总结WQ (Work Queue) 是 RDMA 通信的“任务调度中心”。SQ 负责“发货指令”RQ 负责“收货地址预约”。WQE 是具体的“任务单”。网卡硬件通过读取 WQ 中的 WQE 来执行零拷贝的数据传输是实现高性能、低延迟网络通信的基础机制。二、wq环形缓冲区本质上wq是一个环形缓冲区。这个环形缓冲区也就是Ring Buffer, 形式上在内存中的排布不一定是环形的可能是线性分布的一块区域之所以叫做环形缓冲区是因为有头尾指针循环累加区别于队列或者堆栈而言的。这个东西一般用来通信比如Host驱动和设备端的固件需要交互消息就可以用环形缓冲区来处理名字千奇百怪可能叫做Mailbox可能叫做IPC。一张普通网卡常见的环形缓冲区就是数据包的环形缓冲区通过dma_alloc_coherent申请特点是物理地址连续且保证缓存一致性。rdma的wq包括sq、rq 是存放wqe的环形缓冲区这个东西就不适合用dma_alloc_coherent 来申请因为比较大。所以特点是虚拟地址连续但是物理地址不一定连续。下面讲讲wq的分配过程。三、建立wq的过程我们就以linux 7.0.0 rc2版本的代码来看看mlx5的rdma驱动程序是怎么申请一个wq的吧。先上一个总体的调用关系表libmlx5 (用户态) ibv_create_qp()│ ioctl: ucmd-buf_addr (用户WQ缓冲区虚拟地址)、ucmd-db_addr (doorbell页)▼mlx5_ib_create_qp() [ib/mlx5/qp.c]│├─► _create_user_qp() [qp.c:941]│ ││ ├─► ib_umem_get(dev, ucmd-buf_addr, buf_size, 0) [qp.c:1003]│ │ └─ pin_user_pages_fast(FOLL_LONGTERM) [core/umem.c:235]│ │ 锁定用户页 → 页的DMA地址存入 umem-sgt_append (sg_table)│ ││ ├─► mlx5_umem_find_best_quantized_pgoff() [qp.c:1009]│ │ 选最大页大小 page_size量化页内偏移 page_offset│ ││ ├─► ncont ib_umem_num_dma_blocks(umem, page_size) [qp.c:1017]│ │ 硬件需要的表项数│ ││ ├─► *in kvzalloc(create_qp_in ncont * 8) [qp.c:1022-1024]│ │ 分配 mailbox 命令缓冲区尾部预留 pas[] 数组│ ││ ├─► pas MLX5_ADDR_OF(create_qp_in, *in, pas) [qp.c:1033]│ ││ ├─► mlx5_ib_populate_pas(umem, page_size, pas, 0) [qp.c:1035]│ │ └─► rdma_umem_for_each_dma_block(umem, ...) [ib/mlx5/mem.c:45]│ │ *pas be64(页的DMA地址) ← 这一步就是生成MTT表项│ ││ └─ MLX5_SET(qpc, qpc, log_page_size / page_offset / [qp.c:1036-1040]│ uar_page / dbr_addr …) QP上下文字段│▼mlx5_qpc_create_qp() [ib/mlx5/qpc.c:246]│ MLX5_SET(create_qp_in, in, opcode, MLX5_CMD_OP_CREATE_QP) [qpc.c:252]│ 命令格式: [create_qp_in 头部][qpc][pas[0]..pas[ncont-1]]▼mlx5_cmd_exec(dev-mdev, in, inlen, out, outlen) [qpc.c:254]│ → mlx5_cmd_invoke → cmd_alloc_ent▼写命令门铃 DMA [eth/mlx5/core/cmd.c]│ 命令缓冲区地址写入命令门铃寄存器 (PCIe MMIO)│ 网卡固件 DMA 读走整个 create_qp_in含 pas[] 页表▼网卡固件 (HCA)├─ 在 ICM 中建立 QP context├─ 把 pas[] 复制/关联为该 QP 的 WQ 页表 (即硬件侧 MTT)└─ 返回 create_qp_out { qpn } [qpc.c:260]│▼create_resource_common() → 挂入 dev-qp_table (XArray, qpn→qp) [qpc.c:262]─── 运行时 ───用户态投递 WQE → mmio_write64 到 uar_page 指向的 BF/doorbell (WC BAR)硬件: QPN → QP context → WQ页表(来自pas[]) → DMA读WQE → 用lkey查MR的MTT → DMA读写数据2.1 第 1 步用户态传虚拟地址内核 pin 成 umem用户态创建qp的iverbs api是ibv_create_qp(), malloc 的缓冲区虚拟地址通过 ibv_create_qp 的 command 传下来ucmd-buf_addr陷入内核态后通过ib的钩子函数会调用mlx5_ib_create_qp()这个函数会调用_create_user_qp函数这里面的关键操作是ib_umem_get()这个函数会 pin 住这些页页列表存进 umem-sgt_appendscatter-gather table。2.2 页列表转成 MTT/KSM 项写入网卡自己的页表接下来在 mlx5_ib_populate_pas()mr.c中把 umem-sgt_append 里每个页的 DMA 地址IOVA经 IOMMU 翻译后的地址填进一个连续的 PAS 数组——这就是 MTTMemory Translation Table每项 页的物理/DMA 地址或 KSM大页模式描述 baselength。2.3 组装qpc的mailbox发给硬件qpc的c是context的意思也就是上下文qpc用于描述qp的完整硬件状态的核心数据结构。在驱动侧是一个庞大的结构体同时需要把这个结构体发给网卡硬件 发送的方式是通过mailbox。mlx5_qpc_create_qp组装 CREATE_QP 命令的 mailbox固件命令接口PCIe 门铃 DMA不是普通的 MMIO 寄存器写这个mailbox的结构体是struct mlx5_ifc_create_qp_in_bits { opcode, uid, op_mod; // 命令头 input_qpn, opt_param_mask, ece; struct mlx5_ifc_qpc_bits qpc; // QP上下文 (~0x600 bits) wq_umem_offset / wq_umem_id / wq_umem_valid; u8 pas[][0x40]; // 变长: WQ页表 };其中包含了两个重要成员一个是代表QP context 的mlx5_ifc_qpc_bits是一个很大的结构体这里不再赘述。另外一个是pas这个数组的每一项是一个8字节的大端地址内容是该缓冲区的一个页。所以pas的角色是把用户虚拟地址上一段连续的缓冲区翻译成硬件视角的物理页清单你也可以称之为页表。所以从这个mailbox也可以看出网卡硬件收到这个消息后需要在ICM也就是设备端的内存里“记住”的是建立QP context以及把pas数组复制为该QP的WQ页表。四、如何理解wq是个环形缓冲区从第三节的内容也可以看出wq实际的空间是一个pas页表也就是在物理地址上是分散的。可以这么理解pas 列出的页 环形缓冲区的全部物理空间所谓的环形其实是虚拟地址上的连续。如下图所示