news 2026/10/21 2:00:57

rdma-core 实战指南:从零到微秒级延迟的 RDMA 应用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
rdma-core 实战指南:从零到微秒级延迟的 RDMA 应用

rdma-core 实战指南:从零到微秒级延迟的 RDMA 应用

【免费下载链接】rdma-coreRDMA core userspace libraries and daemons项目地址: https://gitcode.com/GitHub_Trending/rd/rdma-core

写高性能网络应用时,TCP 的延迟和多次内存拷贝迟早会撞上墙;问题往往不在业务代码,而在数据路径本身。rdma-core 解决的就是这件事:RDMA 应用开发所需的 verbs 接口、设备驱动、连接管理,它全做进了用户态,数据路径上不再有内核协议栈这一环。

先搞懂它凭什么快

  • 零拷贝:注册过的内存由网卡 DMA 直接读写,数据不再经过内核缓冲区中转。
  • 内核旁路:收发流程整体在用户态完成,每次操作省掉进出内核的上下文切换。
  • 微秒级延迟:绕开协议栈后,端到端延迟通常能进微秒区间。
  • 高带宽:百 G 以上线速吞吐由硬件直接兑现,软件只负责下发描述符。

三个组件,分工明确

  • libibverbs/:verbs 核心库,设备枚举、内存注册、队列操作都在这层完成,是你大部分代码会调用的接口。
  • librdmacm/:连接管理层,提供类 socket 的建连接口,不想自己交换 QP 参数时用它。
  • providers/:各硬件的用户态驱动,mlx5、irdma、efa 各占一个子目录,按设备厂商加载。

API 细节查 Documentation/ 里的手册页即可。

一条链路跑通第一笔 RDMA WRITE

完整链路是:设备发现 → 上下文与保护域 → 注册内存 → 投递数据 → CQ 确认。下面按顺序走一遍。

1. 发现设备并打开上下文

int num = 0; struct ibv_device **list = ibv_get_device_list(&num); if (!list || num == 0) return 1; struct ibv_context *ctx = ibv_open_device(list[0]); if (!ctx) return 1;

ibv_get_device_list 枚举系统里所有 RDMA 设备,ibv_open_device 打开其中一个,拿到的 ctx 是后续所有操作的入口。

2. 建保护域,注册缓冲区

struct ibv_pd *pd = ibv_alloc_pd(ctx); void *buf = malloc(4096); struct ibv_mr *mr = ibv_reg_mr(pd, buf, 4096, IBV_ACCESS_LOCAL_WRITE | IBV_ACCESS_REMOTE_READ | IBV_ACCESS_REMOTE_WRITE); if (!pd || !mr) return 1;

ibv_alloc_pd 分配保护域,内存与队列都挂在它下面做隔离;ibv_reg_mr 把 buf 注册进驱动,返回的 mr 带着 lkey 和 rkey——前者本地用,后者必须发给对端,对方才能单边访问这块内存。

3. 建队列对,投递 WRITE

struct ibv_cq *cq = ibv_create_cq(ctx, 16, NULL, NULL, 0); struct ibv_qp_init_attr qattr = { .qp_type = IBV_QPT_RC, .send_cq = cq, .recv_cq = cq, .cap = { .max_send_wr = 16, .max_send_sge = 1 }, }; struct ibv_qp *qp = ibv_create_qp(pd, &qattr);

ibv_create_cq 建完成队列,硬件把每个操作的完成状态都扔进这里;ibv_create_qp 在 pd 下建出队列对。注意这里 qp 还处于 RESET 状态,必须先走完状态机,完整写法在下一节的 PingPong 案例里拆。

struct ibv_sge sge = { .addr = (uintptr_t)buf, .length = 4096, .lkey = mr->lkey }; struct ibv_send_wr wr = { .opcode = IBV_WR_RDMA_WRITE, .sg_list = &sge, .num_sge = 1, .wr.rdma = { .remote_addr = REMOTE_ADDR, .rkey = REMOTE_RKEY }, }; struct ibv_send_wr *bad; ibv_post_send(qp, &wr, &bad);

这一步的 purpose 是把 WRITE 写进发送队列:ibv_post_send 只是把描述符交给硬件就返回,数据是否真的落地,还要看完成队列。

4. 从 CQ 确认完成

struct ibv_wc wc; while (!ibv_poll_cq(cq, 1, &wc)) ; if (wc.status != IBV_WC_SUCCESS) return 1;

ibv_poll_cq 从 CQ 取完成事件,wc.status 是这笔操作最终的判决书。到这一步,一笔 RDMA WRITE 的完整生命周期就走完了。

RC PingPong:每一步为什么存在

仓库里最经典的样例是 libibverbs/examples/ 下的 rc_pingpong.c,它演示了完整的点对点收发。六个步骤,一个都省不掉:

  1. TCP 交换连接参数。RDMA 设备之间没有"握手"这回事,QP 号、PSN、LID/GID 必须靠带外通道传递,样例直接用一条 TCP 连接传一个结构体。
  2. 建 QP 并交换信息。双方各自建出 RC 队列对,把自己那半(qpn、psn、lid、gid)发给对方——对方不知道你的地址,就永远没法回包。
  3. QP 状态机推到 RTS。先改到 RTR(可接收)再改到 RTS(可发送);RTR 阶段填入对端地址,RTS 阶段填入自己的发送序列号。跳过任何一步,第一个包都发不出去。
  4. 注册内存并互发 rkey。RC 的 RDMA WRITE/READ 是单边操作,发起方网卡直接改对端内存,对端 CPU 全程不参与——前提是 rkey 已经交换过。
  5. 投递数据。ping 端 post 一笔 WRITE,数据直接出现在 pong 端缓冲区里,不需要事先挂接收请求,这是单边操作和 SEND/RECV 的本质区别。
  6. CQ 确认。双方从完成队列拿到成功状态,流程收尾。

