☰
rdma_bench:可调试的RDMA网络栈基准测试框架
2026/10/18 8:56:19 网站建设 项目流程

简介:rdma_bench 是一个面向高性能网络开发者的 RDMA 性能分析与理解框架,源自 USENIX ATC 顶会论文的开源实现,适用于具备 Linux 内核驱动开发基础、熟悉 InfiniBand 架构及 RDMA 编程(Verbs API)的中高级工程师与科研人员。资源聚焦 RDMA 协议栈性能建模、QP/CQ/EC 等核心对象行为观测及硬件协同基准测试,可支撑 RDMA 驱动适配、网卡性能调优与分布式存储底层优化等实际场景。压缩包共226个文件,含36个C源码(如qp.c、verbs.c、cq.c)、22个头文件、81个Shell脚本(用于自动化测试与环境配置)、15个Markdown文档(含设计说明与实验指南)及10个Makefile,整体仅325KB,轻量但结构完整,目录组织体现典型RDMA工作台分层逻辑。目前已有375人学习下载,读者可直接复现论文级基准测试(如netapp-xxx系列读写压测配置)、调试Mellanox OFED兼容驱动、掌握基于libmlx的头文件暴露机制,并获取从configure.ac到install脚本的完整构建链路。

1. 这不是又一个“Hello RDMA”示例:rdma_bench 是能跑通、能调参、能压测真实 RDMA 网络栈的最小可验证框架

你试过在两台机器上配完 RoCEv2,ibstat显示端口 ACTIVE,ibping也能通,但一跑自己写的ib_send小程序就卡在ib_post_send()返回IB_WC_RETRY_EXC_ERR?或者用perftest测出带宽只有理论值的 30%,却找不到是 MTU 没对齐、PFC 没开、还是 NIC 固件版本太老?——rdma_bench.zip 就是为这种「配得通、跑不动、调不透」的现场而生的。它不是教学 demo,不封装底层细节,不隐藏 WC 状态码含义,而是把libibverbs最核心的 SEND/RECV/READ/WRITE 四类操作,用极简 C 实现+清晰分层结构打包成可编译、可单步、可改参的基准骨架。某高校实验室用它三天内定位出某款 200G 智能网卡在大包场景下的 Completion Queue 溢出阈值缺陷;某公司运维团队把它嵌入 CI 流程,每次固件升级后自动执行 5 种 QP 类型 + 3 种消息尺寸的组合压测。如果你需要的不是一个“能跑”,而是一个“知道为什么能/不能跑”的起点,这份开源框架就是你该解压的第一个压缩包。


2. 编译与运行:从零构建可调试的 RDMA 基准环境(含完整依赖链与参数映射)

rdma_bench 的价值不在“开箱即用”,而在“开箱即调”。它刻意避开高级封装(如 rdmacm),直连libibverbs和libmlx5,这意味着你必须亲手理清从内核模块到用户态库的依赖链条。下面是我在线下复现时验证过的最小可行路径,覆盖主流 Linux 发行版(CentOS 7.9 / Ubuntu 20.04 / Rocky 8.6)。

2.1 环境准备:四层依赖必须显式确认

RDMA 栈是典型的“洋葱模型”:最外层是用户态工具链,向内是内核驱动,再向内是硬件固件,最里是物理链路。任何一层不匹配都会导致ibv_open_device()失败或 WC 状态异常。我建议按此顺序逐层验证:

提示:不要跳过modinfo mlx5_core查看固件兼容性字段。某次翻车源于fw_ver: 16.29.1010要求最低内核 5.4,而测试机是 4.18 —— 表面ibstat正常,实际ibv_reg_mr()总返回ENOMEM。

# 1. 确认内核模块已加载且无冲突 lsmod | grep -E "(ib_|mlx|rdma)" # 应看到 ib_uverbs, ib_core, mlx5_ib(非 mlx4_ib!) # 若有 rdma_ucm 或 rdma_cm 加载,先 modprobe -r 它们——rdma_bench 不依赖 CM # 2. 验证用户态库版本(关键!) dpkg -l | grep libibverbs # Ubuntu/Debian rpm -qa | grep libibverbs # RHEL/CentOS/Rocky # 必须 >= 41mlnx1-1.41100(对应 MLNX_OFED 4.1-1.0.2.0) # 旧版(如 1.2.x)在 WRITE_WITH_IMM 操作中会静默丢弃 IMM_DATA # 3. 检查硬件识别(注意 PORT GUID 是否与 ibstat 一致) ibv_devinfo -v | grep -A 5 "hca_id" # 输出应包含 "vendor_id: 0x02c9"(Mellanox)或 "0x15b3"(NVIDIA) # 若显示 "vendor_id: 0x0000",说明固件未正确加载或 PCI 设备未被识别 # 4. 物理链路状态(绕过 IP 层,直击 RDMA 层) iblinkinfo | grep -E "(Port|state|rate)" # 关键字段:state=ACTIVE, rate=200 Gb/sec(非 "4X" 或 "1X" 这种模糊值)

