- RDMA 基础概念
- 整体架构
- IB Verbs 核心数据结构
- QP 类型与传输服务
- QP 状态机
- 发送与接收操作
- 内存注册(MR)
- RDMA Read/Write 操作流程
- 连接管理(rdma_cm)
- SoftRoCE(rxe)以太网上的 RoCE v2
- 用户态接口
- 上层协议应用
- 关键内核机制详解
- 调试与可观测性
- 完成队列深度解析
- 用户内存注册与 ib_umem
- On-Demand Paging 深度分析
- uverbs 用户态 Verbs 内核实现
- iWARP RDMA over TCP/IP
- RDMA RW API 内核封装层
- MR 池(mr_pool)与 Fast Registration
- SoftRoCE 请求端与响应端状态机
- RoCE 无损网络与流量控制
- 设备管理与资源追踪
- NVMe-oF RDMA 深度分析
- MPI 与 RDMA 集成
- 性能调优与最佳实践
- 安全机制
RDMA(Remote Direct Memory Access,远程直接内存访问)是一种网络技术,允许一台计算机直接读写另一台计算机的内存,无需 CPU 介入数据路径。
零拷贝(Zero Copy)
传统网络通信数据路径:
用户缓冲区 --> 内核 Socket 缓冲区 --> 网卡 DMA --> 网络
(对端)网络 --> 网卡 DMA --> 内核 Socket 缓冲区 --> 用户缓冲区
RDMA 数据路径:
用户缓冲区(已注册的 MR) --> 网卡 DMA --> 网络
(对端)网络 --> 网卡 DMA --> 用户缓冲区(已注册的 MR)
RDMA 完全绕过内核的 Socket 缓冲区,数据直接在用户内存和网卡之间传输,消除了内存拷贝。
Kernel Bypass(内核旁路)
在数据平面上,应用程序直接通过映射到用户空间的 Queue Pair 环形队列与硬件通信,不经过任何系统调用:
用户空间
|
| mmap 映射 QP/CQ 到用户地址空间
|
+---> 直接写 WQE 到发送队列(SQ) 无 syscall
+---> 直接轮询 CQ 获取完成事件 无 syscall
+---> 数据传输完全由硬件处理
仅资源创建/销毁(alloc_pd、create_qp 等)需要系统调用,热路径零 syscall。
内存注册(Memory Registration,MR)
在传输数据前,应用程序必须将内存区域注册到 RDMA 设备:
- 固定物理页,防止被 swap out
- 建立虚拟地址到物理地址的映射(IOVA 翻译表存储在 HCA 的内部 MPT/MTT 中)
- 生成 lkey(本地访问密钥)和 rkey(远程访问密钥)
注册后,应用通过 lkey/rkey 引用内存,硬件直接使用物理地址完成 DMA。
| 特性 | InfiniBand | RoCE v2 | iWARP |
|---|---|---|---|
| 物理层 | 专用 IB 光纤 | 以太网 | 以太网 |
| 传输层 | IB 协议 | UDP | TCP |
| 无损网络 | 是(信用流控) | 需要 PFC/ECN | TCP 保证 |
| 延迟 | 最低 | 低 | 略高 |
| 带宽 | HDR 200Gb/s+ | 取决于以太网 | 取决于以太网 |
| 成本 | 高(专用设备) | 低(标准以太网) | 低 |
+-----------------------------------------------------------+
| 用 户 空 间 |
| libibverbs librdmacm perftest nvme-cli iscsiadm |
| | | | |
| /dev/infiniband/uverbsX /dev/infiniband/rdma_cmX |
+-------------------+-------------------+-------------------+
| |
+-------------------+-------------------+-------------------+
| 内 核 空 间 |
| |
| +------------------+ +---------------------------+ |
| | ULP (上层协议) | | 用户态 Verbs 接口 | |
| | nvme-rdma | | ib_uverbs.c | |
| | iser (iSCSI) | | uverbs_cmd.c | |
| | IPoIB | | rdma_user_cm.c | |
| | SRP | +---------------------------+ |
| +--------+---------+ |
| | |
| +--------v-----------------------------------------+ |
| | RDMA Core / IB Verbs 层 | |
| | ib_verbs.h verbs.c cma.c uverbs.c | |
| | ib_core.c ib_cache.c ib_mad.c | |
| | sa_query.c cm.c pkey.c | |
| +--------+-----------------------------------------+ |
| | | | |
| +--------v--+ +----v------+ +v----------+ |
| | mlx5_ib | | hns_roce | | rxe (SW) | |
| | mlx4_ib | | erdma | | siw (SW) | |
| | qedr | | bnxt_re | | | |
| +--------+--+ +-----------+ +-----------+ |
| | |
+---+-------+-----------------------------------------------+
|
+---v--------------------------------------------------+
| 硬件 / 以太网 |
| Mellanox ConnectX / Intel Omni-Path / Chelsio |
| RoCE 网卡 / 普通以太网(rxe/siw 软件模拟) |
+------------------------------------------------------+
核心源码目录结构:
include/rdma/
ib_verbs.h -- 所有 Verbs 数据结构和接口定义
rdma_cm.h -- 连接管理接口
mr_pool.h -- MR 池管理
drivers/infiniband/
core/
verbs.c -- Verbs API 实现(PD/AH/QP/CQ/MR)
cma.c -- 连接管理代理(RDMA CM)
ib_core.c -- 设备注册管理
uverbs*.c -- 用户态 Verbs 实现
ib_cache.c -- GID/PKey 缓存
ib_mad.c -- 管理数据报
sw/
rxe/ -- SoftRoCE(纯软件 RoCE v2)
siw/ -- SoftiWARP
hw/
mlx5/ -- Mellanox ConnectX 驱动
mlx4/ -- Mellanox 老款 HCA
hns/ -- 华为鲲鹏 HNS RDMA
ulp/
iser/ -- iSCSI Extensions for RDMA
srp/ -- SCSI RDMA Protocol
ipoib/ -- IP over InfiniBand
drivers/nvme/host/rdma.c -- NVMe over RDMA
定义于 include/rdma/ib_verbs.h 第 2812 行:
struct ib_device {
/* 不允许 ULP 和驱动直接访问 @dma_device */
struct device *dma_device;
struct ib_device_ops ops; // 设备操作函数指针表
char name[IB_DEVICE_NAME_MAX];
struct list_head event_handler_list;
struct rw_semaphore event_handler_rwsem;
spinlock_t qp_open_list_lock;
struct rw_semaphore client_data_rwsem;
struct xarray client_data;
/* GID/PKey 缓存锁 */
rwlock_t cache_lock;
struct ib_port_data *port_data;
int num_comp_vectors;
union {
struct device dev;
struct ib_core_device coredev;
};
u64 uverbs_cmd_mask;
char node_desc[IB_DEVICE_NODE_DESC_MAX];
__be64 node_guid;
u32 local_dma_lkey;
u8 node_type;
u32 phys_port_cnt;
struct ib_device_attr attrs; // 设备能力(max_qp、max_cqe 等)
refcount_t refcount;
// ...
};ib_device_ops(第 2300 行附近)是驱动必须实现的函数指针表,包含:
struct ib_device_ops {
// 快路径操作(对延迟敏感)
int (*post_send)(struct ib_qp *qp, const struct ib_send_wr *send_wr,
const struct ib_send_wr **bad_send_wr);
int (*post_recv)(struct ib_qp *qp, const struct ib_recv_wr *recv_wr,
const struct ib_recv_wr **bad_recv_wr);
int (*poll_cq)(struct ib_cq *cq, int num_entries, struct ib_wc *wc);
int (*req_notify_cq)(struct ib_cq *cq, enum ib_cq_notify_flags flags);
// 资源管理操作
int (*alloc_pd)(struct ib_pd *pd, struct ib_udata *udata);
int (*dealloc_pd)(struct ib_pd *pd, struct ib_udata *udata);
int (*create_qp)(struct ib_qp *qp, struct ib_qp_init_attr *qp_init_attr,
struct ib_udata *udata);
int (*modify_qp)(struct ib_qp *qp, struct ib_qp_attr *qp_attr,
int qp_attr_mask, struct ib_udata *udata);
int (*destroy_qp)(struct ib_qp *qp, struct ib_udata *udata);
int (*create_cq)(struct ib_cq *cq, const struct ib_cq_init_attr *attr,
struct uverbs_attr_bundle *attrs);
int (*destroy_cq)(struct ib_cq *cq, struct ib_udata *udata);
struct ib_mr *(*reg_user_mr)(struct ib_pd *pd, u64 start, u64 length,
u64 virt_addr, int mr_access_flags,
struct ib_dmah *dmah, struct ib_udata *udata);
int (*dereg_mr)(struct ib_mr *mr, struct ib_udata *udata);
// ...(约 100 个操作函数)
};定义于 include/rdma/ib_verbs.h 第 1586 行:
struct ib_pd {
u32 local_dma_lkey; // 本地 DMA 默认 lkey
u32 flags;
struct ib_device *device;
struct ib_uobject *uobject; // 用户态对象句柄
atomic_t usecnt; // 引用的资源数量
u32 unsafe_global_rkey;
struct ib_mr *__internal_mr; // 内部 DMA MR
struct rdma_restrack_entry res;
};保护域将 QP、SRQ、AH、MR、MW 关联在一起,形成访问隔离单元。只有同一 PD 内的资源才能相互通信(使用对方的 lkey/rkey)。
内核中分配 PD:verbs.c 第 317 行的 __ib_alloc_pd():
struct ib_pd *__ib_alloc_pd(struct ib_device *device, unsigned int flags,
const char *caller)
{
struct ib_pd *pd;
int mr_access_flags = 0;
int ret;
pd = rdma_zalloc_drv_obj(device, ib_pd); // 分配驱动自定义大小
if (!pd)
return ERR_PTR(-ENOMEM);
pd->device = device;
pd->flags = flags;
rdma_restrack_new(&pd->res, RDMA_RESTRACK_PD);
rdma_restrack_set_name(&pd->res, caller);
ret = device->ops.alloc_pd(pd, NULL); // 调用驱动实现
// ...
// 如果设备不支持 IBK_LOCAL_DMA_LKEY,则创建一个内部 DMA MR
if (mr_access_flags) {
struct ib_mr *mr;
mr = pd->device->ops.get_dma_mr(pd, mr_access_flags);
// ...
pd->local_dma_lkey = pd->__internal_mr->lkey;
}
return pd;
}定义于 include/rdma/ib_verbs.h 第 1817 行:
struct ib_qp {
struct ib_device *device;
struct ib_pd *pd; // 关联的保护域
struct ib_cq *send_cq; // 发送完成队列
struct ib_cq *recv_cq; // 接收完成队列
spinlock_t mr_lock;
int mrs_used;
struct list_head rdma_mrs; // 关联的 RDMA MR 列表
struct ib_srq *srq; // 共享接收队列(可选)
atomic_t usecnt;
struct ib_uqp_object *uobject;
void (*event_handler)(struct ib_event *, void *);
void *qp_context;
const struct ib_gid_attr *av_sgid_attr;
u32 qp_num; // 硬件分配的 QP 编号(24 位)
u32 max_write_sge;
u32 max_read_sge;
enum ib_qp_type qp_type; // RC/UC/UD/XRC 等
struct ib_qp_security *qp_sec;
struct rdma_restrack_entry res;
struct rdma_counter *counter;
};定义于 include/rdma/ib_verbs.h 第 1629 行:
struct ib_cq {
struct ib_device *device;
struct ib_ucq_object *uobject;
ib_comp_handler comp_handler; // 完成通知回调
void (*event_handler)(struct ib_event *, void *);
void *cq_context;
int cqe; // 队列深度(最大完成条目数)
unsigned int cqe_used; // 已使用的条目数
atomic_t usecnt;
enum ib_poll_context poll_ctx; // 轮询上下文
struct ib_wc *wc; // WC 数组缓存
struct list_head pool_entry;
union {
struct irq_poll iop; // NAPI 风格 IRQ 轮询
struct work_struct work; // 工作队列处理
};
struct workqueue_struct *comp_wq;
struct dim *dim; // 动态中断调节
ktime_t timestamp;
u8 interrupt:1;
u8 shared:1;
unsigned int comp_vector; // 中断向量亲和性
struct rdma_restrack_entry res;
};ib_poll_context 枚举决定 CQ 的处理方式:
IB_POLL_SOFTIRQ:在 softirq 上下文中轮询IB_POLL_WORKQUEUE:在绑定工作队列中轮询IB_POLL_UNBOUND_WORKQUEUE:在非绑定工作队列中轮询IB_POLL_DIRECT:调用者直接轮询(无硬件中断)
定义于 include/rdma/ib_verbs.h 第 1889 行:
struct ib_mr {
struct ib_device *device;
struct ib_pd *pd;
u32 lkey; // 本地键(本地 DMA 权限)
u32 rkey; // 远程键(远程访问权限)
u64 iova; // 虚拟地址(IOVA,对远端可见的地址)
u64 length; // 注册长度(字节)
unsigned int page_size; // 页大小(内部分页粒度)
enum ib_mr_type type; // MR 类型
bool need_inval; // 是否需要 INVALIDATE
union {
struct ib_uobject *uobject; // 用户态 MR
struct list_head qp_entry;// Fast Registration MR
};
struct ib_dm *dm; // 设备内存 MR
struct ib_sig_attrs *sig_attrs; // T10-DIF 完整性属性
struct rdma_restrack_entry res;
};MR 类型(ib_mr_type 枚举,第 915 行):
enum ib_mr_type {
IB_MR_TYPE_MEM_REG, // 普通内存注册
IB_MR_TYPE_SG_GAPS, // 支持 scatter-gather gaps 的 MR
IB_MR_TYPE_DM, // 设备内存(MMIO 区域)
IB_MR_TYPE_USER, // 用户空间 MR(reg_user_mr)
IB_MR_TYPE_DMA, // DMA MR(VA=PA,无地址翻译)
IB_MR_TYPE_INTEGRITY, // T10-DIF 数据完整性 MR
};定义于 include/rdma/ib_verbs.h 第 1049 行:
struct ib_wc {
union {
u64 wr_id; // 用户提交 WR 时的 wr_id
struct ib_cqe *wr_cqe; // 或者 CQE 回调指针
};
enum ib_wc_status status; // 完成状态(SUCCESS/错误码)
enum ib_wc_opcode opcode; // 操作类型(SEND/RDMA_WRITE/RDMA_READ)
u32 vendor_err;
u32 byte_len; // 传输字节数
struct ib_qp *qp;
union {
__be32 imm_data; // SEND_WITH_IMM 的即时数据
u32 invalidate_rkey; // SEND_WITH_INV 的 rkey
} ex;
u32 src_qp; // 发送方 QP 号(对 UD QP 有效)
u32 slid; // 发送方 LID(IB 专用)
int wc_flags; // IB_WC_GRH | IB_WC_WITH_IMM 等
u16 pkey_index;
u8 sl; // 服务等级
// ...
};WC 状态码(ib_wc_status),来自 verbs.c 第 93 行的字符串表:
static const char * const wc_statuses[] = {
[IB_WC_SUCCESS] = "success",
[IB_WC_LOC_LEN_ERR] = "local length error",
[IB_WC_LOC_QP_OP_ERR] = "local QP operation error",
[IB_WC_LOC_PROT_ERR] = "local protection error",
[IB_WC_WR_FLUSH_ERR] = "WR flushed",
[IB_WC_REM_ACCESS_ERR] = "remote access error",
[IB_WC_RETRY_EXC_ERR] = "transport retry counter exceeded",
[IB_WC_RNR_RETRY_EXC_ERR]= "RNR retry counter exceeded",
// ...
};QP 类型定义于 include/rdma/ib_verbs.h 第 1145 行的 ib_qp_type 枚举:
enum ib_qp_type {
IB_QPT_SMI, // Subnet Management Interface QP(QP0)
IB_QPT_GSI, // General Service Interface QP(QP1)
IB_QPT_RC = 2, // Reliable Connected
IB_QPT_UC = 3, // Unreliable Connected
IB_QPT_UD = 4, // Unreliable Datagram
IB_QPT_RAW_PACKET = 8, // 原始以太网包(RoCE)
IB_QPT_XRC_INI = 9, // Extended Reliable Connected(发起方)
IB_QPT_XRC_TGT = 10, // Extended Reliable Connected(目标方)
IB_QPT_DRIVER = 0xFF, // 驱动私有 QP 类型
};RC(Reliable Connected,可靠连接)
节点 A 节点 B
QP(RC) <--一对一连接--> QP(RC)
特性:
- 保证消息有序投递
- 硬件级别的确认/重传
- 支持 RDMA Read/Write/Atomic
- 一个 QP 只连接一个对端 QP
- 典型应用:RDMA 存储(NVMe-oF、iSER)、MPI
UC(Unreliable Connected,不可靠连接)
特性:
- 有连接(一对一)
- 不保证投递(无确认/重传)
- 支持 RDMA Write(但不支持 RDMA Read)
- 性能高于 RC(无 ACK 开销)
- 典型应用:视频流、实时数据分发
UD(Unreliable Datagram,不可靠数据报)
节点 A 节点 B
QP(UD) ----单播-----> QP(UD)
----组播-----> 多个 QP(UD)
特性:
- 无连接(类似 UDP)
- 每条消息最大 MTU(4096 字节)
- 需要 Address Handle(AH)指定目标
- 支持组播
- 典型应用:IPoIB、InfiniBand 子网管理
XRC(Extended Reliable Connected)
特性:
- RC 的扩展,用于多对多扩展场景
- 目标端使用 XRC 域(XRCD)替代 QP
- 显著减少大规模集群中的 QP 数量
- N 个节点:RC 需要 N² 个 QP,XRC 需要 N 个 XRC TGT QP
QP 状态定义于 include/rdma/ib_verbs.h 第 1302 行:
enum ib_qp_state {
IB_QPS_RESET, // 重置状态(初始/终止)
IB_QPS_INIT, // 初始化(配置端口/PKey/访问标志)
IB_QPS_RTR, // Ready to Receive(接收就绪)
IB_QPS_RTS, // Ready to Send(发送就绪,可收可发)
IB_QPS_SQD, // Send Queue Draining(发送队列排空中)
IB_QPS_SQE, // Send Queue Error(发送队列错误)
IB_QPS_ERR // Error(错误状态)
}; +----------+
| RESET | (创建 QP 后初始状态)
+----+-----+
| modify_qp(RESET -> INIT)
| 需设置: QP_STATE, PKEY_INDEX, PORT, ACCESS_FLAGS
v
+----+-----+
| INIT | (可以 post_recv,不能 post_send)
+----+-----+
| modify_qp(INIT -> RTR)
| 需设置: QP_STATE, PATH_MTU, DEST_QPN,
| RQ_PSN, MAX_DEST_RD_ATOMIC,
| MIN_RNR_TIMER, AV (地址向量)
v
+----+-----+
| RTR | (可以 post_recv,接收数据)
+----+-----+
| modify_qp(RTR -> RTS)
| 需设置: QP_STATE, SQ_PSN, MAX_QP_RD_ATOMIC,
| RETRY_CNT, RNR_RETRY, TIMEOUT
v
+----+-----+
+-->| RTS |<--+ (可以 post_send 和 post_recv)
| +----+-----+ |
| | |
| SQD | | 恢复
| 请求 | |
| v |
| +----+-----+ |
| | SQD +---+ (发送队列排空,等待飞行中 WR 完成)
| +----+-----+
| | 发送队列错误
| v
| +----+-----+
+---+ SQE | (发送队列错误,可转换回 RTS)
+----+-----+
| 不可恢复错误
v
+----+-----+
| ERR | (所有未完成 WR 被 flush,生成 IB_WC_WR_FLUSH_ERR)
+----+-----+
| modify_qp(ERR -> RESET)
v
+----------+
| RESET | (可以重新初始化)
+----------+
QP 修改通过 ib_modify_qp() 完成,内部调用 device->ops.modify_qp()。
ib_qp_attr_mask 枚举(第 1271 行)控制哪些属性生效:
enum ib_qp_attr_mask {
IB_QP_STATE = 1, // QP 状态
IB_QP_CUR_STATE = (1<<1), // 当前状态(用于条件转换)
IB_QP_ACCESS_FLAGS = (1<<3), // 访问权限
IB_QP_PKEY_INDEX = (1<<4), // PKey 表索引
IB_QP_PORT = (1<<5), // 端口号
IB_QP_AV = (1<<7), // 地址向量(目标地址)
IB_QP_PATH_MTU = (1<<8), // 路径 MTU
IB_QP_TIMEOUT = (1<<9), // 重传超时
IB_QP_RETRY_CNT = (1<<10),// 重传次数
IB_QP_RNR_RETRY = (1<<11),// RNR 重试次数
IB_QP_RQ_PSN = (1<<12),// 接收队列 PSN(包序列号)
IB_QP_MAX_QP_RD_ATOM = (1<<13),// 最大未完成 RDMA Read 数
IB_QP_MIN_RNR_TIMER = (1<<15),// 最小 RNR NAK 定时器
IB_QP_SQ_PSN = (1<<16),// 发送队列 PSN
IB_QP_MAX_DEST_RD_ATOM= (1<<17),// 目标端最大 RDMA Read 数
IB_QP_DEST_QPN = (1<<20),// 目标 QP 号
// ...
};RESET --> INIT --> RTR --> RTS
(无需配置 PATH_MTU、DEST_QPN 等,每次发送通过 AH 指定目标)
发送 WR (ib_send_wr,定义于第 1415 行):
struct ib_send_wr {
struct ib_send_wr *next; // 链表(可批量提交多个 WR)
union {
u64 wr_id; // 用户自定义标识符(在 WC 中返回)
struct ib_cqe *wr_cqe; // 或使用 CQE 回调模式
};
struct ib_sge *sg_list; // scatter-gather 列表
int num_sge; // SGE 数量
enum ib_wr_opcode opcode; // 操作类型
int send_flags; // IB_SEND_SIGNALED | IB_SEND_INLINE 等
union {
__be32 imm_data; // 即时数据(SEND_WITH_IMM)
u32 invalidate_rkey; // 失效 rkey(SEND_WITH_INV)
} ex;
};
/* Scatter-Gather Element */
struct ib_sge {
u64 addr; // 虚拟地址(用户空间地址)
u32 length; // 段长度(字节)
u32 lkey; // 本地内存键(来自 MR)
};RDMA WR(RDMA Read/Write 专用扩展,第 1431 行):
struct ib_rdma_wr {
struct ib_send_wr wr;
u64 remote_addr; // 目标远端内存虚拟地址(IOVA)
u32 rkey; // 目标端 MR 的 rkey
};接收 WR(第 1486 行):
struct ib_recv_wr {
struct ib_recv_wr *next;
union {
u64 wr_id;
struct ib_cqe *wr_cqe;
};
struct ib_sge *sg_list; // 接收缓冲区 SGE 列表
int num_sge;
};定义于第 1353 行:
enum ib_wr_opcode {
IB_WR_RDMA_WRITE = 0, // RDMA Write(单向,无需对端 RR)
IB_WR_RDMA_WRITE_WITH_IMM = 1, // RDMA Write + 即时数据(对端生成 RR WC)
IB_WR_SEND = 2, // 发送(对端需 post_recv)
IB_WR_SEND_WITH_IMM = 3, // 发送 + 即时数据
IB_WR_RDMA_READ = 4, // RDMA Read(从对端内存读取)
IB_WR_ATOMIC_CMP_AND_SWP = 8, // 原子比较并交换
IB_WR_ATOMIC_FETCH_AND_ADD= 9, // 原子获取并加
IB_WR_SEND_WITH_INV = 13, // 发送 + 失效 rkey
IB_WR_RDMA_READ_WITH_INV = 14, // RDMA Read + 失效 rkey
IB_WR_LOCAL_INV = 15, // 本地失效 MR
IB_WR_REG_MR = 0x20,// Fast Registration MR(仅内核)
};内核态通过 ib_post_send() / ib_post_recv() 提交工作请求,这两个是内联函数,直接调用驱动的函数指针(避免函数调用开销):
// include/rdma/ib_verbs.h(约第 2350 行附近的内联封装)
static inline int ib_post_send(struct ib_qp *qp,
const struct ib_send_wr *send_wr,
const struct ib_send_wr **bad_send_wr)
{
const struct ib_send_wr *dummy;
return qp->device->ops.post_send(qp, send_wr,
bad_send_wr ? : &dummy);
}
static inline int ib_post_recv(struct ib_qp *qp,
const struct ib_recv_wr *recv_wr,
const struct ib_recv_wr **bad_recv_wr)
{
const struct ib_recv_wr *dummy;
return qp->device->ops.post_recv(qp, recv_wr,
bad_recv_wr ? : &dummy);
}// 轮询 CQ 获取完成条目
static inline int ib_poll_cq(struct ib_cq *cq, int num_entries,
struct ib_wc *wc)
{
return cq->device->ops.poll_cq(cq, num_entries, wc);
}
// 请求完成通知(下一个完成时通知)
static inline int ib_req_notify_cq(struct ib_cq *cq,
enum ib_cq_notify_flags flags)
{
return cq->device->ops.req_notify_cq(cq, flags);
}应用 / ULP 代码
|
| 1. 构建 ib_send_wr 链表
| wr.opcode = IB_WR_RDMA_WRITE
| wr.sg_list[0] = { .addr = buf_va, .length = len, .lkey = mr->lkey }
| rdma_wr(&wr)->remote_addr = remote_va
| rdma_wr(&wr)->rkey = remote_rkey
|
| 2. ib_post_send(qp, &wr, &bad_wr)
| --> device->ops.post_send() [驱动实现]
| 在 WQE ring 中写入描述符
| 触发硬件 doorbell(写 MMIO 寄存器)
|
| 3. 硬件 DMA 数据到对端内存(零 CPU 介入)
|
| 4a. 轮询模式:ib_poll_cq(cq, 1, &wc)
| --> device->ops.poll_cq()
| 从 CQE ring 读取完成条目
|
| 4b. 中断模式:硬件触发 MSI-X
| --> comp_handler(cq, context)
| --> ib_poll_cq() 处理积累的 WC
定义于 include/rdma/ib_verbs.h 第 1496 行:
enum ib_access_flags {
IB_ACCESS_LOCAL_WRITE = 1, // 允许本地 DMA 写入
IB_ACCESS_REMOTE_WRITE = (1<<1),// 允许远端 RDMA Write
IB_ACCESS_REMOTE_READ = (1<<2),// 允许远端 RDMA Read
IB_ACCESS_REMOTE_ATOMIC = (1<<3),// 允许远端 Atomic 操作
IB_ACCESS_MW_BIND = (1<<4),// 允许绑定 Memory Window
IB_ZERO_BASED = (1<<5),// 零基址 MR(IOVA 从 0 开始)
IB_ACCESS_ON_DEMAND = (1<<6),// 按需页注册(ODP)
IB_ACCESS_HUGETLB = (1<<7),// 使用大页
IB_ACCESS_RELAXED_ORDERING = (1<<19), // 放宽写顺序(提升性能)
};① 用户 MR(reg_user_mr)
最常用的方式,应用传入虚拟地址区间,内核 pin 住页面:
reg_user_mr(pd, start_va, length, virt_addr, access_flags, ...)
--> ib_umem_get() -- pin 住用户页,获得物理地址列表
--> 驱动 reg_user_mr() -- 写入 HCA 的 MPT(内存保护表)和 MTT(内存翻译表)
--> 返回 ib_mr { lkey, rkey }
② Fast Registration MR(Fast Reg)
用于频繁变更注册区域的场景(如 NVMe-oF 每次 I/O 注册不同数据区域):
1. alloc_mr(pd, IB_MR_TYPE_MEM_REG, max_pages) -- 预分配 MR 对象
2. ib_map_mr_sg(mr, sg, sg_nents, ...) -- 填充物理页列表
3. post_send(wr),wr.opcode = IB_WR_REG_MR -- 通过 WQE 激活 MR
4. 使用 mr->lkey / mr->rkey 进行数据传输
5. post_send(wr),wr.opcode = IB_WR_LOCAL_INV -- 失效 MR
③ DMA MR(get_dma_mr)
用于不需要地址翻译的场景(物理地址直接映射),通常只有内核使用:
// verbs.c 第 355 行 -- 在 __ib_alloc_pd() 中内部使用
mr = pd->device->ops.get_dma_mr(pd, mr_access_flags);
pd->local_dma_lkey = pd->__internal_mr->lkey;ODP 允许不预先 pin 住全部页面,而是按需建立 DMA 映射。rxe.c 第 92 行展示了 SoftRoCE 的 ODP 能力配置:
if (IS_ENABLED(CONFIG_INFINIBAND_ON_DEMAND_PAGING)) {
rxe->attr.kernel_cap_flags |= IBK_ON_DEMAND_PAGING;
rxe->attr.odp_caps.general_caps |= IB_ODP_SUPPORT;
rxe->attr.odp_caps.per_transport_caps.rc_odp_caps |=
IB_ODP_SUPPORT_SEND |
IB_ODP_SUPPORT_RECV |
IB_ODP_SUPPORT_WRITE |
IB_ODP_SUPPORT_READ |
IB_ODP_SUPPORT_ATOMIC |
IB_ODP_SUPPORT_FLUSH |
IB_ODP_SUPPORT_ATOMIC_WRITE;
}ODP 通过 mmu_notifier 机制响应内核页面迁移/回收事件。
发起方(Initiator) 目标方(Target)
----------------------- -----------------------
1. 双方已建立 RC QP 连接
2. Target 提前注册内存并将 rkey/iova 发给 Initiator(通过 Send/Recv)
3. Initiator 构建 RDMA Write WR:
wr.opcode = IB_WR_RDMA_WRITE
rdma_wr.remote_addr = target_iova (目标虚拟地址)
rdma_wr.rkey = target_rkey (目标 MR 的 rkey)
wr.sg_list[0].addr = local_buf_va (本地数据地址)
wr.sg_list[0].lkey = local_mr->lkey (本地 MR 的 lkey)
4. ib_post_send() 4. Target CPU 完全不参与!
|
v
5. 硬件 DMA 读取本地内存
6. 通过网络发送 RDMA Write 报文
------ RDMA Write Packet -------->
7. 目标 HCA 收到数据包
8. DMA 写入目标内存
9. 无需生成接收 WC!
10. 发起方 HCA 生成发送完成 WC
ib_poll_cq() 获取 WC
wc.opcode = IB_WC_RDMA_WRITE
wc.status = IB_WC_SUCCESS
发起方(Initiator) 目标方(Target)
----------------------- -----------------------
1. Target 提前注册内存,告知 rkey/iova
2. Initiator 构建 RDMA Read WR:
wr.opcode = IB_WR_RDMA_READ
rdma_wr.remote_addr = target_iova (从对端此地址读取)
rdma_wr.rkey = target_rkey (对端 MR 的 rkey)
wr.sg_list[0].addr = local_buf_va (写入到本地此地址)
wr.sg_list[0].lkey = local_mr->lkey
3. ib_post_send() 3. Target CPU 不参与
|
v
4. 发送 RDMA Read Request 报文
------ RDMA Read Req --------->
5. 目标 HCA 读取目标内存
DMA 传输数据
<------ RDMA Read Response ----
6. 发起方 HCA 将数据 DMA 写入本地缓冲区
7. 生成读完成 WC
wc.opcode = IB_WC_RDMA_READ
wc.byte_len = 实际读取字节数
IB_WR_RDMA_WRITE_WITH_IMM 将 RDMA Write 和 32 位即时数据组合:
- 目标端不需要提供接收缓冲区(RDMA Write 直接写到已知内存)
- 但目标端会生成一个 Recv WC(
IB_WC_RECV_RDMA_WITH_IMM),携带即时数据 - 常用于通知对端"数据已就绪并带有序号/标志"
rdma_cm 提供与 socket 类似的连接建立抽象,屏蔽底层 IB CM / iWARP CM 差异。
rdma_cm.h 第 120 行定义的 rdma_cm_id:
struct rdma_cm_id {
struct ib_device *device; // 绑定的 RDMA 设备
void *context; // 用户自定义上下文
struct ib_qp *qp; // 关联的 QP(可选)
rdma_cm_event_handler event_handler; // 事件回调
struct rdma_route route; // 路由信息(路径记录)
enum rdma_ucm_port_space ps; // 端口空间(TCP/UDP/IB)
enum ib_qp_type qp_type; // QP 类型
u32 port_num; // 本地端口
struct work_struct net_work;
};rdma_cm_event_type 枚举(rdma_cm.h 第 20 行):
enum rdma_cm_event_type {
RDMA_CM_EVENT_ADDR_RESOLVED, // 地址解析完成
RDMA_CM_EVENT_ADDR_ERROR, // 地址解析失败
RDMA_CM_EVENT_ROUTE_RESOLVED, // 路由解析完成
RDMA_CM_EVENT_ROUTE_ERROR, // 路由解析失败
RDMA_CM_EVENT_CONNECT_REQUEST, // 收到连接请求(服务端)
RDMA_CM_EVENT_CONNECT_RESPONSE, // 收到连接响应(客户端)
RDMA_CM_EVENT_CONNECT_ERROR, // 连接建立失败
RDMA_CM_EVENT_REJECTED, // 连接被拒绝
RDMA_CM_EVENT_ESTABLISHED, // 连接建立成功
RDMA_CM_EVENT_DISCONNECTED, // 连接断开
RDMA_CM_EVENT_DEVICE_REMOVAL, // 设备热拔出
RDMA_CM_EVENT_TIMEWAIT_EXIT, // 等待时间结束
// ...
};cma.c 第 52 行的事件名称映射:
static const char * const cma_events[] = {
[RDMA_CM_EVENT_ADDR_RESOLVED] = "address resolved",
[RDMA_CM_EVENT_ROUTE_RESOLVED] = "route resolved ",
[RDMA_CM_EVENT_CONNECT_REQUEST] = "connect request",
[RDMA_CM_EVENT_ESTABLISHED] = "established",
[RDMA_CM_EVENT_DISCONNECTED] = "disconnected",
// ...
};服务端
------
1. rdma_create_id(net, event_handler, context, RDMA_PS_TCP, IB_QPT_RC)
--> 分配 rdma_cm_id,注册到 cma_client
2. rdma_bind_addr(id, &local_sockaddr)
--> 绑定本地 IP:Port,关联 RDMA 设备
3. rdma_listen(id, backlog)
--> 在底层 IB CM 上注册监听服务 ID(Service ID)
--> cma_listen_handler 开始监听
4. 收到连接请求 --> event_handler(RDMA_CM_EVENT_CONNECT_REQUEST)
4a. 为新连接创建 child_id(rdma_create_id)
4b. 创建 QP(rdma_create_qp 或手动 ib_create_qp)
4c. rdma_accept(child_id, &conn_param)
--> 发送 CM REP(Reply)报文
5. event_handler(RDMA_CM_EVENT_ESTABLISHED)
--> QP 进入 RTS 状态,连接就绪
6. 数据传输(ib_post_send / ib_post_recv)
7. rdma_disconnect(id)
8. rdma_destroy_qp(id)
9. rdma_destroy_id(id)
客户端
------
1. rdma_create_id(...)
--> 创建 rdma_cm_id
2. rdma_resolve_addr(id, src_addr, dst_addr, timeout_ms)
--> IP 地址解析到 GID(RoCE)或 LID(IB)
--> 内部使用 ARP/ND 解析,或 SA 查询
--> 完成后:event_handler(RDMA_CM_EVENT_ADDR_RESOLVED)
3. rdma_resolve_route(id, timeout_ms)
--> 查询 SA(Subnet Administrator)获取路径记录(Path Record)
--> 包含 SL、MTU、速率等信息
--> 完成后:event_handler(RDMA_CM_EVENT_ROUTE_RESOLVED)
4. rdma_create_qp(id, pd, &qp_init_attr)
--> CMA 自动管理 QP 状态转换
5. rdma_connect(id, &conn_param)
--> conn_param.private_data 可携带应用层握手数据
--> 发送 CM REQ 报文
6. event_handler(RDMA_CM_EVENT_ESTABLISHED)
--> 连接建立,可以开始传输
7. 数据传输(ib_post_send / ib_post_recv)
8. rdma_disconnect(id)
9. rdma_destroy_id(id)
cma.c 使用 xarray 维护按端口空间分类的连接表(第 167 行):
struct cma_pernet {
struct xarray tcp_ps; // RDMA_PS_TCP 连接
struct xarray udp_ps; // RDMA_PS_UDP 连接
struct xarray ipoib_ps; // RDMA_PS_IPOIB 连接
struct xarray ib_ps; // RDMA_PS_IB 连接
};CMA 作为 IB 客户端注册(第 151 行):
static struct ib_client cma_client = {
.name = "cma",
.add = cma_add_one, // 新 RDMA 设备出现时回调
.remove = cma_remove_one // RDMA 设备移除时回调
};rdma_conn_param(rdma_cm.h 第 75 行)携带连接握手参数:
struct rdma_conn_param {
const void *private_data; // 应用层握手数据(最大 196 字节)
u8 private_data_len;
u8 responder_resources; // 我方最大响应 RDMA Read 数
u8 initiator_depth; // 我方最大发起 RDMA Read 数
u8 flow_control;
u8 retry_count;
u8 rnr_retry_count;
u32 qp_num;
u32 qkey;
};SoftRoCE 是一个完全用软件实现的 RoCE v2 驱动,允许在任何标准以太网网卡上使用 RDMA verbs。
static int __init rxe_module_init(void)
{
int err;
err = rxe_alloc_wq(); // 分配工作队列
if (err)
return err;
err = rxe_net_init(); // 初始化网络发送模块(UDP socket)
if (err) {
rxe_destroy_wq();
return err;
}
rdma_link_register(&rxe_link_ops); // 注册 rxe 链路操作
pr_info("loaded\n");
return 0;
}通过 rdma link add rxe0 netdev eth0 命令在以太网设备上创建 RXE 设备。
static void rxe_init_device_param(struct rxe_dev *rxe, struct net_device *ndev)
{
rxe->max_inline_data = RXE_MAX_INLINE_DATA;
rxe->attr.vendor_id = RXE_VENDOR_ID;
rxe->attr.max_mr_size = RXE_MAX_MR_SIZE;
rxe->attr.max_qp = RXE_MAX_QP;
rxe->attr.max_qp_wr = RXE_MAX_QP_WR;
rxe->attr.max_cq = RXE_MAX_CQ;
rxe->attr.max_cqe = (1 << RXE_MAX_LOG_CQE) - 1;
rxe->attr.max_mr = RXE_MAX_MR;
rxe->attr.max_pd = RXE_MAX_PD;
rxe->attr.atomic_cap = IB_ATOMIC_HCA; // 支持 HCA 级原子操作
rxe->attr.max_fast_reg_page_list_len = RXE_MAX_FMR_PAGE_LIST_LEN;
rxe->attr.max_pkeys = RXE_MAX_PKEYS;
rxe->attr.local_ca_ack_delay = RXE_LOCAL_CA_ACK_DELAY;
// 从网卡 MAC 地址生成 GID
if (ndev->addr_len) {
memcpy(rxe->raw_gid, ndev->dev_addr, min_t(unsigned int,
ndev->addr_len, ETH_ALEN));
} else {
eth_random_addr(rxe->raw_gid); // 随机生成(虚拟设备)
}
addrconf_addr_eui48((unsigned char *)&rxe->attr.sys_image_guid,
rxe->raw_gid);
}SoftRoCE 使用对象池管理所有 RDMA 资源:
static void rxe_init_pools(struct rxe_dev *rxe)
{
rxe_pool_init(rxe, &rxe->uc_pool, RXE_TYPE_UC); // 用户上下文池
rxe_pool_init(rxe, &rxe->pd_pool, RXE_TYPE_PD); // 保护域池
rxe_pool_init(rxe, &rxe->ah_pool, RXE_TYPE_AH); // 地址句柄池
rxe_pool_init(rxe, &rxe->srq_pool, RXE_TYPE_SRQ); // 共享接收队列池
rxe_pool_init(rxe, &rxe->qp_pool, RXE_TYPE_QP); // QP 池
rxe_pool_init(rxe, &rxe->cq_pool, RXE_TYPE_CQ); // 完成队列池
rxe_pool_init(rxe, &rxe->mr_pool, RXE_TYPE_MR); // 内存区域池
rxe_pool_init(rxe, &rxe->mw_pool, RXE_TYPE_MW); // 内存窗口池
}发送路径(rxe_requester):
ib_post_send()
--> rxe_post_send() 将 WR 写入发送队列
--> 调度 rxe_requester 任务处理器(rxe_task)
--> rxe_requester() 处理 WQE,生成 RoCE 报文
--> rxe_xmit_packet() 封装 IB header + BTH
--> rxe_send() 通过 UDP socket 发送
--> udp_tunnel_xmit_skb() 调用内核 UDP 发送路径
接收路径(rxe_responder):
网络栈接收 UDP 包
--> rxe_udp_encap_recv() UDP 封装解封装
--> rxe_recv_skb() 解析 RoCE 报文头
--> rxe_responder() 处理接收端逻辑
--> 写入 CQ(生成 WC)
--> ib_poll_cq() 用户读取完成事件
+------------------+
| Ethernet Header | (14 字节)
+------------------+
| IP Header (v4/v6)| (20/40 字节)
+------------------+
| UDP Header | (8 字节),目标端口 4791(ROCE_V2_UDP_DPORT)
+------------------+
| IB BTH | Base Transport Header(12 字节)
| (opcode, P_Key, | OpCode(1B) + SE/M/Pad/TVer(1B) + P_Key(2B)
| QPN, PSN) | + Resv(1B) + DestQP(3B) + A/PSN(4B)
+------------------+
| IB ExtHdr | 扩展头(RDMA RETH/AETH 等)
+------------------+
| Payload | 数据
+------------------+
| ICRC | Invariant CRC(4 字节)
+------------------+
RoCE v2 使用 UDP 端口 4791(定义于 ib_verbs.h 第 150 行):
#define ROCE_V2_UDP_DPORT 4791
每个 RDMA 设备对应一个字符设备文件,用户态通过 ioctl 与内核交互:
/dev/infiniband/
uverbs0 -- 第一个 RDMA 设备的 verbs 接口
uverbs1 -- 第二个 RDMA 设备
rdma_cm -- 连接管理接口(全局)
umad0 -- Subnet Manager Agent 接口
ib_ucontext(第 1549 行)代表每个用户进程的上下文:
struct ib_ucontext {
struct ib_device *device;
struct ib_uverbs_file *ufile;
struct ib_rdmacg_object cg_obj; // cgroup RDMA 控制
u64 enabled_caps;
struct rdma_restrack_entry res;
struct xarray mmap_xa; // mmap 地址映射(QP/CQ 环形队列)
};ib_uobject(第 1562 行)是所有用户态 RDMA 对象的基类:
struct ib_uobject {
u64 user_handle; // 用户空间传入的句柄值
struct ib_uverbs_file *ufile;
struct ib_ucontext *context;
void *object; // 指向实际内核对象(ib_qp 等)
struct list_head list;
int id; // 内核 idr 索引(用户空间 fd)
struct kref ref;
atomic_t usecnt; // 防止并发销毁
struct rcu_head rcu;
};用户空间 libibverbs 内核 ib_uverbs
---------------------- ----------------------
ibv_alloc_pd(context)
--> write(fd, cmd_alloc_pd, ...)
--> ioctl(fd, IB_IOCTL_VERBS, ...)
--> ib_uverbs_alloc_pd()
--> __ib_alloc_pd()
--> device->ops.alloc_pd()
--> 返回 uobject.id (handle)
ibv_create_qp(pd, &attr)
--> write(fd, cmd_create_qp, ...)
--> ib_uverbs_create_qp()
--> ib_create_qp_kernel()
--> mmap 将 WQ doorbell/ring 映射到用户空间
/* 热路径:无 syscall */
ibv_post_send(qp, wr, &bad_wr)
--> 直接写 WQE 到 mmap 的发送队列环形缓冲区
--> 写 MMIO doorbell 寄存器通知硬件(1 次 IO 写,非 syscall)
ibv_poll_cq(cq, 1, &wc)
--> 直接读 mmap 的 CQ 环形缓冲区
--> 无 syscall!
硬件 QP/CQ 的环形队列通过 mmap 映射到用户空间,实现零 syscall 的热路径。驱动通过 ops.mmap() 实现具体映射:
// ib_device_ops 中的 mmap 相关操作(第 2500 行)
int (*mmap)(struct ib_ucontext *context, struct vm_area_struct *vma);
void (*mmap_free)(struct rdma_user_mmap_entry *entry);ib_ucontext.mmap_xa 是 xarray,存储所有 mmap 映射条目(从 page offset 到物理地址)。
include/uapi/rdma/
ib_user_verbs.h -- 用户态 ioctl 命令结构体
rdma_user_cm.h -- 用户态 CM ioctl 结构体
ib_user_ioctl_verbs.h -- 新版 ioctl 接口(uverbs_ioctl)
rdma_user_ioctl.h -- RDMA 通用 ioctl 定义
drivers/nvme/host/rdma.c 实现 NVMe over Fabrics 的 RDMA 传输。
关键数据结构(第 42 行):
struct nvme_rdma_device {
struct ib_device *dev;
struct ib_pd *pd; // 所有队列共享一个 PD
struct kref ref;
struct list_head entry;
unsigned int num_inline_segments;
};
struct nvme_rdma_queue {
struct nvme_rdma_qe *rsp_ring; // 接收响应的环形缓冲区
int queue_size;
size_t cmnd_capsule_len;
struct nvme_rdma_ctrl *ctrl;
struct nvme_rdma_device *device;
struct ib_cq *ib_cq; // 完成队列
struct ib_qp *qp; // RC QP(每个 NVMe 队列一个)
unsigned long flags;
struct rdma_cm_id *cm_id; // 连接管理 ID
int cm_error;
struct completion cm_done;
bool pi_support; // 数据完整性支持
int cq_size;
};
struct nvme_rdma_request {
struct nvme_request req;
struct ib_mr *mr; // Fast Registration MR(每请求)
struct nvme_rdma_qe sqe;
struct ib_sge sge[1 + NVME_RDMA_MAX_INLINE_SEGMENTS];
u32 num_sge;
struct ib_reg_wr reg_wr; // Fast Reg WR(激活 MR)
struct ib_cqe reg_cqe;
struct nvme_rdma_sgl data_sgl; // 数据 scatter-gather 列表
bool use_sig_mr; // 是否使用 T10-PI MR
};NVMe-RDMA 连接常量(第 31 行):
#define NVME_RDMA_CM_TIMEOUT_MS 3000 // 3 秒连接超时
#define NVME_RDMA_MAX_SEGMENTS 256 // 最大 SGL 段数
#define NVME_RDMA_MAX_INLINE_SEGMENTS 4 // 最大内联段数数据传输流程:
NVMe 读命令
1. 分配 Fast Reg MR,map_mr_sg() 填充数据缓冲区页列表
2. post IB_WR_REG_MR WR -- 激活 MR,得到 lkey/rkey
3. 发送 NVMe Read Command Capsule(包含 SGl rkey/iova)
4. NVMe Target 收到命令,执行 RDMA Write 将数据写回 initiator
5. Initiator 收到 RDMA Write(无需接收 WR)
6. Target 发送 NVMe Response(含 SQ head 指针)
7. Initiator 收到响应,完成 I/O
8. post IB_WR_LOCAL_INV -- 失效 MR
drivers/infiniband/ulp/iser/ 实现 iSCSI 的 RDMA 加速。
模块信息(iscsi_iser.c 第 77 行):
MODULE_DESCRIPTION("iSER (iSCSI Extensions for RDMA) Datamover");
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("Alex Nezhinsky, Dan Bar Dov, Or Gerlitz");iSER 架构:
iSCSI 层(SCSI commands)
|
| iSER Datamover(替换原本的 TCP socket 传输)
|
RDMA Verbs 层
|
InfiniBand / RoCE 网卡
iSER 使用 rdma_cm 建立连接,使用 RC QP 传输数据,通过 RDMA Write/Read 实现零拷贝数据传输,绕过内核 TCP 缓冲区。
drivers/infiniband/ulp/ipoib/ 实现在 InfiniBand 网络上承载 IP 流量:
- 使用 UD QP(Unreliable Datagram)进行 IP 包传输
- 也支持 Connected Mode(使用 RC QP,提升性能但增加连接数)
- InfiniBand 多播用于 ARP 等广播协议
RDMA 设备驱动通过以下步骤注册:
驱动探测 PCI 设备
--> ib_alloc_device(sizeof(struct mlx5_ib_dev))
内部分配 ib_device + 驱动私有数据(嵌入式)
--> 填充 device->ops(所有函数指针)
--> ib_register_device(ibdev, "mlx5_%d", &pdev->dev)
|
--> sysfs 注册(/sys/class/infiniband/mlx5_0/)
--> 字符设备创建(/dev/infiniband/uverbs0)
--> 通知所有 ib_client(cma_client, ipoib_client 等)
GID(Global ID)是 128 位全局唯一标识符,用于路由(类似 IPv6 地址)。
定义于第 133 行:
union ib_gid {
u8 raw[16];
struct {
__be64 subnet_prefix; // 高 64 位:子网前缀
__be64 interface_id; // 低 64 位:接口 ID(通常从 MAC 派生)
} global;
};GID 类型(第 143 行):
enum ib_gid_type {
IB_GID_TYPE_IB = 0, // 纯 IB GID
IB_GID_TYPE_ROCE = 1, // RoCE v1 GID
IB_GID_TYPE_ROCE_UDP_ENCAP = 2, // RoCE v2 GID(UDP 封装)
};verbs.c 第 128 行展示了 IB 速率的完整支持范围:
__attribute_const__ int ib_rate_to_mult(enum ib_rate rate)
{
switch (rate) {
case IB_RATE_2_5_GBPS: return 1; // SDR:2.5 Gb/s
case IB_RATE_5_GBPS: return 2; // DDR:5 Gb/s
case IB_RATE_10_GBPS: return 4; // QDR/FDR10:10 Gb/s
case IB_RATE_25_GBPS: return 10; // HDR100:25 Gb/s
case IB_RATE_50_GBPS: return 20; // HDR:50 Gb/s/lane
case IB_RATE_100_GBPS: return 40; // HDR:100 Gb/s (2x50)
case IB_RATE_200_GBPS: return 80; // HDR:200 Gb/s
case IB_RATE_400_GBPS: return 160; // NDR:400 Gb/s
case IB_RATE_800_GBPS: return 320; // XDR:800 Gb/s
case IB_RATE_1600_GBPS: return 640; // 未来标准
// ...
}
}端口速度从 SDR(2.5 Gb/s)到 XDR(1600 Gb/s),通过 IB_SPEED_* 枚举和端口宽度(IB_WIDTH_1X/4X/12X)组合计算实际带宽。
IB 事件通过 ib_event 结构体传递,定义于第 774 行:
struct ib_event {
struct ib_device *device;
union {
struct ib_cq *cq; // CQ 相关事件
struct ib_qp *qp; // QP 相关事件(QP_FATAL/QP_ACCESS_ERR)
struct ib_srq *srq; // SRQ 相关事件
u32 port_num;
} element;
enum ib_event_type event;
};verbs.c 第 61 行的事件字符串表(实际内容):
static const char * const ib_events[] = {
[IB_EVENT_CQ_ERR] = "CQ error",
[IB_EVENT_QP_FATAL] = "QP fatal error",
[IB_EVENT_QP_REQ_ERR] = "QP request error",
[IB_EVENT_QP_ACCESS_ERR] = "QP access error",
[IB_EVENT_COMM_EST] = "communication established",
[IB_EVENT_SQ_DRAINED] = "send queue drained",
[IB_EVENT_PATH_MIG] = "path migration successful",
[IB_EVENT_PATH_MIG_ERR] = "path migration error",
[IB_EVENT_DEVICE_FATAL] = "device fatal error",
[IB_EVENT_PORT_ACTIVE] = "port active",
[IB_EVENT_PORT_ERR] = "port error",
[IB_EVENT_LID_CHANGE] = "LID change",
[IB_EVENT_PKEY_CHANGE] = "P_key change",
[IB_EVENT_SM_CHANGE] = "SM change",
[IB_EVENT_SRQ_ERR] = "SRQ error",
[IB_EVENT_SRQ_LIMIT_REACHED]= "SRQ limit reached",
[IB_EVENT_QP_LAST_WQE_REACHED] = "last WQE reached",
[IB_EVENT_CLIENT_REREGISTER] = "client reregister",
[IB_EVENT_GID_CHANGE] = "GID changed",
};内核支持通过 cgroup 限制 RDMA 资源使用(CONFIG_CGROUP_RDMA):
// ib_pd 和 ib_uobject 中的 cgroup 对象
struct ib_rdmacg_object {
#ifdef CONFIG_CGROUP_RDMA
struct rdma_cgroup *cg; // 所属 RDMA cgroup
#endif
};ib_cq 中的 struct dim *dim 实现 RDMA DIM(Dynamic Interrupt Moderation),类似网卡的 ethtool coalesce 参数,自动调节中断聚合以平衡延迟和吞吐:
struct ib_cq {
// ...
u16 use_cq_dim:1; // ib_device 中的 use_cq_dim 标志
struct dim *dim; // DIM 算法状态机
// ...
};# 列出 RDMA 设备
rdma dev
# 显示设备链路信息
rdma link
# 查看 QP 资源使用
rdma res show qp
# 查看 MR 资源
rdma res show mr
# 查看 CQ 资源
rdma res show cq
# 查看性能统计
rdma stat
# SoftRoCE:在以太网设备上创建 rxe 设备
rdma link add rxe0 type rxe netdev eth0/sys/class/infiniband/
mlx5_0/ # 设备目录
fw_ver # 固件版本
node_type # 节点类型(CA/Switch/Router)
node_guid # 设备 GUID
ports/
1/ # 端口 1
state # 端口状态(ACTIVE/DOWN)
phys_state # 物理状态
rate # 速率(如 "400 Gb/sec (4X NDR)")
gids/ # GID 表
0 # GID 0
pkeys/ # P_Key 表
0
counters/ # 性能计数器
port_rcv_packets
port_xmit_packets
port_rcv_errors
drivers/infiniband/core/verbs.c 引入了 RDMA 核心 tracepoints:
#include <trace/events/rdma_core.h>可用 tracepoints 包括:
rdma_core:mr_integ_alloc:MR 分配rdma_core:post_send:发送 WR- 通过
trace-cmd或perf捕获
# 查看 InfiniBand 性能计数器
perfquery -x
# 诊断连接问题
ibping -S # 服务端
ibping <LID> # 客户端
# 带宽测试(使用 RDMA Write)
ib_write_bw -d mlx5_0 -p 18515
# 延迟测试
ib_write_lat -d mlx5_0
# RDMA Read 带宽测试
ib_read_bw -d mlx5_0QP 进入 ERR 状态
- 检查 WC 的
status字段(ib_wc_status_msg(status)获取可读字符串) - 常见原因:remote access error(对端 MR rkey 已失效/权限不足)
- 常见原因:retry exceeded(网络丢包或对端宕机)
内存注册失败
- 检查
/proc/sys/vm/nr_hugepages(大页配置) - 检查
ulimit -l(RLIMIT_MEMLOCK 内存锁定限制) ENOMEM:MR 数量超过max_mr(ibv_query_device查询)
RNR(Receiver Not Ready)
- 接收方未 post_recv,发送方重试超限
- 增大接收队列深度或提高 post_recv 频率
- 调整
min_rnr_timer和rnr_retry参数
drivers/infiniband/core/cq.c 中的 __ib_alloc_cq() 是内核态 CQ 分配的标准接口(第 212 行):
struct ib_cq *__ib_alloc_cq(struct ib_device *dev, void *private, int nr_cqe,
int comp_vector, enum ib_poll_context poll_ctx,
const char *caller)
{
struct ib_cq_init_attr cq_attr = {
.cqe = nr_cqe,
.comp_vector = comp_vector,
};
struct ib_cq *cq;
int ret = -ENOMEM;
cq = rdma_zalloc_drv_obj(dev, ib_cq); // 驱动自定义大小
if (!cq)
return ERR_PTR(ret);
cq->device = dev;
cq->cq_context = private;
cq->poll_ctx = poll_ctx;
atomic_set(&cq->usecnt, 0);
cq->comp_vector = comp_vector;
cq->wc = kmalloc_objs(*cq->wc, IB_POLL_BATCH); // 预分配 WC 数组
if (!cq->wc)
goto out_free_cq;
rdma_restrack_new(&cq->res, RDMA_RESTRACK_CQ);
rdma_restrack_set_name(&cq->res, caller);
ret = dev->ops.create_cq(cq, &cq_attr, NULL); // 调用驱动
if (ret)
goto out_free_wc;
rdma_dim_init(cq); // 初始化 DIM 动态中断调节
switch (cq->poll_ctx) {
case IB_POLL_DIRECT:
cq->comp_handler = ib_cq_completion_direct; // 直接模式
break;
case IB_POLL_SOFTIRQ:
cq->comp_handler = ib_cq_completion_softirq;
irq_poll_init(&cq->iop, IB_POLL_BUDGET_IRQ, ib_poll_handler);
ib_req_notify_cq(cq, IB_CQ_NEXT_COMP);
break;
case IB_POLL_WORKQUEUE:
case IB_POLL_UNBOUND_WORKQUEUE:
cq->comp_handler = ib_cq_completion_workqueue;
INIT_WORK(&cq->work, ib_cq_poll_work);
ib_req_notify_cq(cq, IB_CQ_NEXT_COMP);
cq->comp_wq = (cq->poll_ctx == IB_POLL_WORKQUEUE) ?
ib_comp_wq : ib_comp_unbound_wq;
break;
}
rdma_restrack_add(&cq->res);
trace_cq_alloc(cq, nr_cqe, comp_vector, poll_ctx);
return cq;
// ...
}drivers/infiniband/core/cq.c 开头定义了关键的轮询预算(第 13 行):
/* Max size for shared CQ, may require tuning */
#define IB_MAX_SHARED_CQ_SZ 4096U
/* # of WCs to poll for with a single call to ib_poll_cq */
#define IB_POLL_BATCH 16
#define IB_POLL_BATCH_DIRECT 8
/* # of WCs to iterate over before yielding */
#define IB_POLL_BUDGET_IRQ 256
#define IB_POLL_BUDGET_WORKQUEUE 65536这些常量决定 CQ 轮询的批次大小和让权时机,对延迟/吞吐的平衡至关重要。
CQ 的 DIM 功能通过 9 档配置文件实现(cq.c 第 27 行):
static const struct dim_cq_moder
rdma_dim_prof[RDMA_DIM_PARAMS_NUM_PROFILES] = {
{1, 0, 1, 0}, // 配置 0:最低延迟(1 WC,0 usec)
{1, 0, 4, 0},
{2, 0, 4, 0},
{2, 0, 8, 0},
{4, 0, 8, 0},
{16, 0, 8, 0},
{16, 0, 16, 0},
{32, 0, 16, 0},
{32, 0, 32, 0}, // 配置 8:最高吞吐(32 WC,32 usec 聚合)
};格式为 {comps, pkts, usec, pkts_bias},DIM 算法根据当前负载在这 9 档配置间自动切换,调用 ops.modify_cq(cq, comps, usec) 更新硬件中断聚合参数。
内核提供 CQ 池机制(cq.c 第 436 行),允许多个 QP 共享同一个 CQ,减少系统资源消耗:
struct ib_cq *ib_cq_pool_get(struct ib_device *dev, unsigned int nr_cqe,
int comp_vector_hint,
enum ib_poll_context poll_ctx)
{
// 1. 查找已有 CQ 中有空闲槽位且向量匹配的
// 2. 若无,调用 ib_alloc_cqs() 批量分配新 CQ
// 3. 返回 CQ 并增加 cqe_used 计数
}
void ib_cq_pool_put(struct ib_cq *cq, unsigned int nr_cqe)
{
// 释放 CQ 槽位(减少 cqe_used)
spin_lock_irq(&cq->device->cq_pools_lock);
cq->cqe_used -= nr_cqe;
spin_unlock_irq(&cq->device->cq_pools_lock);
}该机制按 poll_ctx 分 bucket,并利用 comp_vector 实现 CPU 亲和性分布。
当 poll_ctx == IB_POLL_SOFTIRQ 时,CQ 使用 irq_poll(NAPI 风格):
硬件完成中断(MSI-X)
--> ib_cq_completion_softirq()
--> irq_poll_sched(&cq->iop) 在 NET_RX_SOFTIRQ 中调度
--> ib_poll_handler() IRQ poll 回调
--> __ib_process_cq() 批量处理 CQE
--> if (completed < budget) 处理完毕,重新启用通知
irq_poll_complete()
ib_req_notify_cq()
--> rdma_dim() 更新 DIM 统计
这套机制与网卡 NAPI 类似,防止中断风暴导致 CPU 饥饿。
ib_umem 表示被注册为 RDMA MR 的用户内存区域(include/rdma/ib_umem.h):
struct ib_umem {
struct ib_device *ibdev; // 关联的 RDMA 设备
struct mm_struct *owning_mm; // 所属进程的 mm_struct
u64 iova; // IOVA(驱动调用 ib_umem_find_best_pgsz 设置)
unsigned long address; // 用户虚拟地址起始
size_t length; // 注册长度
struct sg_append_table sgt_append;// scatter-gather 物理页表
unsigned int is_odp:1; // 是否为 ODP
unsigned int is_dmabuf:1; // 是否为 DMA-buf
unsigned int writable:1; // 是否可写
};drivers/infiniband/core/umem.c:164 的 ib_umem_get() 是所有用户 MR 注册的核心:
struct ib_umem *ib_umem_get(struct ib_device *device, unsigned long addr,
size_t size, int access)
{
// 1. 参数检查:地址溢出检测
if (((addr + size) < addr) ||
PAGE_ALIGN(addr + size) < (addr + size))
return ERR_PTR(-EINVAL);
// 2. 权限检查
if (!can_do_mlock())
return ERR_PTR(-EPERM);
// 3. ODP 模式走不同路径
if (access & IB_ACCESS_ON_DEMAND)
return ERR_PTR(-EOPNOTSUPP); // 走 ib_umem_odp_alloc_implicit()
// 4. 检查 RLIMIT_MEMLOCK 限制
lock_limit = rlimit(RLIMIT_MEMLOCK) >> PAGE_SHIFT;
new_pinned = atomic64_add_return(npages, &mm->pinned_vm);
if (new_pinned > lock_limit && !capable(CAP_IPC_LOCK)) {
atomic64_sub(npages, &mm->pinned_vm);
ret = -ENOMEM;
}
// 5. 循环调用 pin_user_pages_fast() 固定物理页
while (npages) {
cond_resched();
pinned = pin_user_pages_fast(cur_base,
min_t(unsigned long, npages,
PAGE_SIZE / sizeof(struct page *)),
gup_flags, page_list);
// ...
// 累积到 sgt_append scatter-gather 表
ret = sg_alloc_append_table_from_pages(
&umem->sgt_append, page_list, pinned, ...);
}
// 6. DMA 映射 scatter-gather 表
if (access & IB_ACCESS_RELAXED_ORDERING)
dma_attr |= DMA_ATTR_WEAK_ORDERING;
ret = ib_dma_map_sgtable_attrs(device, &umem->sgt_append.sgt,
DMA_BIDIRECTIONAL, dma_attr);
}整个过程的关键步骤如下图:
用户虚拟地址空间
[addr, addr+size)
|
| pin_user_pages_fast() -- 增加页面引用计数,防止被换出
v
物理页列表 page_list[]
|
| sg_alloc_append_table_from_pages() -- 构建 scatter-gather 表
v
struct sg_append_table(物理地址段列表)
|
| ib_dma_map_sgtable_attrs() -- 创建 IOMMU DMA 映射
v
DMA 地址列表(IOVA)
|
| 驱动 reg_user_mr() -- 写入 HCA MPT/MTT
v
lkey / rkey(可用于数据传输)
umem.c:85 中该函数根据内存的物理连续性为 HCA 选择最优页面大小:
unsigned long ib_umem_find_best_pgsz(struct ib_umem *umem,
unsigned long pgsz_bitmap,
unsigned long virt)
{
// 算法:遍历 scatter-gather 条目,计算 VA 与 PA 不一致的位位置
// mask 的尾部连续 0 的个数即最大可用页大小的 log2
mask = pgsz_bitmap &
GENMASK(BITS_PER_LONG - 1,
bits_per((umem->length - 1 + virt) ^ virt));
for_each_sgtable_dma_sg(&umem->sgt_append.sgt, sg, i) {
if (end != sg_dma_address(sg)) {
mask |= (curr_base + pgoff) ^ va; // VA/PA bit 差异
if (i != 0)
mask |= va; // 物理不连续点 VA 对齐限制
}
// ...
}
if (mask)
pgsz_bitmap &= GENMASK(count_trailing_zeros(mask), 0);
return pgsz_bitmap ? rounddown_pow_of_two(pgsz_bitmap) : 0;
}对大页内存(如 2MB HugePage),此函数返回 2MB 页大小,使 HCA MPT/MTT 表项数量减少 512 倍,显著提升注册性能。
umem.c:284 的 ib_umem_release() 逆向操作:
void ib_umem_release(struct ib_umem *umem)
{
if (!umem) return;
if (umem->is_dmabuf) return ib_umem_dmabuf_release(...);
if (umem->is_odp) return ib_umem_odp_release(...);
// 1. DMA 取消映射 + 解 pin 物理页
__ib_umem_release(umem->ibdev, umem, 1 /* dirty */);
// 2. 减少 mm->pinned_vm 计数
atomic64_sub(ib_umem_num_pages(umem), &umem->owning_mm->pinned_vm);
mmdrop(umem->owning_mm);
kfree(umem);
}ODP(On-Demand Paging)允许注册 MR 时不预先 pin 住所有页面,而是在 HCA 访问时触发缺页处理。
include/rdma/ib_umem_odp.h 中 ib_umem_odp 继承自 ib_umem:
struct ib_umem_odp {
struct ib_umem umem; // 基类
struct mmu_interval_notifier notifier; // MMU 变化通知
struct mutex umem_mutex; // 保护 DMA 映射表
struct hmm_dma_map map; // HMM DMA 映射
unsigned long page_shift; // 页面大小 log2
unsigned int is_implicit_odp:1; // 隐式 ODP(整个地址空间)
};static int ib_init_umem_odp(struct ib_umem_odp *umem_odp,
const struct mmu_interval_notifier_ops *ops)
{
// 注册 MMU 区间通知器
// 当该地址范围的页表发生变化(迁移/换出)时,通知 RDMA 驱动
ret = mmu_interval_notifier_insert(&umem_odp->notifier,
umem_odp->umem.owning_mm,
start, end - start, ops);
// 分配 HMM DMA 映射表(按需填充)
nr_entries = (end - start) >> PAGE_SHIFT;
map->pfn_list = kvcalloc(nr_entries, sizeof(*map->pfn_list), ...);
}HCA 访问未映射页面
--> 向 CPU 发送 Page Fault 事件(通过 ATS/PRI 机制或软件模拟)
--> 驱动的缺页处理函数
--> hmm_range_fault() -- 通过 HMM 建立物理页映射
--> 更新 DMA 映射表
--> 通知 HCA 重试(invalidate_range 结束后继续)
页面换出/迁移时:
--> mmu_notifier 回调 invalidate_range_start()
--> 驱动将 MR 对应范围标记为无效(发送 MMU_INVALIDATE 给 HCA)
--> mmu_notifier 回调 invalidate_range_end()
--> 驱动可以在下次访问时重新建立映射
| 特性 | 显式 ODP | 隐式 ODP |
|---|---|---|
| 注册粒度 | 特定虚拟地址范围 | 整个进程地址空间(0 到 ULONG_MAX) |
| MR 数量 | 每块内存一个 MR | 全局唯一一个 MR |
| 典型场景 | 普通应用 | UCX/MPI 的零配置 RDMA |
| 代码位置 | ib_umem_odp_alloc_child() |
ib_umem_odp_alloc_implicit() |
drivers/infiniband/core/uverbs_main.c 实现字符设备的创建(第 65 行):
enum {
IB_UVERBS_MAJOR = 231, // 主设备号
IB_UVERBS_BASE_MINOR = 192, // 次设备号起始
IB_UVERBS_MAX_DEVICES = RDMA_MAX_PORTS,
IB_UVERBS_NUM_FIXED_MINOR = 32, // 固定分配 32 个
IB_UVERBS_NUM_DYNAMIC_MINOR = IB_UVERBS_MAX_DEVICES - 32,
};
static char *uverbs_devnode(const struct device *dev, umode_t *mode)
{
if (mode)
*mode = 0666; // 所有用户可访问
return kasprintf(GFP_KERNEL, "infiniband/%s", dev_name(dev));
}打开 /dev/infiniband/uverbs0
--> ib_uverbs_open()
--> 分配 ib_uverbs_file
.device = ib_uverbs_device
.ucontext = NULL(懒初始化)
.ref = kref
.umap_lock = mutex
--> 后续 ioctl 操作通过此 file 访问 device 和 ucontext
新版 ioctl 框架(uverbs_ioctl.c)使用属性包(attribute bundle)传递参数:
// 用户空间写入命令
// ioctl(fd, RDMA_VERBS_IOCTL, &hdr)
// hdr 包含 object_id, method_id 和 attrs 数组
// 内核端路由
uverbs_ioctl()
--> uverbs_handle_method()
--> 根据 object_id + method_id 查找 uverbs_method_spec
--> 调用对应的 handler 函数
--> 返回结果通过 attrs 写回用户空间用户进程可以通过 poll/read 监听异步事件(QP 错误、端口状态变化等):
// uverbs_main.c:228
static ssize_t ib_uverbs_async_event_read(struct file *filp,
char __user *buf,
size_t count, loff_t *pos)
{
// 从 ev_queue.event_list 读取事件
// 若队列空则 wait_event_interruptible() 阻塞等待
// 每次读一个 ib_uverbs_async_event_desc
}用户空间通过 ibv_get_async_event() 获取这些事件。
ib_ucontext 不在 open() 时创建,而是在第一次 alloc_pd 或 get_context 命令时初始化(通过 ib_uverbs_get_ucontext_file() 懒获取)。这允许同一个进程的多个 ib_uverbs_file 共享设备,而无需立即分配驱动资源。
iWARP 在标准 TCP/IP 网络上实现 RDMA,适合无需专用无损以太网的场景。
drivers/infiniband/core/iwcm.c 实现 iWARP 特有的连接管理层(第 59 行):
MODULE_AUTHOR("Tom Tucker");
MODULE_DESCRIPTION("iWARP CM");
MODULE_LICENSE("Dual BSD/GPL");
static const char * const iwcm_rej_reason_strs[] = {
[ECONNRESET] = "reset by remote host",
[ECONNREFUSED] = "refused by remote application",
[ETIMEDOUT] = "setup timeout",
};iWARP CM 使用 netlink 与用户空间的 iWARP 端口映射守护进程(iwpmd)通信:
static struct rdma_nl_cbs iwcm_nl_cb_table[RDMA_NL_IWPM_NUM_OPS] = {
[RDMA_NL_IWPM_REG_PID] = {.dump = iwpm_register_pid_cb},
[RDMA_NL_IWPM_ADD_MAPPING] = {.dump = iwpm_add_mapping_cb},
[RDMA_NL_IWPM_QUERY_MAPPING] = {.dump = iwpm_add_and_query_mapping_cb},
[RDMA_NL_IWPM_REMOTE_INFO] = {.dump = iwpm_remote_info_cb},
[RDMA_NL_IWPM_HANDLE_ERR] = {.dump = iwpm_mapping_error_cb},
[RDMA_NL_IWPM_MAPINFO] = {.dump = iwpm_mapping_info_cb},
[RDMA_NL_IWPM_HELLO] = {.dump = iwpm_hello_cb},
};+---------------------------+---------------------------+
| iWARP | RoCE v2 |
+---------------------------+---------------------------+
| 传输层:TCP | 传输层:UDP |
| 可靠性:TCP 保证 | 可靠性:IB 协议层保证 |
| 有序性:TCP 保证 | 有序性:IB PSN 机制保证 |
| 需要 MR:RDMA Read 必须 | 需要 MR:可选优化 |
| 连接管理:iWARP CM | 连接管理:IB CM / RDMA CM |
| 流控:TCP 滑动窗口 | 流控:需要 PFC/ECN |
| 网络穿透:可穿越 NAT/路由 | 网络穿透:L2 限制(v1) |
+---------------------------+---------------------------+
由于 iWARP 基于 TCP,在 RDMA Read 操作中强制要求使用 Fast Registration MR(而非多 SGE 方式)。rw.c:30 中明确体现:
static inline bool rdma_rw_can_use_mr(struct ib_device *dev, u32 port_num)
{
if (rdma_protocol_iwarp(dev, port_num))
return true; // iWARP 必须使用 MR
if (dev->attrs.max_sgl_rd)
return true; // 设备 RDMA Read SGE 限制
if (unlikely(rdma_rw_force_mr))
return true;
return false;
}这是因为 iWARP 协议(IETF RFC 5041)规定 RDMA Read 请求必须包含 STag(即 rkey),而直接 SGE 方式不携带 rkey。
drivers/infiniband/sw/siw/ 是纯软件的 iWARP 实现,与 SoftRoCE 类似:
siw 架构:
RDMA Verbs API
|
siw 驱动(纯内核 TCP)
|
内核 TCP/IP 协议栈
|
任意以太网网卡
SoftiWARP 可用于在没有 iWARP 硬件的情况下测试 iWARP 兼容性。
drivers/infiniband/core/rw.c 提供了一个高层次的 RDMA Read/Write 封装 API,供 NVMe-oF、iSER 等内核 ULP 使用,屏蔽了底层的 Fast Registration 细节。
// include/rdma/rw.h
struct rdma_rw_ctx {
union {
struct {
struct ib_sge sge;
struct ib_rdma_wr wr;
} single; // 单 SGE 情形(无需 MR)
struct {
u32 nr_ops;
struct rdma_rw_reg_ctx *reg; // 多 MR 情形
} mr;
};
};
struct rdma_rw_reg_ctx {
struct ib_sge sge;
struct ib_rdma_wr rdma_wr;
struct ib_reg_wr reg_wr; // Fast Reg WR
struct ib_send_wr inv_wr; // Invalidate WR
struct ib_mr *mr; // 从 MR 池获取
};对于需要 MR 的情形(iWARP 或超过 max_sgl_rd 的 scatter-gather 列表),该函数从 MR 池获取一个预分配 MR 并构建 Fast Reg WR:
static int rdma_rw_init_one_mr(struct ib_qp *qp, u32 port_num,
struct rdma_rw_reg_ctx *reg, struct scatterlist *sg,
u32 sg_cnt, u32 offset)
{
u32 pages_per_mr = rdma_rw_fr_page_list_len(qp->pd->device, ...);
u32 nents = min(sg_cnt, pages_per_mr);
// 从 QP 关联的 MR 池获取
reg->mr = ib_mr_pool_get(qp, &qp->rdma_mrs);
if (!reg->mr)
return -EAGAIN;
// 若 MR 需要先无效化(need_inval),插入 LOCAL_INV WR
count += rdma_rw_inv_key(reg);
// 填充物理页到 MR
ret = ib_map_mr_sg(reg->mr, sg, nents, &offset, PAGE_SIZE);
// 构建 Fast Reg WR
reg->reg_wr.wr.opcode = IB_WR_REG_MR;
reg->reg_wr.mr = reg->mr;
reg->reg_wr.access = IB_ACCESS_LOCAL_WRITE;
if (rdma_protocol_iwarp(qp->device, port_num))
reg->reg_wr.access |= IB_ACCESS_REMOTE_WRITE;
count++;
reg->sge.addr = reg->mr->iova;
reg->sge.length = reg->mr->length;
reg->sge.lkey = reg->mr->lkey;
return count;
}rw.c:21 提供调试用模块参数:
static bool rdma_rw_force_mr;
module_param_named(force_mr, rdma_rw_force_mr, bool, 0);
MODULE_PARM_DESC(force_mr, "Force usage of MRs for RDMA READ/WRITE operations");设置 rdma_core.force_mr=1 可强制所有 RDMA R/W 操作走 Fast Registration 路径,用于测试该代码路径的正确性。
drivers/infiniband/core/mr_pool.c 管理 QP 私有的 MR 池,避免频繁 ib_alloc_mr / ib_dereg_mr 的开销:
// 池初始化:预分配 nr 个 MR
int ib_mr_pool_init(struct ib_qp *qp, struct list_head *list, int nr,
enum ib_mr_type type, u32 max_num_sg, u32 max_num_meta_sg)
{
for (i = 0; i < nr; i++) {
if (type == IB_MR_TYPE_INTEGRITY)
mr = ib_alloc_mr_integrity(qp->pd, max_num_sg, max_num_meta_sg);
else
mr = ib_alloc_mr(qp->pd, type, max_num_sg);
spin_lock_irqsave(&qp->mr_lock, flags);
list_add_tail(&mr->qp_entry, list); // 加入 QP 的 MR 链表
spin_unlock_irqrestore(&qp->mr_lock, flags);
}
}
// 从池获取 MR(无锁快路径)
struct ib_mr *ib_mr_pool_get(struct ib_qp *qp, struct list_head *list)
{
spin_lock_irqsave(&qp->mr_lock, flags);
mr = list_first_entry_or_null(list, struct ib_mr, qp_entry);
if (mr) {
list_del(&mr->qp_entry);
qp->mrs_used++;
}
spin_unlock_irqrestore(&qp->mr_lock, flags);
return mr;
}
// 归还 MR 到池
void ib_mr_pool_put(struct ib_qp *qp, struct list_head *list, struct ib_mr *mr)
{
spin_lock_irqsave(&qp->mr_lock, flags);
list_add(&mr->qp_entry, list); // 放回链表头(LRU)
qp->mrs_used--;
spin_unlock_irqrestore(&qp->mr_lock, flags);
}QP 创建时(nvme-rdma / iser):
ib_mr_pool_init(qp, &qp->rdma_mrs, pool_size,
IB_MR_TYPE_MEM_REG, max_pages, 0)
--> 预分配 pool_size 个 Fast Reg MR 对象(lkey/rkey 已分配但未激活)
每次 I/O 时:
mr = ib_mr_pool_get(qp, &qp->rdma_mrs) // 从池取出
ib_map_mr_sg(mr, sg_list, nents, ...) // 填充物理页
post IB_WR_REG_MR // 激活 MR
使用 mr->lkey/rkey 传输数据
post IB_WR_LOCAL_INV // 失效 MR(mr->need_inval = true)
ib_mr_pool_put(qp, &qp->rdma_mrs, mr) // 归还池
QP 销毁时:
ib_mr_pool_destroy(qp, &qp->rdma_mrs)
--> 遍历链表,依次调用 ib_dereg_mr()
drivers/infiniband/sw/rxe/rxe_mr.c:16,SoftRoCE 为每个 MR 生成随机 rkey:
u8 rxe_get_next_key(u32 last_key)
{
u8 key;
do {
get_random_bytes(&key, 1); // 使用内核 CSPRNG
} while (key == last_key); // 保证与上次不同
return key;
}
void rxe_mr_init(int access, struct rxe_mr *mr)
{
// lkey = (池中索引 << 8) | 随机 key
u32 key = mr->elem.index << 8 | rxe_get_next_key(-1);
mr->lkey = mr->ibmr.lkey = key;
mr->rkey = mr->ibmr.rkey = key; // 用户 MR 的 lkey == rkey
mr->state = RXE_MR_STATE_INVALID; // 初始无效,需 REG_MR 激活
}这保证了 rkey 的不可预测性,防止越权访问。
drivers/infiniband/sw/rxe/rxe_task.c 实现了 SoftRoCE 的轻量级任务调度器(第 9 行):
static struct workqueue_struct *rxe_wq;
int rxe_alloc_wq(void)
{
rxe_wq = alloc_workqueue("rxe_wq", WQ_UNBOUND, WQ_MAX_ACTIVE);
if (!rxe_wq)
return -ENOMEM;
return 0;
}任务状态机:
IDLE ----调度----> BUSY ----再次调度----> ARMED
| |
完成返回 0 完成返回 0
| |
--> IDLE --> BUSY(继续处理)
|
draining
|
DRAINED
当 rxe_sched_task() 被调用时(如新 WR 入队),若任务处于 IDLE 则立即提交到 rxe_wq;若已 BUSY 则置为 ARMED,确保下一次循环再处理。
drivers/infiniband/sw/rxe/rxe_resp.c:13 定义了完整的响应状态名:
static char *resp_state_name[] = {
[RESPST_NONE] = "NONE",
[RESPST_GET_REQ] = "GET_REQ", // 从 req_pkts 队列取包
[RESPST_CHK_PSN] = "CHK_PSN", // 检查包序列号
[RESPST_CHK_OP_SEQ] = "CHK_OP_SEQ", // 检查操作码序列
[RESPST_CHK_OP_VALID] = "CHK_OP_VALID", // 检查操作码有效性
[RESPST_CHK_RESOURCE] = "CHK_RESOURCE", // 检查接收缓冲区
[RESPST_CHK_LENGTH] = "CHK_LENGTH", // 检查长度
[RESPST_CHK_RKEY] = "CHK_RKEY", // 检查 rkey 权限
[RESPST_EXECUTE] = "EXECUTE", // 执行操作(写内存/读内存)
[RESPST_READ_REPLY] = "READ_REPLY", // 生成 RDMA Read 响应
[RESPST_ATOMIC_REPLY] = "ATOMIC_REPLY", // 生成原子操作响应
[RESPST_COMPLETE] = "COMPLETE", // 生成 WC
[RESPST_ACKNOWLEDGE] = "ACKNOWLEDGE", // 发送 ACK
[RESPST_ERR_RNR] = "ERR_RNR", // RNR 错误(无接收 WR)
[RESPST_ERR_RKEY_VIOLATION] = "ERR_RKEY_VIOLATION", // rkey 违规
[RESPST_ERR_PSN_OUT_OF_SEQ] = "ERR_PSN_OUT_OF_SEQ", // PSN 乱序
};PSN 检查逻辑(rxe_resp.c:70):
static enum resp_states check_psn(struct rxe_qp *qp,
struct rxe_pkt_info *pkt)
{
int diff = psn_compare(pkt->psn, qp->resp.psn);
switch (qp_type(qp)) {
case IB_QPT_RC:
if (diff > 0) { // 未来包(乱序)
if (qp->resp.sent_psn_nak)
return RESPST_CLEANUP;
qp->resp.sent_psn_nak = 1;
rxe_counter_inc(rxe, RXE_CNT_OUT_OF_SEQ_REQ);
return RESPST_ERR_PSN_OUT_OF_SEQ; // 发送 NAK
} else if (diff < 0) { // 重复包
rxe_counter_inc(rxe, RXE_CNT_DUP_REQ);
return RESPST_DUPLICATE_REQUEST; // 可能需要重发 ACK
}
break;
case IB_QPT_UC:
if (qp->resp.drop_msg || diff != 0)
// UC 不保证有序,直接丢弃乱序包
...
}
}drivers/infiniband/sw/rxe/rxe_req.c:37 的 req_retry() 在超时重传时重置 WQE 状态:
static void req_retry(struct rxe_qp *qp)
{
qp->req.wqe_index = cons; // 回退到第一个未完成 WQE
qp->req.psn = qp->comp.psn; // PSN 回退到最后确认位置
qp->req.opcode = -1;
// 遍历发送队列中所有飞行中的 WQE
for (wqe_index = cons; wqe_index != prod; ...) {
if (wqe->state == wqe_state_done) continue; // 已确认,跳过
wqe->dma.resid = wqe->dma.length; // 重置 DMA 偏移
wqe->state = wqe_state_posted; // 标记为待重传
}
}
// RNR 超时定时器
void rnr_nak_timer(struct timer_list *t)
{
struct rxe_qp *qp = timer_container_of(qp, t, rnr_nak_timer);
// 重新调度请求端,触发重传
rxe_sched_task(&qp->req_task);
}InfiniBand 协议基于"无丢包"假设设计:
- 重传超时(
QP_TIMEOUT)为指数退避,设计为处理偶发错误 - 高丢包率会导致大量 WC 错误(
IB_WC_RETRY_EXC_ERR) - 与 TCP 不同,RDMA 没有滑动窗口拥塞控制
以太网帧格式(带 PFC):
当某个优先级队列将要溢出时,发送 PAUSE 帧
目标端暂停发送对应优先级的流量
配置示例(mlnx_qos 或 ethtool):
mlnx_qos -i eth0 --pfc 0,0,0,1,0,0,0,0 # 启用优先级 3 的 PFC
ethtool --set-priv-flags eth0 flow_steering on
在 Linux 内核中,通过 dcbnl(Data Center Bridging Netlink)接口配置 PFC。
RoCE v2 支持 DCQCN(Data Center Quantized Congestion Notification)算法:
拥塞通知流程:
交换机检测到拥塞
--> 在数据包 IP 头设置 ECN 位(CE = Congestion Experienced)
--> 接收端 HCA 检测到 ECN
--> 发送 CNP(Congestion Notification Packet)给发送端
--> 发送端 HCA 减慢发送速率
--> 逐渐恢复速率(DCQCN 算法控制)
Mellanox ConnectX 等硬件支持 DCQCN,完全在网卡 firmware 中实现,对内核透明。
在 RoCE v2 报文中,通过 IP DSCP 字段标记流量优先级,确保 PFC 正确工作:
IP TOS/DSCP 字段(6 位)
--> 通过 iptables/tc 规则映射到以太网 CoS(VLAN PCP)
--> 交换机根据 CoS 应用 PFC
Linux rdma_cm 通过 QP 的 AH 属性中的 traffic_class 字段设置 DSCP
drivers/infiniband/core/device.c 使用 xarray 管理所有已注册设备(第 93 行):
// 设备 xarray,按设备 ID 索引
static DEFINE_XARRAY_FLAGS(devices, XA_FLAGS_ALLOC);
static DECLARE_RWSEM(devices_rwsem);
#define DEVICE_REGISTERED XA_MARK_1 // 标记设备已完全注册
#define DEVICE_GID_UPDATES XA_MARK_2 // 标记 GID 表正在更新
// 客户端 xarray
static DEFINE_XARRAY_FLAGS(clients, XA_FLAGS_ALLOC);
static DECLARE_RWSEM(clients_rwsem);设备注册完成后,内核遍历所有已注册的 ib_client,调用其 add() 回调,实现模块化的设备探测通知机制。
device.c:57 定义了 RDMA 子系统的全局工作队列:
struct workqueue_struct *ib_comp_wq; // CQ 绑定工作队列
struct workqueue_struct *ib_comp_unbound_wq; // CQ 非绑定工作队列
struct workqueue_struct *ib_wq; // 通用 IB 工作队列
static struct workqueue_struct *ib_unreg_wq; // 设备注销工作队列ib_comp_wq 用于 IB_POLL_WORKQUEUE 模式的 CQ,与 CPU 亲和性绑定;ib_comp_unbound_wq 用于 IB_POLL_UNBOUND_WORKQUEUE,可在任意 CPU 上运行。
drivers/infiniband/core/restrack.c 实现了 RDMA 资源的引用计数追踪,支持通过 rdma res 命令查看:
int rdma_restrack_init(struct ib_device *dev)
{
// 为每种资源类型分配 xarray
dev->res = kzalloc_objs(*rt, RDMA_RESTRACK_MAX);
for (i = 0; i < RDMA_RESTRACK_MAX; i++)
xa_init_flags(&rt[i].xa, XA_FLAGS_ALLOC);
}
// 资源类型枚举(rdma_restrack_type)
// RDMA_RESTRACK_PD, RDMA_RESTRACK_CQ, RDMA_RESTRACK_QP,
// RDMA_RESTRACK_MR, RDMA_RESTRACK_SRQ, RDMA_RESTRACK_AH, ...每个 ib_pd、ib_qp、ib_cq、ib_mr 都嵌入一个 rdma_restrack_entry,在创建时通过 rdma_restrack_add() 注册,销毁时通过 rdma_restrack_del() 注销。
device.c:129 通过模块参数控制 RDMA 设备的 netns 可见性:
bool ib_devices_shared_netns = true;
module_param_named(netns_mode, ib_devices_shared_netns, bool, 0444);
MODULE_PARM_DESC(netns_mode,
"Share device among net namespaces; default=1 (shared)");当 netns_mode=0 时,RDMA 设备绑定到特定 net namespace,容器环境下可实现 RDMA 设备隔离。
+---------------------------------------------------+
| NVMe 主机(Initiator) |
| |
| nvme_submit_cmd() |
| | |
| nvme_rdma_queue_rq() -- blk-mq 回调 |
| | |
| nvme_rdma_post_send() -- 发送 NVMe 命令 |
| | (1) Fast Reg MR(注册数据缓冲区) |
| | (2) post_send(发送命令 capsule) |
| | |
| nvme_rdma_process_nvme_rsp() -- 处理响应 |
| | (1) 接收 NVMe Response |
| | (2) Local Inv MR |
| | (3) 完成 blk-mq request |
| |
+---------------------------------------------------+
| RC QP(每个队列一个)
| RDMA CM 连接
+---------------------------------------------------+
| NVMe 目标(Target) |
| |
| nvmet_rdma 驱动(drivers/nvme/target/rdma.c) |
| | |
| 接收 NVMe 命令 capsule |
| 执行 nvme 命令(读/写 NVMe 设备) |
| RDMA Write 数据回 Initiator(读操作) |
| 发送 NVMe Response |
| |
+---------------------------------------------------+
// drivers/nvme/host/rdma.c(简化)
static int nvme_rdma_alloc_queue(struct nvme_rdma_ctrl *ctrl,
int idx, size_t queue_size)
{
struct nvme_rdma_queue *queue = &ctrl->queues[idx];
// 1. 创建 rdma_cm_id
queue->cm_id = rdma_create_id(ctrl->ctrl.opts->net,
nvme_rdma_cm_handler, queue,
RDMA_PS_TCP, IB_QPT_RC);
// 2. 分配共享 PD(如果是第一个队列)
if (nvme_rdma_dev_is_new(queue->device))
queue->device->pd = ib_alloc_pd(ibdev, 0);
// 3. 创建 CQ
queue->ib_cq = ib_alloc_cq(ibdev, queue, queue->cq_size,
comp_vector, IB_POLL_SOFTIRQ);
// 4. 创建 RC QP
init_attr.qp_type = IB_QPT_RC;
init_attr.send_cq = queue->ib_cq;
init_attr.recv_cq = queue->ib_cq;
init_attr.cap.max_send_wr = queue_size;
init_attr.cap.max_recv_wr = queue_size;
rdma_create_qp(queue->cm_id, queue->device->pd, &init_attr);
// 5. 预分配 Fast Reg MR 池
ret = ib_mr_pool_init(queue->qp, &queue->qp->rdma_mrs,
ctrl->queue_size,
IB_MR_TYPE_MEM_REG,
NVME_RDMA_MAX_SEGMENTS, 0);
}| 特性 | 普通 RDMA 应用 | NVMe-oF RDMA |
|---|---|---|
| MR 管理 | 静态注册(长期有效) | 动态 Fast Reg(每 I/O |
| 内存来源 | 用户应用缓冲区 | blk-mq 的 bio 缓冲区 |
| 连接数 | 应用决定 | 每个 nvme queue 一个 QP |
| 传输方向 | 双向 | 读操作由 Target RDMA Write |
| 错误恢复 | 应用层处理 | 自动重连(fabric error) |
NVMe-oF RDMA 支持端到端 T10-PI(Protection Information):
struct nvme_rdma_request {
// ...
bool use_sig_mr; // 使用 IB_MR_TYPE_INTEGRITY MR
};使用 IB_MR_TYPE_INTEGRITY 类型的 MR,HCA 在 DMA 过程中自动计算/验证 T10-DIF 校验,无需 CPU 参与,实现硬件加速的端到端数据完整性。
MPI(Message Passing Interface)是 HPC 领域最广泛使用的并行编程接口。通过 RDMA 加速 MPI 通信是 InfiniBand 的核心应用场景。
+------------------+ +------------------+
| MPI 进程 0 | | MPI 进程 1 |
| MPI_Send(buf) | | MPI_Recv(buf) |
| | | | | |
| UCX/OpenMPI | | UCX/OpenMPI |
| 传输层 | | 传输层 |
+--------+---------+ +-----|------------+
| |
RDMA Verbs API(libibverbs)
| |
InfiniBand / RoCE 网卡
UCX(Unified Communication X)是现代 MPI 的 RDMA 传输层:
Rendezvous 协议(大消息):
发送方 接收方
MPI_Send(buf, large)
UCX 发送 RTS(Ready To Send)
包含本地 MR 的 rkey/iova
MPI_Recv() 准备好
UCX 收到 RTS
--> RDMA Read 拉取数据
(对发送方 MR 执行 RDMA Read)
--> 发送 ACK
收到 ACK,完成发送
Eager 协议(小消息,< 阈值):
直接通过 SEND/RECV 传输
使用内联数据(inline data)避免 MR 注册开销
典型阈值:~16KB(取决于 MTU 和延迟/吞吐权衡)
集群规模对 QP 数量的影响:
N 个进程,每对进程需要 RC QP:
- 全互连:需要 N*(N-1) 个 QP
- 1000 个进程:约 100 万个 QP(内存开销巨大)
解决方案:
1. XRC(Extended Reliable Connected):减少到 N 个 XRC TGT QP
2. DC(Dynamic Connected,Mellanox 专有):按需建立连接
3. UD(Unreliable Datagram):只需 N 个 UD QP(但限制消息大小)
4. Shared Receive Queue(SRQ):减少接收队列内存
MPI_Allreduce(全规约)的 RDMA 优化:
传统实现:
MPI_Send + MPI_Recv 的树形归约
RDMA 优化(使用原子操作):
对支持 IB_WR_ATOMIC_FETCH_AND_ADD 的设备:
所有节点并发写入共享内存区域
使用原子 FAA 实现无锁规约
延迟从 O(log N) 降至接近 O(1)
RDMA 性能对 NUMA 非常敏感。最佳实践:
# 查看网卡所在 NUMA 节点
cat /sys/class/infiniband/mlx5_0/device/numa_node
# 在 NUMA 节点本地分配内存
numactl --membind=0 --cpunodebind=0 ./rdma_app
# 绑定中断向量到本地 CPU
# 查看 MSI-X 中断
cat /proc/interrupts | grep mlx5
# 使用 irqbalance 或手动绑定
echo 0x3 > /proc/irq/<irq_num>/smp_affinity # 绑定到 CPU 0-1超时与重传参数:
QP timeout:
取值范围:0-31(单位:4.096 μs × 2^timeout)
timeout=14 对应约 67ms(局域网典型值)
timeout=17 对应约 536ms(广域网)
retry_cnt:重传次数(0-7,建议 7)
rnr_retry:RNR 重试次数(0-7,7=无限重试)
min_rnr_timer:RNR 最小等待时间(0-31,单位 655μs)
发送队列深度(max_send_wr):
深度越大 = 更多并发 WR = 更高吞吐(但内存开销增加)
深度太小 = 应用等待 CQ 轮询 = 延迟增加
NVMe-oF 通常设置 = queue_depth + extra_wr
策略对比:
1. 静态大内存注册(适合长期稳定的大缓冲区)
优点:一次注册,无 reg 延迟
缺点:内存锁定,RLIMIT_MEMLOCK 限制
2. 动态 Fast Registration(适合 I/O 场景)
优点:内存按需注册,无锁定
缺点:每次 I/O 有 REG_MR + LOCAL_INV 开销(约 2-5μs)
3. ODP(适合大地址空间、内存压力场景)
优点:无需提前 pin 页面
缺点:缺页时有额外延迟
推荐:
HPC/MPI:静态注册(通信缓冲区固定)
NVMe-oF:Fast Registration(每 I/O 不同缓冲区)
UCX:混合策略(小 MR 池 + 按需注册)
Busy Polling(忙轮询,最低延迟):
while (!ib_poll_cq(cq, 1, &wc))
; // CPU 100% 占用,延迟最低
Event-Driven(事件驱动,最低 CPU 开销):
ib_req_notify_cq(cq, IB_CQ_NEXT_COMP);
wait_for_completion(&cq_comp); // 睡眠等待中断
混合模式(Adaptive Polling):
先短时间 busy poll(如 2μs)
超时后切换到事件驱动
大多数高性能应用(如 MPI、NVMe-oF)使用此策略
# 分配大页(2MB HugePage)
echo 1024 > /proc/sys/vm/nr_hugepages # 分配 2GB 大页内存
# 使用 mmap 分配大页内存
void *buf = mmap(NULL, size, PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_ANONYMOUS|MAP_HUGETLB, -1, 0);
# 大页内存注册 MR 的优势:
# - ib_umem_find_best_pgsz() 返回 2MB,而非 4KB
# - HCA MPT/MTT 条目减少 512 倍
# - 注册时间大幅缩短(重要的是 TLB 条目减少,转发效率提升)PD(Protection Domain)是 RDMA 安全的第一层防线:
同一 PD 内的 QP 才能使用同一 PD 内的 MR(lkey)
不同 PD 的 QP 无法使用对方 MR,即使知道 lkey 值
实现原理:
HCA 在验证 WR 时检查:
qp.pd == lkey 所属 mr.pd (本地访问检查)
rkey 中编码了 PD 信息 (远端访问检查)
远端 rkey 防止未授权的 RDMA Read/Write:
- rkey 是 32 位随机数,不可猜测(SoftRoCE 使用 CSPRNG)
- rkey 与 iova(虚拟地址)+ length 绑定,越界访问被拒绝
LOCAL_INV/SEND_WITH_INV可使 rkey 立即失效
rxe_mr.c:27 中的范围检查:
int mr_check_range(struct rxe_mr *mr, u64 iova, size_t length)
{
if (iova < mr->ibmr.iova ||
iova + length > mr->ibmr.iova + mr->ibmr.length) {
// 越界访问 -> RESPST_ERR_RKEY_VIOLATION
return -EINVAL;
}
return 0;
}
IB 子网管理通过 MAD(Management Datagram,QP0/QP1)进行,内核通过 ib_mad.c 和 user_mad.c 提供接口:
user_mad.c:/dev/infiniband/umad0,允许用户空间子网管理器(如 OpenSM)直接发送 MAD- 访问需要
CAP_NET_ADMIN权限 rdma_dev_has_raw_cap()检查CAP_NET_RAW(用于 Raw Packet QP)
通过 cgroup v2 限制容器的 RDMA 资源:
# 查看 RDMA cgroup 资源
cat /sys/fs/cgroup/system.slice/rdma.current
# 设置 RDMA 资源限制(最大 MR 数量和 QP 数量)
echo "mlx5_0 mr=100 pd=10" > /sys/fs/cgroup/mycontainer/rdma.max
# 限制 RDMA 资源防止 DoS 攻击(耗尽 HCA 资源)cgroup RDMA 控制器的内核实现位于 drivers/infiniband/core/cgroup.c,通过在每个 ib_uobject 创建时调用 rdma_cgroup_try_charge() 实施限制。
Linux 内核 RDMA/InfiniBand 子系统是一个高度工程化的多层架构:
关键路径延迟分析:
传统 TCP Socket:~10-100 μs(含系统调用、内存拷贝、协议处理)
RDMA(RC/RDMA Write):~1-5 μs(硬件直接 DMA,无 CPU 介入)
RDMA(UD Send):~2-10 μs(单向,无确认)
设计要点:
1. 控制平面(create/destroy)通过 syscall,在 /dev/infiniband/uverbs* 上操作
2. 数据平面(post_send/poll_cq)完全在用户空间,通过 mmap 的 QP/CQ 环形队列
3. 硬件通过 MSI-X 中断或用户态轮询(busy-poll)报告完成事件
4. 内存注册(MR)是安全隔离的关键:lkey/rkey 防止越界访问
5. RDMA CM 提供类 socket 的连接抽象,统一 IB/RoCE/iWARP 差异
6. SoftRoCE(rxe)使任何以太网设备可以运行 RDMA,用于测试和低成本部署
7. CQ 池、MR 池等共享机制减少大规模部署中的资源消耗
8. ODP 允许不预先 pin 页面,支持超大内存注册场景
主要源码文件速查:
| 文件 | 行数 | 功能 |
|---|---|---|
include/rdma/ib_verbs.h |
3000+ | 所有 Verbs 数据结构和接口定义 |
include/rdma/rdma_cm.h |
400+ | 连接管理接口 |
drivers/infiniband/core/verbs.c |
3000+ | Verbs 核心实现(PD/AH/MR/CQ) |
drivers/infiniband/core/cma.c |
5000+ | RDMA CM 实现 |
drivers/infiniband/core/cq.c |
516 | CQ 分配/轮询/DIM |
drivers/infiniband/core/umem.c |
334 | 用户内存注册(pin 页面) |
drivers/infiniband/core/umem_odp.c |
600+ | ODP 按需分页 |
drivers/infiniband/core/mr_pool.c |
83 | MR 池管理 |
drivers/infiniband/core/rw.c |
600+ | RDMA R/W 高层封装 |
drivers/infiniband/core/device.c |
2000+ | 设备注册与管理 |
drivers/infiniband/core/restrack.c |
200+ | 资源追踪 |
drivers/infiniband/core/uverbs_main.c |
800+ | uverbs 字符设备 |
drivers/infiniband/core/uverbs_cmd.c |
3000+ | uverbs ioctl 命令 |
drivers/infiniband/core/iwcm.c |
1000+ | iWARP CM |
drivers/infiniband/sw/rxe/rxe.c |
281 | SoftRoCE 驱动入口 |
drivers/infiniband/sw/rxe/rxe_qp.c |
1000+ | SoftRoCE QP 实现 |
drivers/infiniband/sw/rxe/rxe_req.c |
600+ | SoftRoCE 请求端 |
drivers/infiniband/sw/rxe/rxe_resp.c |
1000+ | SoftRoCE 响应端 |
drivers/infiniband/sw/rxe/rxe_mr.c |
500+ | SoftRoCE MR 实现 |
drivers/infiniband/sw/rxe/rxe_task.c |
200+ | SoftRoCE 任务调度 |
drivers/infiniband/sw/rxe/rxe_net.c |
400+ | SoftRoCE UDP 网络层 |
drivers/nvme/host/rdma.c |
2500+ | NVMe over RDMA |
drivers/infiniband/ulp/iser/iscsi_iser.c |
1500+ | iSCSI over RDMA |
由 Claude Code 分析生成