想换成无连接或双向语义,看同目录的 ud_pingpong.c、uc_pingpong.c。

调优清单:先做收益最大的

按收益从高到低排:

  1. 批量投递工作请求。ibv_post_send 每次调用都要敲一次 doorbell,把多个 WR 串成链表一次投递,doorbell 开销摊薄到 1/N,高吞吐场景这是第一刀。
  2. 非信号化操作。WR 上不开 IBV_SEND_SIGNALED,CQ 就不记录它;每几十个操作打一个信号,完成队列压力降一个数量级。
  3. 缓冲区复用。ibv_reg_mr 要锁页,注册/注销都很贵;启动时注册一批大 buffer,跑起来就别动。
  4. 缓存对齐。buffer 按 cache line 对齐,避免跨 cacheline 的原子更新和伪共享,网卡 DMA 的收尾处理也更干净。
  5. 原子操作按需上。分布式锁、序号分配用 FETCH_AND_ADD / CMP_AND_SWP,单操作由硬件完成,比 CAS 自旋轻得多——但只在确实需要强一致语义时才用。

传输服务的选择也有立场:多数场景用 RC 即可,可靠、有序、支持单边;UD 只在需要无连接、组播或不可靠语义的场合考虑。

出问题时看什么

按"现象 → 工具 → 预期"排查:

  • 设备认不出来:跑 ibv_devinfo,应看到 PORT_ACTIVE 状态的端口和合理的 active_mtu;端口是 DOWN 就先查物理链路和驱动。
  • post_send 报 ENOMEM:发送队列满了,完成没被及时回收——检查轮询节奏,确认 CQ 深度与 max_send_wr 是否匹配。
  • wc.status 异常:用 perfquery 拉端口计数器,重传与 RNR NAK 相关计数应接近零;明显上涨说明网络有丢包或拥塞。
  • 带宽不达标:perfquery 看端口吞吐是否接近线速,再看 MTU;RoCE 场景 MTU 偏小会把大消息切碎。
  • 延迟抖动:对照队列深度使用率,SQ 长期贴满上限,说明投递和回收速率不均衡。

行动路线图

三个阶段,每步都有明确产出:

  1. 跑通样例:git clone https://gitcode.com/GitHub_Trending/rd/rdma-core 后按 CMake 构建,把 rc_pingpong 两端跑起来,确认 CQ 正常收到完成。先不急着改。
  2. 压一轮测:改大小和循环次数压测,盯 perfquery 的吞吐与重传计数,把批量投递和非信号化开上去,对比前后数字。
  3. 挑协议变体:按业务决定用 SEND/RECV 还是单边读写,翻一遍 examples 目录里的 ud/uc/srq 变体,不适用的直接删。

今晚的动作就一个:把 rc_pingpong 在两台机器上跑起来,把两端的输出逐行看一遍——你后面所有的优化,都从这份输出开始。

【免费下载链接】rdma-coreRDMA core userspace libraries and daemons项目地址: https://gitcode.com/GitHub_Trending/rd/rdma-core

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/21 1:59:49

iloader:3 步装好 SideStore 的 iOS 侧载工具

iloader:3 步装好 SideStore 的 iOS 侧载工具 【免费下载链接】iloader User friendly sideloader 项目地址: https://gitcode.com/GitHub_Trending/iloa/iloader iloader 是一款免费开源的 iOS 侧载工具,把下载、签名、安装、放置配对文件压缩成…

作者头像 李华
网站建设 2026/10/21 1:57:00

STM32串口IAP升级实战:Bootloader与App分区设计及避坑指南

简介:STM32F103串口IAP升级程序是一套完整且可直接落地的固件在线升级方案,面向嵌入式开发工程师及学习IAP原理的爱好者,解决设备运行中无需断电、不使用编程器即可更新固件的需求。该工程整合了开发者原创的核心逻辑与部分参考代码&#xff…

作者头像 李华
网站建设 2026/10/21 1:55:46

OpenAI API 身份验证上手指南:从 API Key 到 OAuth 2.0 的正确选择

OpenAI API 身份验证上手指南:从 API Key 到 OAuth 2.0 的正确选择 【免费下载链接】openai-openapi OpenAPI specification for the OpenAI API 项目地址: https://gitcode.com/GitHub_Trending/op/openai-openapi 第一次执行 curl 请求,返回了 …

作者头像 李华
网站建设 2026/10/21 1:54:51

STM32F103串口IAP升级:Bootloader与APP完整实现

简介:基于意法半导体STM32F103微控制器的串口应用内编程升级程序完整原创开源包,面向嵌入式开发者,解决设备运行状态下经串口更新固件、避免拆机烧录与依赖专用编程器的问题。工程实现了从引导程序、串口通信到闪存擦写、循环冗余校验、非法固…

作者头像 李华