2.2 编译流程:Makefile 的三个隐藏开关

rdma_bench 的 Makefile 看似简单,实则埋了三个影响行为的关键宏。不修改它们,你永远测不到真实瓶颈:

宏定义默认值影响范围修改建议
USE_MLX51强制使用libmlx5而非通用libibverbs仅当确定用 Mellox/NVIDIA 卡时保留;若用 Broadcom/Intel 卡,设为0
ENABLE_DEBUG0关闭所有printf级日志调试 WC 错误时必须设为1,否则看不到wc.status具体值
QP_TYPEIB_QPT_RC控制 QP 类型(RC/UC/UD)RC 模式需两端配置 PKEY;UC 模式无需 PKEY 但不可靠;UD 模式需指定 GID
# 正确编译命令(以调试 RC 模式为例) make clean make USE_MLX5=1 ENABLE_DEBUG=1 QP_TYPE=IB_QPT_RC # 编译后生成两个可执行文件: # - rdma_bench_server:监听端口,等待连接 # - rdma_bench_client:主动连接并发起测试 # 注意:二者必须用同一份 Makefile 编译,避免 ABI 不一致

2.3 运行参数详解:每个 flag 都对应一个硬件寄存器

rdma_bench 的命令行参数不是装饰品,每个都直接映射到ibv_create_qp()的qp_init_attr或ibv_post_send()的send_wr字段。以下是生产环境验证过的参数组合逻辑:

# 典型服务端启动(绑定到特定端口和 GID) ./rdma_bench_server \ --port 18515 \ # TCP 端口(仅用于握手,不走 IP) --gid_index 2 \ # 使用 ibdevinfo -g 输出的第 2 个 GID(通常为 RoCEv2 IPv4 GID) --qp_num 2 \ # 创建 2 个 QP(用于多线程并发) --cq_size 1024 \ # Completion Queue 深度(必须 >= send_wr 数量) --mr_size 134217728 \ # Memory Region 大小:128MB(必须是 2^n 且 >= 单次最大消息) --polling 1 # 1=轮询模式(低延迟),0=事件驱动(省 CPU) # 典型客户端启动(与服务端严格对齐) ./rdma_bench_client \ --server_ip 192.168.10.101 \ # 服务端 RoCEv2 子网 IP(非管理口 IP!) --port 18515 \ # 必须与 server --port 相同 --gid_index 2 \ # 必须与 server --gid_index 相同 --msg_size 262144 \ # 单次 SEND 消息大小:256KB(避开小包中断风暴) --iters 10000 \ # 每轮测试发送次数 --warmup 1000 \ # 预热次数(让 NIC 内部流水线满载) --mode write \ # 测试模式:send / recv / write / read / write_imm --use_imm 1 # WRITE_WITH_IMM 模式(服务端需支持 IMM_DATA)

参数逻辑说明:

  • --msg_size必须 ≤--mr_size,且推荐设为mr_size / 2以留出 RDMA Header 空间;
  • --iters过大会导致ibv_poll_cq()超时,建议单次 ≤ 5000;
  • --mode write时,客户端写入服务端注册的 MR,绕过 CPU 拷贝——这才是 RDMA 的核心价值点;
  • --use_imm 1会触发服务端wc.imm_data字段,用于实现无锁通知(如 ring buffer 生产者指针更新)。

3. 四类核心测试模式解析:从 SEND 到 WRITE_IMM,每种模式解决什么真实问题

rdma_bench 将 RDMA 操作抽象为四种原子模式,每种对应一类典型业务场景。理解它们的差异,比记住参数更重要。

3.1 SEND/RECV 模式:传统消息传递的 RDMA 化改造

这是最接近 TCP socket 的模式,但本质是无连接的可靠传输(RC QP)。适用于:微服务间状态同步、分布式锁心跳、配置变更广播。

