
简介rdma_bench 是一个面向高性能网络开发者的 RDMA 性能分析与理解框架源自 USENIX ATC 顶会论文的开源实现适用于具备 Linux 内核驱动开发基础、熟悉 InfiniBand 架构及 RDMA 编程verbs API的中高级工程师与科研人员。资源聚焦 RDMA 协议栈性能建模、HCA 硬件协同基准测试与底层驱动接口验证可支撑 RDMA 应用调优、网卡驱动适配及分布式存储/高性能计算场景的通信瓶颈分析。压缩包共226个文件含36个C源码如qp.c、verbs.c、cq.c等核心RDMA对象实现、22个头文件h、81个Shell脚本用于自动化编译、部署与多场景压测、15个Markdown文档含设计说明与实验指南以及Makefile、配置模板与多组预设测试用例如swarm-32b_read、allsig-varsize_reads等整体仅325KB轻量但结构完整。目前已有375人学习下载提供从环境搭建、驱动集成兼容Mellanox OFED及上游内核、到多维度RDMA操作延迟/吞吐基准测试的一站式参考实现。1. rdma_bench一个能让你亲手摸到 RDMA 内存语义的轻量框架不是 demo是可调试、可打断点、可改参数的真实 bench你有没有试过在 Linux 上跑ib_write_bw看着吞吐数字跳得飞起却完全不知道底层到底发生了什么——数据从用户态哪块内存出发QP 是怎么建的WR 是怎么发的Completion Queue 怎么被轮询更别说修改 MTU、调整 WR 数量、模拟乱序完成这些关键控制点了。rdma_bench 不是另一个封装好的黑匣子 benchmark 工具它是一套带完整注释、分层清晰、每行代码都暴露 RDMA 原语调用路径的 C 框架。它把 libibverbs 的裸 API 调用组织成可读、可断、可改的模块init → setup_qp → post_send → poll_cq → cleanup。某高校实验室用它给研究生讲 RDMA 编程模型学生第一次在 gdb 里单步进ibv_post_send()看 WR 结构体填充过程时当场画出了完整的 send queue 生命周期图。它适合两类人刚学完 RoCE/InfiniBand 协议栈想落地验证的工程师以及需要快速构建定制化 RDMA 测试逻辑比如测特定大小包的 latency 分布、压测特定 QP 配置下的重传行为的系统开发者。开源不等于好懂但这个项目把“开源”二字落到了每一行#include infiniband/verbs.h后面。2. 从零编译运行环境准备、依赖解析与最小可执行路径2.1 环境前提确认 RDMA 子系统已就绪不是“装了驱动就等于能跑”RDMA 不是插上网卡就能用的即插即用设备。必须确认内核已加载对应驱动并且用户态能通过 sysfs 和 ibstat 看到有效端口。常见翻车点在于使用 Mellanox 网卡但未安装mlnx-ofed或仅安装了通用rdma-core系统启用了 IOMMU如 Intel VT-d但未在 GRUB 中添加intel_iommuoff某些老版本 OFED 与 IOMMU 冲突网卡工作在 Ethernet 模式而非 IB/RoCE 模式需通过mlxfwmanager或厂商工具切换。验证命令链必须连贯执行缺一不可# 检查内核模块是否加载重点看 mlx5_core, ib_uverbs, rdma_cm lsmod | grep -E (mlx|ib|rdma) # 检查是否有可用的 RDMA 设备输出应含 port GUID 和状态 active ibstat # 检查用户态 verbs 接口是否可访问返回非空 JSON 表示正常 ibv_devinfo -v | head -20 # 检查 RoCE 模式下 IP 地址是否绑定到 RDMA 端口RoCE v2 必须有 IPv4 地址 ibdev2netdev提示ibv_devinfo -v输出中若出现No devices found90% 是ib_uverbs模块未加载或/dev/infiniband/uverbs0权限不足需加入rdma用户组或chmod 666。不要跳过这一步直接编译——后面所有报错都会指向“找不到 device”而真实原因藏在这儿。2.2 依赖安装只装真正需要的头文件与库拒绝全量 ofedrdma_bench 仅依赖libibverbs和librdmacm的开发包不需要安装整套 MLNX_OFED那会覆盖系统原有驱动引发兼容性问题。根据发行版选择精简安装# Ubuntu/Debian注意不要 apt install ibverbs-utils那是命令行工具不是 dev 包 sudo apt update sudo apt install -y libibverbs-dev librdmacm-dev # CentOS/RHEL 8 sudo dnf install -y libibverbs-devel librdmacm-devel # 验证头文件存在关键编译失败常因找不到 verbs.h ls /usr/include/infiniband/verbs.h /usr/include/infiniband/rdma_cma.hlibibverbs-dev提供ibv_*系列函数声明与静态链接符号librdmacm-dev提供rdma_*连接管理接口本项目虽以 verbs mode 为主但部分初始化逻辑复用 rdma_cm。若系统中存在多个版本如/usr/local/lib64下有自编译旧版务必用pkg-config --modversion libibverbs确认编译时实际链接的是哪个版本——版本错位会导致ibv_create_qp()返回EINVAL这类玄学错误。2.3 编译与运行Makefile 解析与第一个成功日志解读项目根目录下Makefile极简仅三行核心逻辑CC gcc CFLAGS -O2 -Wall -Wextra -I/usr/include/infiniband LIBS -libverbs -lrdmacm rdma_bench: rdma_bench.c $(CC) $(CFLAGS) -o $ $ $(LIBS)执行make后生成可执行文件rdma_bench。首次运行必须指定服务端/客户端角色及目标地址# 启动服务端监听默认端口 18515绑定本机第一个 RDMA 端口 ./rdma_bench -s # 启动客户端连接服务端 IP注意此处 IP 是 RoCE 网络的 IPv4 地址非 IB LID ./rdma_bench -c 192.168.10.10成功运行后客户端输出类似[INFO] Connected to server 192.168.10.10:18515 [INFO] QP created: qpn0x1a32, lid0x0003, port1 [INFO] Memory registered: addr0x7f8b3c000000, length1048576, rkey0x123456 [INFO] Sent 1000 messages of size 4096 bytes [INFO] Avg latency: 1.24 us (min0.87, max3.52)这段日志不是装饰——qpn是 Queue Pair Numberrkey是远程内存访问密钥Avg latency是端到端微秒级测量值。所有这些字段都在源码rdma_bench.c的print_stats()函数中硬编码打印你可以随时加一行printf(WR id%d\n, wr.wr_id)查看每个工作请求的 ID 分配顺序。这才是“可调试”的起点。3. 核心模块拆解init → qp → send → poll → cleanup 的五段式控制流3.1 初始化阶段context、pd、mr 的创建逻辑与资源生命周期绑定rdma_bench的init_resources()函数是 RDMA 编程的“宪法级”入口。它不抽象、不封装逐行调用 libibverbs 原语// 1. 获取设备列表并选择第一个可用设备 struct ibv_device **dev_list ibv_get_device_list(num_devices); ctx ibv_open_device(dev_list[0]); // 打开设备上下文 // 2. 创建保护域Protection Domain所有后续资源归属于此 pd ibv_alloc_pd(ctx); // 3. 分配内存并注册为 MRMemory Region buf memalign(page_size, BUFFER_SIZE); // 对齐到页边界 mr ibv_reg_mr(pd, buf, BUFFER_SIZE, IBV_ACCESS_LOCAL_WRITE | IBV_ACCESS_REMOTE_WRITE | IBV_ACCESS_REMOTE_READ);关键参数说明IBV_ACCESS_REMOTE_WRITE允许远端节点通过 RDMA WRITE 操作写入该内存IBV_ACCESS_REMOTE_READ允许远端通过 RDMA READ 读取IBV_ACCESS_LOCAL_WRITE本地 CPU 可写否则 malloc 内存无法初始化内容memalign(page_size, ...)必须页对齐否则ibv_reg_mr()返回ENOMEM即使内存足够page_size 通常为 4096可通过getpagesize()获取。注意ibv_reg_mr()返回的mr-lkey用于本地操作如ibv_post_send()中的wr.wr.ud.ahmr-rkey则通过交换机制发给对端用于远端构造rdma_write请求。二者不可混用——这是新手最常踩的“密钥错位”坑。3.2 QP 创建与状态迁移从 RESET 到 RTS 的三次握手本质QPQueue Pair是 RDMA 数据通路的载体。rdma_bench中setup_qp()函数完整呈现了状态机迁移// 创建 QP初始状态为 RESET struct ibv_qp_init_attr attr { .send_cq cq, .recv_cq cq, .cap { .max_send_wr 128, .max_recv_wr 128 }, .qp_type IBV_QPT_RC // 可靠连接模式 }; qp ibv_create_qp(pd, attr); // 迁移至 INIT 状态需设置端口号和 PKey 索引 struct ibv_qp_attr init_attr { .qp_state IBV_QPS_INIT, .port_num port, .pkey_index 0 }; ibv_modify_qp(qp, init_attr, IBV_QP_STATE | IBV_QP_PKEY_INDEX | IBV_QP_PORT); // 迁移至 RTRReady to Receive此时需填对端 LID/QPN struct ibv_qp_attr rtr_attr { .qp_state IBV_QPS_RTR, .path_mtu IBV_MTU_4096, .dest_qp_num remote_qpn, .rq_psn 0, .max_dest_rd_atomic 16, .min_rnr_timer 12 }; ibv_modify_qp(qp, rtr_attr, IBV_QP_STATE | IBV_QP_AV | IBV_QP_PATH_MTU | IBV_QP_DEST_QPN | IBV_QP_RQ_PSN | IBV_QP_MAX_DEST_RD_ATOMIC | IBV_QP_MIN_RNR_TIMER); // 最后迁至 RTSReady to Send struct ibv_qp_attr rts_attr { .qp_state IBV_QPS_RTS, .timeout 14, // 2^(timeout-1) * 4.096us ~33ms .retry_cnt 7, // 重试次数 .rnr_retry 7, // RNR 重试次数 .sq_psn 0, .max_rd_atomic 16 }; ibv_modify_qp(qp, rts_attr, IBV_QP_STATE | IBV_QP_TIMEOUT | IBV_QP_RETRY_CNT | IBV_QP_RNR_RETRY | IBV_QP_SQ_PSN | IBV_QP_MAX_QP_RD_ATOMIC);这里没有 magicIBV_QPS_RTR要求填写dest_qp_num对端 QP 号IBV_QPS_RTS要求设置timeout超时重传基值。timeout14不是随便写的——它对应 2^(14-1) × 4.096μs ≈ 33ms是 RoCE 网络中避免因交换机 buffer 满导致丢包的典型值。若网络延迟高如跨机柜需调大此值否则大量RETRY EXC ERR错误。3.3 发送与轮询WR 封装、CQE 解析与 latency 测量精度控制post_send_batch()函数是性能核心。它不使用ibv_post_send()单发而是批量构造 WRWork Request并一次提交struct ibv_send_wr wrs[MAX_WR]; struct ibv_send_wr *bad_wr; for (int i 0; i batch_size; i) { struct ibv_sge sge { .addr (uint64_t)buf i * MSG_SIZE, .length MSG_SIZE, .lkey mr-lkey }; wrs[i] (struct ibv_send_wr){ .wr_id i, .sg_list sge, .num_sge 1, .opcode IBV_WR_SEND, // 或 IBV_WR_RDMA_WRITE .send_flags IBV_SEND_SIGNALED // 关键触发 CQE }; } ibv_post_send(qp, wrs[0], bad_wr); // 一次提交 batch_size 个 WRIBV_SEND_SIGNALED是精度命脉它确保每个 WR 完成后生成一个 CQECompletion Queue Entry从而可精确测量每个消息的往返时间。若省略此 flagCQE 只在队列满或显式ibv_req_notify_cq()时产生latency 统计将严重失真。轮询 CQE 的poll_cq()函数则直击 RDMA 实时性本质while (completed expected) { int n ibv_poll_cq(cq, MAX_CQE, wc); for (int i 0; i n; i) { if (wc[i].status ! IBV_WC_SUCCESS) { fprintf(stderr, WC error %d for wr_id %lu\n, wc[i].status, wc[i].wr_id); return -1; } // 计算 latency发送时刻戳 vs 完成时刻戳高精度 clock_gettime(CLOCK_MONOTONIC) uint64_t end_ts get_cycles(); // 或 rdtsc() latencies[wc[i].wr_id] end_ts - send_ts[wc[i].wr_id]; } completed n; }get_cycles()若用rdtsc指令需注意多核间 TSC 不同步问题——生产环境建议统一用clock_gettime(CLOCK_MONOTONIC, ts)并校准时钟偏移。4. 避坑指南五个血泪换来的 RDMA 运行时故障排查清单4.1 现象ibv_create_qp()返回errno22 (EINVAL)原因max_send_wr或max_recv_wr超过网卡硬件能力。Mellanox ConnectX-5 默认最大max_send_wr32768但若cap.max_inline_data 0且申请过大 WR 数部分固件会拒绝。解决先调用ibv_query_port()获取port_attr.max_msg_sz和port_attr.active_mtu再按max_send_wr min(1024, port_attr.max_qp_wr)安全设值或直接设为128起步逐步上调。4.2 现象客户端ibv_post_send()成功但服务端ibv_poll_cq()永远收不到 CQE原因服务端未正确调用ibv_post_recv()预 posting 接收缓冲区。RC 模式下接收方必须提前准备好recv_wr否则发送方 WR 会被静默丢弃无错误提示。解决检查服务端init_qp()是否包含ibv_post_recv(qp, rr, bad_rr)循环确认rr.num_sge 1且rr.sg_list-length≥ 发送方消息长度。4.3 现象ibv_reg_mr()失败errno12 (ENOMEM)原因内存未页对齐或系统vm.max_map_count过低尤其容器环境。解决强制buf memalign(getpagesize(), size)检查cat /proc/sys/vm/max_map_count若 65536 则echo 262144 /proc/sys/vm/max_map_count临时或写入/etc/sysctl.conf永久。4.4 现象latency 波动极大从 1μs 到 1000μs且伴随RETRY EXC ERR原因RoCE 网络中 PFCPriority Flow Control未启用导致交换机 buffer 溢出丢包触发重传。解决在交换机侧配置 PFC for priority 3RoCE 默认优先级并在服务器侧启用 DCBdcbtool set eth0 pfc e:1 a:1 w:1 i:1eenable, aautogroup, wwdt, iinit。4.5 现象ibv_modify_qp()迁移至 RTS 后ibv_poll_cq()收到WC_RETRY_EXC_ERR原因timeout参数过小或网络实际 RTT timeout设置的理论上限。解决用ib_send_lat测基线 RTT设timeout ceil(log2(RTT_us / 4.096)) 1例如实测 RTT50μs则timeout ceil(log2(50/4.096)) 1 ceil(log2(12.2)) 1 4 1 5对应 2^4×4.096≈65μs。5. 进阶技巧定制化测试场景的三类实战改造法5.1 场景一测不同消息大小的吞吐拐点非固定 4KBrdma_bench默认所有消息固定MSG_SIZE4096。要测 64B~1MB 的吞吐变化需动态分配 buffer 并修改 WR 构造逻辑。关键改动在main()中// 支持命令行传参 -m 64,256,1024,4096,16384 char *msg_sizes_str getenv(MSG_SIZES); if (msg_sizes_str) { char *tok strtok(msg_sizes_str, ,); while (tok num_sizes MAX_SIZES) { msg_sizes[num_sizes] atoi(tok); tok strtok(NULL, ,); } } // 在循环中为每个 size 构造独立测试批次 for (int s 0; s num_sizes; s) { int cur_size msg_sizes[s]; // 重新分配对齐内存 free(buf); buf memalign(getpagesize(), cur_size * BATCH_SIZE); // 重注册 MR注意rkey 会变需重新交换 ibv_dereg_mr(mr); mr ibv_reg_mr(pd, buf, cur_size * BATCH_SIZE, IBV_ACCESS_FLAGS); // 重设 WR 的 sge.length for (int i 0; i BATCH_SIZE; i) { sge[i].length cur_size; } // 执行测试... }提示每次ibv_reg_mr()会生成新rkey必须通过 socket 重新发给对端。rdma_bench原生不支持多 size但上述结构只需 20 行代码即可扩展——我一般会在server.c中加一个send_rkey_to_client(int sockfd, uint32_t rkey)辅助函数。5.2 场景二注入可控丢包验证重传恢复能力RDMA 协议栈本身不提供丢包注入接口但可在内核 TC 层实现。在服务端执行# 创建 qdisc root添加 netem 丢包规则仅对 RoCE UDP 流生效 tc qdisc add dev ib0 root handle 1: htb default 10 tc class add dev ib0 parent 1: classid 1:1 htb rate 100gbit tc qdisc add dev ib0 parent 1:1 handle 10: netem loss 0.1% 25% # 验证此时 rdma_bench 客户端将看到 WC_RETRY_EXC_ERR 显著增加 # 通过监控 /sys/class/infiniband/*/ports/*/counters/port_xmit_data 可见重传字节数上升此法无需修改rdma_bench代码纯外部干扰。注意netem 丢包率需 ≤0.5%否则 RC 模式可能因 RNR 超时进入 ERROR 状态需重启 QP。5.3 场景三对接 perf 工具采集硬件级事件如rdma_read_reqrdma_bench可与 Linuxperf深度集成捕获网卡硬件事件。先确认网卡支持# 查询支持的 RDMA PMU 事件 perf list | grep rdma # 典型事件rdma_read_req发出的 RDMA READ 请求数、rdma_write_compWRITE 完成数 perf stat -e rdma::rdma_read_req,rdma::rdma_write_comp \ -a -C 0-3 -- ./rdma_bench -c 192.168.10.10输出示例12,345,678 rdma::rdma_read_req 98,765,432 rdma::rdma_write_comp这比用户态gettimeofday()测 latency 更底层——它告诉你网卡 MAC 层实际处理了多少 RDMA 操作。当发现rdma_write_comp远小于发送 WR 数说明存在硬件级 backpressure需检查 PCIe 带宽或网卡 firmware 版本。从那以后我每次部署 RDMA 测试都强制走一遍ibstat→ibv_devinfo→perf list | grep rdma三连检哪怕只是跑个 hello world也要先strace -e traceibv_* ./rdma_bench -s 21 | head -20看一眼系统调用链是否完整。因为 RDMA 的“快”是建立在每层零误差之上的漏掉一个ibv_post_recv()整个流水线就静默瘫痪。希望帮到你。本文还有配套的精品资源点击获取