
简介本资源是一套面向C语言开发者与高性能网络编程学习者的RDMA实践代码包聚焦远程直接内存访问技术的底层C编程实现适用于高性能计算、云计算及低延迟通信场景的技术攻关与教学实践。压缩包共18个文件含8个核心C源码、3个头文件封装RDMA通用接口与结构体、3个Makefile支持多模块编译、3个说明类文本文件及1个README文档总大小仅19KB轻量精炼便于快速上手与源码级剖析。内容按功能分层组织为01_basic-client-server、02_read-write、03_file-transfer三个典型实验模块覆盖RDMA上下文初始化、队列对与完成队列管理、内存注册、Work Request构造与Completion处理等关键流程。已有271人学习下载提供从基础连接到文件传输的完整可运行示例辅以sequence.txt和results.txt等执行参考是理解libibverbs API调用链与RDMA通信模型不可多得的实操入口。1. 这不是“绕过内核”的玄学而是用rdma-server.c和rdma-client.c真正跑通一次零拷贝内存直写你可能已经看过 dozens 次“RDMA 绕过 TCP/IP 协议栈”“CPU 零参与”这类宣传语但真正卡住你的往往是ibv_create_qp()返回 -1、ibv_post_send()失败后wc.status IB_WC_RETRY_EXC_ERR、或者 client 端rdma_connect()成功却收不到 server 的IB_WC_RECV完成事件——这些都不是理论问题是rdma-common.c里资源初始化顺序、QP 状态迁移时机、以及ibv_modify_qp()中qp_attr.qp_state设置不当导致的硬伤。这个the-geek-in-the-corner-master_rdma_源码包不讲抽象模型只放能make ./server ./client一键跑通的 C 实现从01_basic-client-server的裸 socket 通信打底到02_read-write中用ibv_post_send()发起 RDMA WRITE再到03_file-transfer里把整个文件分块映射为 MR 并批量提交 WR。它面向的是刚配好 Mellanox ConnectX-5、装好libibverbs-dev、rdmacm-utils但ibstat能看到端口 up、ibping能通、ib_write_bw跑得动却始终无法让自己的rdma-client.c和rdma-server.c对上 QP 号的工程师。它不替代《RDMA Aware Programming User Manual》但它让你在gdb里单步rdma_common.c:rdma_create_qp()时一眼看懂qp_attr.port_num 1和qp_attr.path_mtu IB_MTU_4096的实际作用。2. 从ibv_context到ibv_qprdma-common.c如何构建可复用的 RDMA 资源基座rdma-common.c是整个示例项目的骨架它把 RDMA 初始化中重复度最高的逻辑封装成函数避免每个 demo 都重写ibv_open_device()、ibv_alloc_pd()、ibv_create_cq()。理解它等于拿到了打开所有 RDMA 示例的钥匙。它的核心不是炫技而是解决三个现实问题如何安全地发现本地 RDMA 设备、如何为不同用途send/recv/write分配合适的 CQ 深度、以及为什么ibv_reg_mr()必须在ibv_create_qp()之后才调用。2.1 设备发现与上下文初始化rdma_get_devices()的隐含约束rdma-common.c中rdma_get_devices()函数看似简单实则埋着关键前提struct ibv_device **rdma_get_devices(int *num_devices) { struct ibv_device **dev_list; dev_list ibv_get_device_list(num_devices); if (!dev_list || !(*num_devices)) { fprintf(stderr, No RDMA devices found\n); return NULL; } return dev_list; }提示ibv_get_device_list()返回的设备列表不保证按 PCI 地址排序也不保证dev_list[0]就是你ibstat看到的那个 port。实际部署中必须结合ibv_get_device_name(dev_list[i])和ibv_get_sysfs_path_by_name()检查/sys/class/infiniband/*/ports/*/gid_idx否则在多网卡服务器上极易绑定错设备。本示例默认使用dev_list[0]仅适用于单卡环境。拿到设备后ibv_open_device()创建ibv_context。这里的关键参数是ibv_context的async_fd—— 它是异步事件通知的文件描述符。rdma-common.c未启用该机制而是采用轮询ibv_poll_cq()这降低了复杂度但也意味着你不能依赖IB_EVENT_PORT_ACTIVE这类事件来触发后续流程。若需事件驱动必须显式调用ibv_async_event()并epoll_ctl()监听async_fd。2.2 内存注册与队列对创建rdma_create_qp()中的时序陷阱rdma_create_qp()是最易出错的函数。它内部执行三步ibv_create_cq()→ibv_create_qp()→ibv_modify_qp()。但ibv_create_qp()的qp_init_attr结构体中qp_init_attr.send_cq和qp_init_attr.recv_cq必须指向已创建的 CQ而qp_init_attr.cap.max_send_wr和qp_init_attr.cap.max_recv_wr决定了该 QP 能挂起多少未完成的 WR。示例中设为 10这是平衡吞吐与内存占用的常见起点qp_init_attr.send_cq cq; qp_init_attr.recv_cq cq; // 注意send/recv 共享同一 CQ 是简化做法 qp_init_attr.cap.max_send_wr 10; qp_init_attr.cap.max_recv_wr 10; qp_init_attr.cap.max_send_sge 1; // 单个 WR 最多 1 个 scatter-gather entry qp_init_attr.cap.max_recv_sge 1;注意max_send_wr不是“最大并发请求数”而是“发送队列深度”。若业务需连续发 100 个 WR必须确保前 10 个 WR 的 WC 已被ibv_poll_cq()消费否则ibv_post_send()会返回-1并置errno ENOMEM。02_read-write/rdma-server.c中while (1)循环里未做 WC 清理正是为演示这一阻塞行为。更隐蔽的陷阱在ibv_modify_qp()。QP 创建后处于IB_QPS_RESET状态必须依次迁移到INIT→RTRReady to Receive→RTSReady to Send。rdma-common.c的rdma_connect()函数中qp_attr.qp_state IB_QPS_INIT后立即调用ibv_modify_qp()但qp_attr.port_num必须与物理端口一致通常为 1且qp_attr.pkey_index 0—— 若你的子网管理器Subnet Manager未分配 pkey 或 pkey table 为空此调用将失败。验证方法ibstat -l查看Port字段ibquery -P查看 pkey 值。2.3 内存区域注册ibv_reg_mr()的地址对齐与权限粒度rdma-common.c中rdma_register_memory()封装了ibv_reg_mr()其参数mr_access_flags直接决定该内存能否被远程读写mr ibv_reg_mr(pd, buf, size, IBV_ACCESS_LOCAL_WRITE | IBV_ACCESS_REMOTE_READ | IBV_ACCESS_REMOTE_WRITE);IBV_ACCESS_LOCAL_WRITE允许本机 CPU 写入该内存必选否则memcpy()会 segfaultIBV_ACCESS_REMOTE_READ允许远端通过 RDMA READ 访问02_read-write中 client 读 server 内存用到IBV_ACCESS_REMOTE_WRITE允许远端通过 RDMA WRITE 写入02_read-write中 client 写 server 内存用到提示buf地址必须页对齐posix_memalign(buf, 4096, size)否则ibv_reg_mr()返回NULL。size也必须是页大小的整数倍或至少大于 4KB。03_file-transfer中对大文件分块处理每块都调用posix_memalign()分配独立 buffer正是为满足此约束。ibv_reg_mr()返回的mr-lkey和mr-rkey是关键凭证lkey用于本机ibv_post_send()构造 WR 时指定本地内存地址rkey必须通过rdma_cm或自定义协议如sequence.txt中的 handshake传递给远端远端构造 WR 时用它校验访问权限。rdma-common.h中struct rdma_connection显式包含uint32_t rkey字段就是为承载此值。3.rdma-server.c与rdma-client.c的状态机协同从IB_WC_RECV到IB_WC_SEND的完整闭环02_read-write目录下的rdma-server.c和rdma-client.c是理解 RDMA 通信模型的核心。它们不依赖rdma_cm而是手动管理 QP 状态和 WR 提交因此每一行ibv_post_recv()/ibv_post_send()都对应明确的硬件行为。这种“裸金属”写法暴露了 RDMA 的真实控制流没有“连接建立”概念只有 QP 状态迁移和 WR 提交-完成循环。3.1 Server 端ibv_post_recv()预填充接收队列与IB_WC_RECV触发机制rdma-server.c的主循环始于ibv_post_recv()预填充接收队列struct ibv_sge recv_sge; struct ibv_recv_wr recv_wr, *bad_recv_wr; recv_sge.addr (uintptr_t)buf; recv_sge.length BUFFER_SIZE; recv_sge.lkey mr-lkey; recv_wr.wr_id (uintptr_t)buf; recv_wr.next NULL; recv_wr.sg_list recv_sge; recv_wr.num_sge 1; if (ibv_post_recv(qp, recv_wr, bad_recv_wr)) { fprintf(stderr, Failed to post recv WR\n); return -1; }这段代码向 QP 的接收队列Recv Queue提交一个 WR告诉硬件“当有数据到来请直接写入buf完成后在 CQ 中生成IB_WC_RECV事件”。关键点在于recv_wr.wr_id是用户自定义 IDibv_poll_cq()返回的wc.wr_id即为此值用于关联完成事件与原始 bufferrecv_sge.lkey是本机 MR 的本地密钥仅用于本机校验不传给远端ibv_post_recv()可以批量提交多个 WR但02_read-write中每次只 post 1 个便于调试。Server 进入while (1)后持续调用ibv_poll_cq(cq, 1, wc)。当 client 执行ibv_post_send()发送数据后硬件检测到 QP 处于RTS状态且接收队列有空位便将数据 DMA 写入buf并生成wc.status IB_WC_SUCCESS的IB_WC_RECV事件。此时wc.wr_id等于之前recv_wr.wr_idwc.byte_len为实际接收字节数。3.2 Client 端ibv_post_send()构造 RDMA WRITE WR 与IB_WC_SEND验证Client 的核心是构造并提交 RDMA WRITE WRstruct ibv_sge send_sge; struct ibv_send_wr send_wr, *bad_send_wr; send_sge.addr (uintptr_t)buf; send_sge.length strlen(buf) 1; send_sge.lkey mr-lkey; send_wr.wr_id (uintptr_t)buf; send_wr.next NULL; send_wr.sg_list send_sge; send_wr.num_sge 1; send_wr.opcode IB_WR_SEND; // 或 IB_WR_RDMA_WRITE send_wr.send_flags IB_SEND_SIGNALED; send_wr.wr.ud.ah ah; // 若用 UD QP 才需 // RDMA WRITE 特有字段 send_wr.wr.ud.qp_num server_qpn; // 远端 QP 号 send_wr.wr.ud.qkey 0x11111111; // 远端 QKey仅 UD // 若为 RC QP则用 // send_wr.wr.ud.qp_num 0; // send_wr.wr.ud.qkey 0;注意02_read-write使用 RCReliable ConnectedQP因此send_wr.opcode应设为IB_WR_RDMA_WRITE而非IB_WR_SEND。IB_WR_SEND用于发送消息如 control packetIB_WR_RDMA_WRITE才是真正的内存直写。示例中rdma-client.c实际使用IB_WR_SEND这是为演示基础通信若要复现rdma-file-transfer的零拷贝必须切换为IB_WR_RDMA_WRITE并设置send_wr.wr.ud.qp_num和send_wr.wr.ud.qkey。send_wr.send_flags IB_SEND_SIGNALED表示此 WR 完成后必须生成IB_WC_SEND事件。若省略此 flagibv_poll_cq()可能永远收不到完成通知。Client 在ibv_post_send()后立即ibv_poll_cq()等待wc.status IB_WC_SUCCESS确认数据已发出。3.3 状态同步sequence.txt与results.txt揭示的握手协议本质03_file-transfer目录下sequence.txt并非日志而是明文协议规范1. Client sends file size to server via RDMA SEND 2. Server allocates buffer, registers MR, sends rkey and qp_num back 3. Client posts RDMA WRITE WRs with remote rkey and qp_num 4. Server polls CQ for IB_WC_RDMA_WRITE completionresults.txt则记录实测吞吐File size: 1048576 bytes Transfer time: 0.002341s Bandwidth: 4.48 Gbps这揭示了 RDMA 应用层协议的核心它不是“连接”而是“状态协商”。rdma-client.c必须先ibv_post_send()一个包含文件大小的 control messageserver 收到IB_WC_RECV后动态posix_memalign()分配 buffer、ibv_reg_mr()获取rkey再ibv_post_send()将rkey和qp_num回传。client 解析后才能构造真正的IB_WR_RDMA_WRITEWR。Makefile中CC gcc -O2 -g编译-g保证gdb可单步跟踪rdma_common.c:rdma_post_send()内部ibv_post_send()调用观察wr-opcode如何影响硬件行为。4.Makefile的工程化细节从CFLAGS到LDLIBS的链接链路解析01_basic-client-server/Makefile和02_read-write/Makefile表面简单实则定义了 RDMA 程序的编译契约。它不依赖cmake或autotools用纯make规则暴露底层依赖是理解“为什么必须链接-libverbs”的活教材。4.1 编译标志CFLAGS中的-I与-D如何影响头文件包含Makefile中CFLAGS -Wall -Wextra -O2 -g -I../common -D_GNU_SOURCE-I../common使#include rdma-common.h能找到rdma-common.h该头文件声明了struct rdma_connection和rdma_create_qp()等函数原型-D_GNU_SOURCE启用 GNU 扩展关键在于posix_memalign()声明位于stdlib.h但需此宏才可见。若省略编译报implicit declaration of function posix_memalign。02_read-write/Makefile额外添加-DUSE_RDMA_WRITE控制rdma-client.c中#ifdef USE_RDMA_WRITE分支演示IB_WR_RDMA_WRITE与IB_WR_SEND的切换。这种预编译宏是 RDMA 示例支持多模式的标准做法。4.2 链接库LDLIBS中-libverbs与-lrdmacm的分工LDLIBS -libverbs -lrdmacm-libverbs链接libibverbs提供ibv_open_device()、ibv_create_cq()、ibv_post_send()等 verbs API。这是 RDMA 程序的绝对依赖缺失则undefined reference to ibv_open_device-lrdmacm链接librdmacm提供rdma_create_id()、rdma_resolve_addr()等 Connection Manager API。01_basic-client-server未使用它仅用裸 verbs因此LDLIBS可简化为-libverbs03_file-transfer若改用rdma_cm才需此库。提示libibverbs本身不实现协议它是一个中间层向下调用kernel ib_uverbs驱动向上提供统一接口。ibstat、ibwrite等工具也链接此库。验证是否安装dpkg -l | grep libibverbsUbuntu或rpm -qa | grep libibverbsCentOS。4.3 目标规则all: server client与clean的原子性保障all: server client server: server.o rdma-common.o $(CC) $(CFLAGS) -o $ $^ $(LDLIBS) client: client.o rdma-common.o $(CC) $(CFLAGS) -o $ $^ $(LDLIBS) clean: rm -f server client *.o$^表示所有 prerequisitesserver.o rdma-common.o确保rdma-common.o被正确链接进两个可执行文件clean删除*.o但不删除rdma-common.o的依赖关系。若修改rdma-common.cmake会重新编译rdma-common.o再重新链接server和client符合增量构建逻辑。03_file-transfer/Makefile中CFLAGS -D_FILE_TRANSFER启用大文件分块逻辑。rdma-client.c中#ifdef _FILE_TRANSFER分支调用posix_memalign()为每块分配 buffer并循环ibv_post_send()提交 WR。Makefile的简洁性恰恰反衬出 RDMA 程序的模块化本质rdma-common.c是基础设施server.c/client.c是业务逻辑Makefile是粘合剂。5. 排错实战当ibv_poll_cq()返回 0 或wc.status ! IB_WC_SUCCESS时该查什么RDMA 开发中最常见的“静默失败”不是程序崩溃而是ibv_poll_cq()永远返回 0或返回wc.status为IB_WC_RETRY_EXC_ERR、IB_WC_LOC_QP_OP_ERR。这些错误码不告诉你具体原因只给出方向。本节提供一套基于the-geek-in-the-corner-master源码的快速定位路径每一步都对应rdma-common.c或rdma-server.c中的具体变量。5.1ibv_poll_cq()返回 0CQ 为空的五种可能ibv_poll_cq(cq, 1, wc)返回 0表示 CQ 中无完成事件。此时应按顺序检查检查项命令/代码说明CQ 是否被其他线程消费grep -r ibv_poll_cq .确认rdma-common.c中无其他地方调用ibv_poll_cq()避免 CQ 事件被偷走WR 是否成功提交if (ibv_post_send(qp, send_wr, bad_send_wr)) { perror(post_send); }ibv_post_send()返回非 0说明 WR 提交失败CQ 自然无事件QP 状态是否为 RTSibv_query_qp(qp, attr, sizeof(attr), IB_QP_STATE); printf(QP state: %d\n, attr.qp_state);若attr.qp_state ! IB_QPS_RTSibv_post_send()会失败但错误码不直接暴露接收队列是否有空间ibv_query_qp(qp, attr, sizeof(attr), IB_QP_RQ_PSN);attr.rq_psn为接收序列号若 server 未ibv_post_recv()接收队列为空client 的IB_WR_SEND会被丢弃网络连通性ibping -G gid -C portibping使用相同 GID 和端口验证底层 InfiniBand 链路是否 UP02_read-write/rdma-server.c中ibv_post_recv()仅在启动时调用一次若 client 发送多个包server 的接收队列会迅速耗尽。解决方案在while (1)循环中每次ibv_poll_cq()收到IB_WC_RECV后立即ibv_post_recv()新的 WR形成接收队列“永不断流”。5.2wc.status IB_WC_RETRY_EXC_ERR重试机制超限的根因分析此错误表示 client 端ibv_post_send()的 WR 在规定重试次数内未被 server 确认。常见原因Server QP 未进入 RTS 状态rdma-connect()中ibv_modify_qp()设置qp_attr.qp_state IB_QPS_RTS后未检查返回值。添加if (ibv_modify_qp(qp, qp_attr, IB_QP_STATE)) perror(modify QP to RTS);Remote QP Number 错误rdma-client.c中send_wr.wr.ud.qp_num必须等于 serveribv_query_qp()返回的attr.qp_num而非硬编码0x000001Path MTU 不匹配qp_attr.path_mtu IB_MTU_4096但物理链路 MTU 为 2048。ibstat查看Port的MTU字段iblinkinfo查看链路协商结果GID 配置错误rdma_common.c中ibv_create_ah()使用的ah_attr.grh.dgid必须与 serveribv_query_gid()获取的 GID 完全一致包括gid_index。5.3wc.status IB_WC_LOC_QP_OP_ERR本地 QP 操作非法的典型场景此错误表明 client 提交的 WR 本身违反 QP 配置。最常见于send_sge.length超出 MR 注册范围ibv_reg_mr()时size为 1024但send_sge.length 2048send_sge.lkey与 MR 不匹配ibv_reg_mr()返回的mr-lkey未赋给send_sge.lkey仍用 0send_wr.opcode与 QP 类型冲突RC QP 使用IB_WR_RDMA_READ但未设置send_wr.wr.ud.qp_num和send_wr.wr.ud.qkey。rdma-common.c中rdma_register_memory()返回mr后必须显式send_sge.lkey mr-lkey。示例中rdma-client.c直接使用mr-lkey但若你重构代码为多 MR 管理此处极易出错。提示ibv_dump_qp()可输出 QP 详细状态但需自行解析二进制输出。更实用的方法是在rdma_common.c:rdma_create_qp()后插入ibv_query_qp(qp, attr, sizeof(attr), IB_QP_CAP);打印attr.cap.max_send_wr和attr.cap.max_recv_wr确认与ibv_post_send()/ibv_post_recv()的调用频次匹配。本文还有配套的精品资源点击获取