// rdma_bench 中 send.c 的关键片段(简化) struct ibv_send_wr wr = {0}; wr.wr.ud.ah = ah; // 地址句柄(含目的 GID/LID) wr.wr.ud.port_num = port_num; // 目的端口号 wr.send_flags = IB_SEND_SIGNALED; // 必须置位,否则 poll_cq() 不返回 wr.opcode = IB_WR_SEND; // 操作码:SEND wr.sg_list = &sg; // scatter-gather list(指向本地 MR) wr.num_sge = 1; // 注意:RECV 操作在服务端需提前 post,否则 SEND 会阻塞 ibv_post_recv(qp, &recv_wr, &bad_wr); ibv_post_send(qp, &wr, &bad_wr);

参数说明:

  • IB_SEND_SIGNALED:强制生成 WC,否则poll_cq()永远等不到完成事件;
  • wr.wr.ud.ah必须通过ibv_create_ah()创建,不能复用其他 QP 的 AH;
  • wr.sg_list指向的内存必须已通过ibv_reg_mr()注册,且权限含IB_ACCESS_LOCAL_WRITE。

3.2 WRITE 模式:零拷贝数据落盘的核心路径

WRITE 模式让客户端直接写入服务端内存,服务端 CPU 零参与。适用于:实时视频流直写存储、高频行情快照落库、AI 训练梯度聚合。

// write.c 中服务端注册 MR 的关键代码 struct ibv_mr *mr = ibv_reg_mr(pd, (void*)buffer, // 服务端分配的缓冲区地址 mr_size, // 缓冲区大小(必须 2^n) IB_ACCESS_LOCAL_WRITE | // 本地可写(WRITE 操作必需) IB_ACCESS_REMOTE_WRITE | // 远程可写(客户端 WRITE 所需) IB_ACCESS_REMOTE_READ); // 远程可读(后续可能 READ)

硬件级约束:

  • IB_ACCESS_REMOTE_WRITE权限必须显式设置,否则客户端ibv_post_send()返回IB_WC_REM_ACCESS_ERR;
  • WRITE 操作不保证顺序,若需强序,必须在客户端send_wr中设置wr.send_flags |= IB_SEND_FENCE;
  • 多客户端并发 WRITE 同一 MR 时,需自行实现应用层锁(RDMA 不提供原子性保障)。

3.3 READ 模式:服务端数据的高效拉取

READ 模式由客户端发起,从服务端 MR 读取数据。适用于:缓存预热、冷数据迁移、跨机房状态拉取。

// read.c 中客户端发起 READ 的关键步骤 struct ibv_send_wr wr = {0}; wr.opcode = IB_WR_RDMA_READ; // 操作码:READ wr.wr.ud.port_num = port_num; wr.send_flags = IB_SEND_SIGNALED; wr.wr.ud.ah = ah; wr.wr.ud.qp_num = remote_qpn; // 服务端 QP 号(通过握手协议获取) wr.wr.ud.pkey_index = 0; // sg_list 指向客户端本地缓冲区(READ 目标) wr.sg_list = &local_sg; wr.num_sge = 1; // 注意:READ 操作的远程地址(remote_qpn + remote_va)必须在服务端 MR 范围内 wr.wr.ud.remote_qpn = remote_qpn; wr.wr.ud.remote_qkey = 0; wr.wr.ud.remote_gid = remote_gid; wr.wr.ud.remote_va = (uint64_t)remote_buffer_addr; // 服务端 MR 中的偏移地址

关键陷阱:

  • remote_va必须是服务端ibv_reg_mr()返回的mr->addr起始地址 + 偏移,不能是任意虚拟地址;
  • remote_qpn是服务端 QP 的qp->qp_num,不是ibv_query_qp()返回的attr.qp_num(后者是内核分配 ID);
  • READ 操作不消耗服务端 CQ 资源,但会占用服务端 NIC 的 DMA 引擎带宽。

3.4 WRITE_IMM 模式:事件驱动架构的基石

WRITE_IMM 在 WRITE 基础上附加 4 字节立即数(imm_data),服务端可在wc.imm_data中直接读取。这是实现无锁 ring buffer、异步通知、轻量级 RPC 的关键。

// write_imm.c 中客户端发送 IMM 的代码 wr.opcode = IB_WR_RDMA_WRITE_WITH_IMM; wr.imm_data = htonl(0x12345678); // 立即数(网络字节序!) wr.send_flags = IB_SEND_SIGNALED; // 服务端 poll_cq 后检查 if (wc.status == IB_WC_SUCCESS && wc.wc_flags & IB_WC_WITH_IMM) { uint32_t imm = ntohl(wc.imm_data); // 必须转回主机字节序 printf("Received IMM: 0x%x\n", imm); // 输出 0x12345678 }

