摘要
RDMA(Remote Direct Memory Access,远程直接内存访问)正在从根本上改变数据中心网络编程模型。在分布式存储(Ceph、TiKV)、高性能计算(MPI)、GPUDirect 加速等场景中,RDMA 绕过内核协议栈实现亚微秒级延迟和零拷贝数据传输,单链路可达 400Gbps 吞吐。
本文深入分析 Linux 内核 RDMA 子系统完整架构,从 Verbs API 抽象、Queue Pair 状态机、内存注册机制,到 RoCE v2 与 InfiniBand 双栈实战,帮助你构建生产级 RDMA 应用。
1. 为什么需要 RDMA
1.1 传统 TCP/IP 栈的瓶颈
传统 Linux TCP/IP 网络存在三大核心开销:
| 开销类型 | 典型耗时 | 说明 |
|---|---|---|
| 系统调用 | 1~5 μs | 每次 I/O 触发用户态到内核态切换 |
| 内存拷贝 | 3~10 μs | 数据在 socket buffer 与用户态间 copy |
| 协议栈处理 | 2~10 μs | sk_buff 协议封装/解封装 |
| 上下文切换 | ~2 μs | 进程在调度与阻塞间切换 |
| 总计 | 10+ μs | 无法满足高性能场景 |
RDMA 通过内核旁路(Kernel Bypass)将数据传输延迟降至 1~2 μs,吞吐带宽利用率接近线速。
1.2 RDMA 三大核心能力
1. 零拷贝(Zero-Copy):应用程序数据直接在本地内存与远端 RDMA 网卡(HCA)DMA 引擎之间传输,完全跳过内核协议栈。
2. 内核旁路(Kernel Bypass):用户态驱动(libibverbs)直接操作网卡寄存器,免去系统调用开销,实现真正的异步全双工通信。
3. CPU 卸载(CPU Offload):协议处理(重传、分片、ACK 生成)全部卸载到网卡硬件,传输过程零 CPU 占用。
2. RDMA 硬件与传输层
2.1 InfiniBand(IB)
InfiniBand 是原生 RDMA 协议,从物理层到传输层全部专为 RDMA 设计:
- 物理层:QSFP28/QSFP56 光模块,单端口 200/400 Gbps
- 链路层:Credit-based Flow Control,无损传输
- 网络层:Subnet Manager(SM)管理 LID 路由
- 传输层:RC(可靠连接)、UC(不可靠连接)、UD(不可靠数据报)、RD(可靠数据报)
- 原生延迟:~600 ns(交换机端口到端口)
2.2 RoCE v2(RDMA over Converged Ethernet)
RoCE v2 允许在标准以太网上运行 RDMA,但依赖以下基础设施:
- PFC(Priority Flow Control):802.1Qbb 基于优先级的流控,实现无损以太网
- ECN(Explicit Congestion Notification):拥塞标记与反馈
- DCQCN(Data Center Quantized Congestion Notification):RoCE v2 的速率控制算法
- Basic QoS:DSCP 标记,带宽隔离
2.3 iWARP vs RoCE v2
| 特性 | RoCE v2 | iWARP | InfiniBand |
|---|---|---|---|
| 网络层 | UDP/IP | TCP/SCTP | 原生 IB |
| 延迟 | ~1-3 μs | ~5-10 μs | ~600 ns |
| 交换机要求 | 需 PFC/ECN 支持 | 标准以太网 | IB 交换机 |
| 路由能力 | 可路由 | 可路由 | 不可路由(需) |
| 部署成本 | 中 | 低 | 高 |
3. Linux RDMA 内核子系统架构
3.1 内核模块拓扑
Linux 内核从 2.6 版本开始集成 RDMA 子系统(drivers/infiniband),核心模块包括:
- ib_core:核心框架层,管理设备、注册驱动接口
- ib_uverbs:用户态 verbs 通信通道(/dev/infiniband/uverbsX)
- rdma_cm:RDMA 连接管理(对应 TCP socket API 的角色)
- ib_umad:用户态管理数据报(子网管理/SM 通信)
- mlx5_core:Mellanox ConnectX 系列网卡驱动
- ib_ipoib:IP over IB 网络层(提供传统 socket 兼容)
- ib_isert/ib_srpt:iSER/SRP SCSI 目标端(块存储协议)
- nvmet-rdma/nvme-rdma:NVMe-oF RDMA 目标/发起端
完整模块可通过以下命令查看:
# 查看已加载的 RDMA 内核模块
$ lsmod | grep -E "ib_|rdma|mlx|mlx5"
# 查看 RDMA 设备列表
$ ibv_devinfo
hca_id: mlx5_0
transport: InfiniBand (0)
fw_ver: 16.35.1012
node_guid: 248a:0703:00b4:7f80
sys_image_guid: 248a:0703:00b4:7f80
vendor_id: 0x02c9
vendor_part_id: 4123
hw_ver: 0x0
board_id: MT_0000000221
phys_port_cnt: 1
state: PORT_ACTIVE (4)
max_mtu: 4096 (5)
active_mtu: 4096 (5)
sm_lid: 1
port_lid: 1
port_lmc: 0x00
link_layer: InfiniBand
3.2 uverbs 用户态驱动框架
用户态驱动程序运行在进程地址空间,通过 uverbs 字符设备与内核 RDMA 子系统交互:
// uverbs 设备打开
struct ibv_context *ctx = ibv_open_device(ib_dev);
// 分配 Protection Domain
struct ibv_pd *pd = ibv_alloc_pd(ctx);
// 创建 Completion Queue
struct ibv_cq *cq = ibv_create_cq(ctx, cq_depth, NULL, NULL, 0);
// 创建 Queue Pair
struct ibv_qp_init_attr qp_init_attr = {
.send_cq = cq,
.recv_cq = cq,
.cap = { .max_send_wr = 128, .max_recv_wr = 128,
.max_send_sge = 1, .max_recv_sge = 1 },
.qp_type = IBV_QPT_RC, // Reliable Connection
};
struct ibv_qp *qp = ibv_create_qp(pd, &qp_init_attr);
关键文件路径:
- 内核驱动:
drivers/infiniband/core/uverbs_main.c - 用户态 lib:
libibverbs/verbs.c(rdma-core 项目) - 设备节点:
/dev/infiniband/uverbsX、/dev/infiniband/rdma_cm - sysfs 接口:
/sys/class/infiniband/
4. RDMA Verbs API 核心编程模型
4.1 Queue Pair(QP)状态机
Queue Pair 是 RDMA 通信的核心抽象,由 Send Queue 和 Receive Queue 组成,可支持 RC(可靠连接)和 UD(不可靠数据报)。状态迁移如下:
RESET → INIT → RTR(Ready To Receive) → RTS(Ready To Send)
RESET:复位状态,清除所有资源
INIT:初始化端口属性,配置 P_Key、Q_Key
RTR:远端 QP 信息已知,可接收数据包
RTS:激活发送/接收路径,完全可操作
状态迁移通过 ibv_modify_qp() 实现,对应内核中 drivers/infiniband/core/verbs.c 的 ib_modify_qp() 函数,最终调用网卡驱动 modify_qp 回调。
4.2 Memory Registration & Memory Region
RDMA 网络 DMA 引擎绕过 CPU 访问物理内存,因此需要提前注册(钉住)内存区域:
// 分配内存
void *buf = aligned_alloc(4096, BUFFER_SIZE);
// 注册 Memory Region
struct ibv_mr *mr = ibv_reg_mr(pd, buf, BUFFER_SIZE,
IBV_ACCESS_LOCAL_WRITE |
IBV_ACCESS_REMOTE_READ |
IBV_ACCESS_REMOTE_WRITE);
注册时内核将执行:
- 钉住页面(get_user_pages):防止物理内存被换出(swap)
- IOMMU 映射:建立 HCA 设备可访问的 DMA 地址映射
- 注册 MR 描述符:创建 lkey(本地访问)和 rkey(远程访问授权)
关键参数选择:
IBV_ACCESS_LOCAL_WRITE:允许本端写入IBV_ACCESS_REMOTE_READ:允许远端读取IBV_ACCESS_REMOTE_WRITE:允许远端写入IBV_ACCESS_REMOTE_ATOMIC:允许远端原子操作(CAS/FAA)IBV_ACCESS_ON_DEMAND:按需分页(与内存注册解耦,延迟注册)
4.3 Work Request 与 Completion Queue
RDMA 的工作模式是完全异步的:
// 发送端:构造 SGE(Scatter-Gather Element)
struct ibv_sge sge = {
.addr = (uint64_t)local_buf,
.length = data_size,
.lkey = mr->lkey,
};
struct ibv_send_wr wr = {
.wr_id = 1,
.opcode = IBV_WR_RDMA_WRITE, // RDMA Write(零拷贝单向写入)
.send_flags = IBV_SEND_SIGNALED, // 产生 Completion Queue Entry
.sg_list = &sge,
.num_sge = 1,
.wr.rdma.remote_addr = remote_mr_addr,
.wr.rdma.rkey = remote_rkey,
};
struct ibv_send_wr *bad_wr;
ibv_post_send(qp, &wr, &bad_wr); // 异步投递
// 轮询等待完成
struct ibv_wc wc;
while (ibv_poll_cq(cq, 1, &wc) == 0) { /* spin */ }
if (wc.status != IBV_WC_SUCCESS) { /* 错误处理 */ }
可选 OPCODE:
| 操作码 | 语义 | CPU 参与 |
|---|---|---|
| IBV_WR_SEND | Send/Recv(双向) | 接收端需 post_recv |
| IBV_WR_RDMA_WRITE | RDMA Write(单向写入) | 接收端零感知 |
| IBV_WR_RDMA_READ | RDMA Read(单向读取) | 接收端零感知 |
| IBV_WR_ATOMIC_CMP_AND_SWP | Compare-and-Swap | 远端原子操作 |
| IBV_WR_ATOMIC_FETCH_AND_ADD | Fetch-and-Add | 远端原子操作 |
5. RDMA-CM 连接管理实战
RDMA Connection Manager(rdma_cm)提供面向连接的通信建立,协议类似 TCP socket:
5.1 服务端流程
// 创建 rdma_cm 事件通道
struct rdma_event_channel *ec = rdma_create_event_channel();
// 创建监听 ID
struct rdma_cm_id *listen_id;
rdma_create_id(ec, &listen_id, NULL, RDMA_PS_TCP); // TCP 类型(RoCE v2 over UDP)
// 绑定地址并监听
struct sockaddr_in addr = { .sin_family = AF_INET, .sin_port = htons(18515) };
rdma_bind_addr(listen_id, (struct sockaddr *)&addr);
rdma_listen(listen_id, 8);
// 阻塞等待连接事件
struct rdma_cm_event *event;
rdma_get_cm_event(ec, &event); // 阻塞,直到 RDMA_CM_EVENT_CONNECT_REQUEST
struct rdma_cm_id *conn_id = event->id;
// 分配并初始化资源
conn_id->verbs = listen_id->verbs;
setup_qp_resources(conn_id); // CQ, PD, QP
// 接受连接
rdma_accept(conn_id, NULL);
rdma_ack_cm_event(event); // 必须 ACK 事件
5.2 客户端流程
struct rdma_cm_id *conn_id;
rdma_create_id(ec, &conn_id, NULL, RDMA_PS_TCP);
setup_qp_resources(conn_id);
// 解析地址
struct sockaddr_in dst_addr = { .sin_family = AF_INET, .sin_port = htons(18515) };
inet_pton(AF_INET, "192.168.10.100", &dst_addr.sin_addr);
// rdma_resolve_addr 先做 ARP 解析(ICM path record)
rdma_resolve_addr(conn_id, NULL, (struct sockaddr *)&dst_addr, 2000);
// 等待地址解析完成
rdma_get_cm_event(ec, &event); // RDMA_CM_EVENT_ADDR_RESOLVED
rdma_ack_cm_event(event);
// 发起连接
rdma_resolve_route(conn_id, 2000);
rdma_get_cm_event(ec, &event); // RDMA_CM_EVENT_ROUTE_RESOLVED
rdma_ack_cm_event(event);
// 发送 CONNECT 报文
struct rdma_conn_param param = { .responder_resources = 1, .initiator_depth = 1 };
rdma_connect(conn_id, ¶m);
// 等待远端 ACCEPT
rdma_get_cm_event(ec, &event); // RDMA_CM_EVENT_ESTABLISHED
6. SR-IOV 虚拟化与 RDMA
6.1 内核 SR-IOV 框架
Mellanox ConnectX-5 及更新网卡支持 SR-IOV 虚拟化,将物理 HCA 拆分为多个 VF(Virtual Function):
# 启用 SR-IOV VF
echo 8 > /sys/class/infiniband/mlx5_0 device/sriov_numvfs
# 查看 VF PCI 设备
lspci | grep Mellanox
b5:00.0 InfiniBand controller: Mellanox Technologies MT27800 Family [ConnectX-5] # PF
b5:02.0 InfiniBand controller: Mellanox Technologies ConnectX-5 VF # VF0
b5:02.1 InfiniBand controller: Mellanox Technologies ConnectX-5 VF # VF1
# 验证 VF 功能
VF 0000:b5:02.0:
Link Status: up
Node GUID: 0000:0000:0000:0001
6.2 直通 VF 到容器
通过 Kubernetes Device Plugin,将 VF 注入到 Pod:
# sriov-device-plugin ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: sriovdp-config
data:
config.json: |
{
"resourceList": [{
"resourceName": "mlx5_rdma",
"selectors": {
"isRdma": true,
"drivers": ["mlx5_core"],
"pciAddresses": ["0000:b5:02.0"]
}
}]
}
---
# Pod 使用 RDMA VF
apiVersion: v1
kind: Pod
spec:
containers:
- name: rdma-app
resources:
requests:
mellanox.com/mlx5_rdma: 1
6.3 Sub-Function 虚拟化(SmartNIC 模式)
Mellanox BlueField SmartNIC 引入 Sub-Function 模型,在有限 PCIe 通道下提供超大规模 VF 调度:
- 每个 SF 拥有独立的 netdev、E-Switch 端口、verbs 设备
- 支持热插拔,不依赖 PCIe PF 数量限制
- 实现 DPU 连接模式(SmartNIC/DPU)从次通道卸载 RDMA 主机通信
7. RDMA 在分布式存储与计算中的应用
7.1 NVMe over Fabrics(NVMe-oF)RDMA
NVMe-oF 将 NVMe 命令封装在 RDMA 消息中,实现跨网络的低延迟块存储访问:
# 目标端(Target)
nvmetcli / - > cd subsystems
/subsystems/ -> create nqn.2024-01.com.vendor:nvme-rdma-01
/subsystems/ -> cd namespaces
/subsystems/nvme-rdma-01/namespaces/ -> create 1
/subsystems/nvme-rdma-01/namespaces/1/ -> set device path=/dev/nvme0n1
# 配置 RDMA 传输端口
/ports/ -> create 1
/ports/1/ -> set addr trtype=rdma,traddr=192.168.10.100,trsvcid=4420
# 发起端(Initiator)
nvme connect -t rdma -n nqn.2024-01.com.vendor:nvme-rdma-01 \
-a 192.168.10.100 -s 4420
性能表现:
- 延迟:~10 μs(本地 NVMe ~3 μs)
- 带宽利用率:>95%(对比TCP的~60%)
- IOPS:单 RoCE 25G 链路可达 3.8M IOPS
7.2 GPUDirect RDMA
NVIDIA GPUDirect RDMA 允许 HCA 直接读取 GPU 显存,跨节点 GPU 通信带宽突破 600 GB/s(NVLink 级性能):
// 分配 CUDA 显存
cudaMalloc(&gpu_buf, buf_size);
// 注册 CUDA 内存为可 RDMA 访问的 MR
struct ibv_mr *mr = ibv_reg_mr(pd, gpu_buf, buf_size,
IBV_ACCESS_LOCAL_WRITE | IBV_ACCESS_REMOTE_WRITE |
IBV_ACCESS_REMOTE_READ);
// RDMA Write 直接写入对端 GPU 显存(不经过 CPU)
// NCCL 内部已集成此机制
ncclAllReduce(send_buf, recv_buf, count, type, op, comm, stream);
7.3 DAOS 分布式存储引擎
Intel DAOS 是专为 NVMe + RDMA 设计的分布式对象存储引擎:
- 元数据 + 数据双通道分离
- bulk transfer 绕过 CPU 调度
- 基于 RDMA 的多层复制仲裁(FPGA 参与故障恢复)
- 单集群 EB 级容量,每节点 100Gbps RDMA 链路
8. 性能调优与故障排查
8.1 QP Cache 优化
动态 QP 创建有显著上下文切换开销(~20 μs),启用 QP Cache 可降低至 ~1 μs:
// 启用 QP 缓存
struct ibv_qp_init_attr_ex qp_attr = {
.qp_type = IBV_QPT_RC,
.comp_mask = IBV_QP_INIT_ATTR_PD |
IBV_QP_INIT_ATTR_SEND_OPS_FLAGS,
.send_ops_flags = IBV_QP_EX_WITH_RDMA_WRITE |
IBV_QP_EX_WITH_SEND |
IBV_QP_EX_WITH_ATOMIC_CMP_AND_SWP,
};
// 使用 QP Ex API 创建
ibv_create_qp_ex(ctx, &qp_attr);
8.2 CQ 轮询优化
默认的 CQ 轮询在高速场景下造成大量 CPU 占用:
- Adaptive coalescing:设置合并阈值(adaptive-moderation),平衡延迟与 CPU 消耗
- Busy polling:
echo 1 > /sys/class/infiniband/mlx5_0/settings/busypolling - MSI-X 中断亲和:
echo "0f" > /proc/irq/<irq_num>/smp_affinity,绑定到 NUMA 本地核心
8.3 GDB 调试指引
RDMA 常见问题排查:
# 查看QP状态
ibv_query_qp
# 监控 HCA 错误计数器
cat /sys/class/infiniband/mlx5_0/ports/1/counters/port_rcv_errors
cat /sys/class/infiniband/mlx5_0/ports/1/counters/port_xmit_discards
# 查看 rdma resource(QP/MR/CQ 使用统计)
rdma resource qp
# perftest 工具集
ib_write_bw -d mlx5_0 -i 1 --size 65536 -n 1000 # 带宽测试
ib_read_lat -d mlx5_0 -i 1 -n 1000 # 延迟测试
ib_atomic_bw -d mlx5_0 -i 1 -F --size 8 -n 500 # 原子操作带宽
9. 完整代码框架
完整可编译的 UDMA 示例(简化版),包含 server 端与 client 端的核心逻辑框架:
// rdma_common.hpp
struct RDMAContext {
struct ibv_context *ctx;
struct ibv_pd *pd;
struct ibv_cq *cq;
struct ibv_qp *qp;
struct ibv_mr *mr;
char *buf;
uint32_t rkey;
uint64_t remote_addr;
};
// server.cpp
int main() {
RDMAContext ctx;
init_device(ctx, "mlx5_0");
register_memory(ctx, 1024 * 1024);
create_qp(ctx);
// rdma listen + accept loop
rdma_event_channel *ec = rdma_create_event_channel();
rdma_cm_id *listen_id;
rdma_create_id(ec, &listen_id, NULL, RDMA_PS_TCP);
rdma_bind_addr(listen_id, &server_bind_addr);
rdma_listen(listen_id, 8);
while (true) {
rdma_cm_event *event;
rdma_get_cm_event(ec, &event);
handle_connect_request(ctx, event->id); // setup + accept
rdma_ack_cm_event(event);
// 等待数据就绪
wait_data_processed(ctx);
}
}
// client.cpp
int main() {
RDMAContext ctx;
init_device(ctx, "mlx5_0");
register_memory(ctx, 1024 * 1024);
create_qp(ctx);
rdma_event_channel *ec = rdma_create_event_channel();
rdma_cm_id *conn_id;
rdma_create_id(ec, &conn_id, NULL, RDMA_PS_TCP);
// resolve addr + route + connect
rdma_resolve_addr(conn_id, ...);
rdma_resolve_route(conn_id, ...);
rdma_connect(conn_id, ...);
// RDMA Write 发送数据
struct ibv_send_wr wr = { .opcode = IBV_WR_RDMA_WRITE, ... };
ibv_post_send(conn_id->qp, &wr, &bad_wr);
// 通知server
struct ibv_send_wr msg_wr = { .opcode = IBV_WR_SEND, ... };
ibv_post_send(conn_id->qp, &msg_wr, &bad_wr);
}
10. 总结
RDMA 通过内核旁路、CPU 卸载、零拷贝三位一体机制,实现了亚微秒级延迟与高吞吐。在内核层面,Linux RDMA 子系统(ib_core、ib_uverbs、rdma_cm)为用户态 Verbs API 提供了可靠的基础架构。对于生产环境部署:
- RoCE v2 是当前数据中心主流选择,需 PFC/ECN/DCQCN 无损网保障
- InfiniBand 适用于超低延迟(HPC、AI 训练)场景
- NVMe-oF RDMA 是跨网 NVMe 块存储的最佳实践
- GPUDirect RDMA 是 AI 分布式训练 GPU 直连通信的事实标准
掌握 RDMA Verbs API 和连接管理(rdma_cm),能够帮助你构建下一代高性能分布式存储、GPU 集群通信和边缘计算应用。

发表评论 取消回复