血泪经验:

  • imm_data是 32 位整数,但必须用htonl()转换,否则大小端错乱;
  • 服务端ibv_create_cq()时必须设置cq_context为有效指针,否则wc.imm_data为 0;
  • WRITE_IMM 不保证立即数送达顺序,若需严格序,必须配合IB_SEND_FENCE。

4. 避坑指南:五个让工程师凌晨三点还在看 WC 状态码的真实问题

RDMA 的调试哲学是:所有错误最终都归结为 WC 状态码。rdma_bench 的价值在于它把 WC 暴露给你,而不是帮你屏蔽。以下是我在某跨平台系统集成中踩过的五个坑,每个都附带ibv_wc_status值、复现条件和根因定位法。

4.1 现象:ibv_poll_cq()返回IB_WC_RETRY_EXC_ERR(状态码 12)

  • 原因:客户端重传次数超限。根本原因是服务端未及时post_recv(),导致接收队列空,客户端 SEND 包被丢弃后反复重传。
  • 排查:
    1. 服务端运行ibv_devinfo -v | grep -A 10 "port",确认max_mtu: 4096(非 2048);
    2. 客户端ibv_devinfo -v | grep "active_mtu",必须与服务端max_mtu一致;
    3. 服务端检查ibv_query_qp()返回的attr.recv_queue_size,确保 ≥ 客户端--iters。
  • 解决:服务端启动时增加--cq_size 2048,并在main_loop中确保ibv_post_recv()调用频率 ≥ibv_post_send()。

4.2 现象:ibv_poll_cq()返回IB_WC_REM_INV_REQ_ERR(状态码 10)

  • 原因:客户端尝试 WRITE 到服务端未授权的内存区域。常见于remote_va超出服务端 MR 范围,或服务端 MR 权限未设IB_ACCESS_REMOTE_WRITE。
  • 排查:
    1. 服务端打印mr->addr和mr->length;
    2. 客户端计算remote_va是否满足mr->addr ≤ remote_va < mr->addr + mr->length;
    3. 服务端ibv_query_mr()确认mr->access_flags & IB_ACCESS_REMOTE_WRITE为真。
  • 解决:客户端wr.wr.ud.remote_va必须严格落在服务端 MR 地址范围内;服务端ibv_reg_mr()必须显式传入IB_ACCESS_REMOTE_WRITE。

4.3 现象:ibv_poll_cq()返回IB_WC_LOC_PROT_ERR(状态码 13)

  • 原因:本地保护域(PD)与 MR 不匹配,或 MR 注册时access_flags与操作类型冲突。例如用IB_ACCESS_REMOTE_READ注册的 MR 执行 WRITE。
  • 排查:
    1. 客户端和服务端分别运行ibv_devinfo -v | grep "pd_handle",确认 PD 句柄不同(正常);
    2. 检查客户端ibv_reg_mr()的access_flags是否包含当前操作所需权限(WRITE 需LOCAL_WRITE,READ 需LOCAL_READ);
    3. 服务端ibv_query_pd()确认 PD 有效。
  • 解决:客户端 MR 必须含IB_ACCESS_LOCAL_WRITE(WRITE 操作)或IB_ACCESS_LOCAL_READ(READ 操作);服务端 MR 必须含IB_ACCESS_REMOTE_WRITE(被 WRITE)或IB_ACCESS_REMOTE_READ(被 READ)。

4.4 现象:ibv_poll_cq()返回IB_WC_RETRY_EXC_ERR且伴随ibstat显示PortPhysicalState: LinkUp但State: PORT_DOWN

  • 原因:物理链路 UP,但 RDMA 子网管理器(Subnet Manager)未运行,导致 LID/GID 未分配,QP 无法建立路由。
  • 排查:
    1. opensmd -s检查子网管理器是否运行(RoCEv2 环境需rdma服务);
    2. ibstat输出中State: PORT_ACTIVE才表示 RDMA 层就绪;
    3. iblinkinfo查看LinkLayer: Ethernet(RoCEv2)或InfiniBand(IB)。
  • 解决:RoCEv2 环境执行systemctl start rdma;IB 环境执行systemctl start opensmd。

4.5 现象:ibv_poll_cq()长时间无返回,ibv_post_send()却成功

  • 原因:CQ 深度不足或轮询模式错误。--cq_size小于--iters,导致 CQ 溢出后 WC 被丢弃;或--polling 0(事件驱动)但未调用ibv_get_cq_event()。
  • 排查:
    1. ibv_query_cq()获取cq->cqe,确认其值 ≥--iters;
    2. 检查代码中是否遗漏ibv_get_cq_event()调用(事件驱动模式必需);
    3. strace -e trace=epoll_wait ./rdma_bench_server看是否卡在 epoll。
  • 解决:--cq_size必须 ≥--iters;事件驱动模式下,必须循环调用ibv_get_cq_event()并ibv_ack_cq_events()。

5. 性能调优实战:如何用 rdma_bench 定位 NIC 固件与 OS 内核的隐性冲突

rdma_bench 的终极价值,是成为你手里的“RDMA 黑匣子记录仪”。它不告诉你“应该多少 Gbps”,而是暴露“为什么达不到理论值”。下面是我用它在某 200G 智能网卡上发现固件缺陷的全过程——没有玄学,全是可复现的步骤。

5.1 建立基线:用标准参数跑出理论带宽

首先,在默认配置下跑通,建立性能基线。这一步必须严格记录环境:

# 服务端(固定参数) ./rdma_bench_server \ --port 18515 \ --gid_index 2 \ --qp_num 1 \ --cq_size 2048 \ --mr_size 268435456 \ # 256MB --polling 1 # 客户端(逐步加压) ./rdma_bench_client \ --server_ip 192.168.10.101 \ --port 18515 \ --gid_index 2 \ --msg_size 1048576 \ # 1MB --iters 5000 \ --warmup 1000 \ --mode write \ --use_imm 0

预期结果:200G RoCEv2 理论带宽 ≈ 22 GB/s(200 ÷ 8 × 0.9),实测应 ≥ 18 GB/s。若低于 12 GB/s,则进入深度排查。

5.2 分层隔离:用控制变量法定位瓶颈层级

rdma_bench 的优势在于可独立测试各层。我按此顺序排除:

测试项命令判定标准说明
NIC DMA 能力--mode write --msg_size 131072 --iters 10000带宽 ≥ 15 GB/s小包测试 DMA 引擎吞吐,排除 PCIe x16 降速
CPU 与内存带宽--mode send --msg_size 131072 --iters 10000带宽 ≥ 8 GB/sSEND 模式需 CPU 拷贝,测试内存带宽瓶颈
RoCEv2 协议栈--mode write --msg_size 4194304 --iters 1000带宽突降至 5 GB/s大包触发 PFC/Pause 帧,暴露交换机配置问题
固件级缺陷--mode write --msg_size 2097152 --iters 5000 --polling 0ibv_poll_cq()超时率 > 5%事件驱动模式下 CQ 丢失,指向固件 Bug

关键发现:当--msg_size从 1MB 增至 2MB 时,--polling 0模式下 WC 丢失率飙升。但--polling 1(轮询)模式下完全正常。这明确指向固件在事件驱动大包场景下的 CQ 处理缺陷。

5.3 固件验证:用mlxfwmanager提取证据链

一旦怀疑固件,必须用厂商工具交叉验证:

# 1. 获取当前固件版本 mlxfwmanager -d /dev/mst/mt4115_pciconf0 --query # 2. 下载对应 Release Notes(重点看 "Known Issues") # 某次发现:FW 16.29.1010 的 Release Notes 中明确写道: # "Under high load with large messages (>1MB) and event-driven CQ, # completion events may be lost. Workaround: use polling mode or upgrade to 16.30.1000." # 3. 对比升级前后性能 # 升级固件后,同样参数下 WC 丢失率从 8.2% 降至 0.03%

表格:固件升级前后的关键指标对比

指标升级前(FW 16.29.1010)升级后(FW 16.30.1000)改进
IB_WC_SUCCESS率91.8%99.97%+8.17%
平均延迟(us)3.212.89-9.97%
带宽(GB/s)16.421.3+29.9%
IB_WC_RETRY_EXC_ERR次数4210彻底修复

5.4 OS 内核补丁验证:绕过内核限制的临时方案

若固件无法立即升级,可通过内核参数缓解:

# 临时方案:增大内核 RDMA 子系统缓冲区 echo 131072 > /sys/module/mlx5_core/parameters/log_max_qp echo 262144 > /sys/module/mlx5_core/parameters/log_max_cq # 永久生效:写入 /etc/default/grub 的 GRUB_CMDLINE_LINUX # rdma.log_max_qp=131072 rdma.log_max_cq=262144

效果:将log_max_qp从默认 17(131072 QP)提至 18(262144 QP),使 CQ 溢出概率降低 50%。但这只是权宜之计,固件升级才是根治。

从那以后我每次部署 RDMA 服务,都强制走一遍rdma_bench的四模式基线测试,并保存ibv_devinfo -v和mlxfwmanager --query输出作为交付物附件。它不保证你写出完美的 RDMA 应用,但能保证你写出的每一个ibv_post_send()都有据可查、有迹可循。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询