Skip to content

Latest commit

 

History

History
2978 lines (2401 loc) · 98.2 KB

File metadata and controls

2978 lines (2401 loc) · 98.2 KB

Linux 内核 RDMA / InfiniBand 子系统深度分析

目录

  1. RDMA 基础概念
  2. 整体架构
  3. IB Verbs 核心数据结构
  4. QP 类型与传输服务
  5. QP 状态机
  6. 发送与接收操作
  7. 内存注册(MR)
  8. RDMA Read/Write 操作流程
  9. 连接管理(rdma_cm)
  10. SoftRoCE(rxe)以太网上的 RoCE v2
  11. 用户态接口
  12. 上层协议应用
  13. 关键内核机制详解
  14. 调试与可观测性
  15. 完成队列深度解析
  16. 用户内存注册与 ib_umem
  17. On-Demand Paging 深度分析
  18. uverbs 用户态 Verbs 内核实现
  19. iWARP RDMA over TCP/IP
  20. RDMA RW API 内核封装层
  21. MR 池(mr_pool)与 Fast Registration
  22. SoftRoCE 请求端与响应端状态机
  23. RoCE 无损网络与流量控制
  24. 设备管理与资源追踪
  25. NVMe-oF RDMA 深度分析
  26. MPI 与 RDMA 集成
  27. 性能调优与最佳实践
  28. 安全机制

1. RDMA 基础概念

RDMA(Remote Direct Memory Access,远程直接内存访问)是一种网络技术,允许一台计算机直接读写另一台计算机的内存,无需 CPU 介入数据路径。

1.1 核心特性

零拷贝(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 设备:

  1. 固定物理页,防止被 swap out
  2. 建立虚拟地址到物理地址的映射(IOVA 翻译表存储在 HCA 的内部 MPT/MTT 中)
  3. 生成 lkey(本地访问密钥)和 rkey(远程访问密钥)

注册后,应用通过 lkey/rkey 引用内存,硬件直接使用物理地址完成 DMA。

1.2 InfiniBand vs RoCE vs iWARP

特性 InfiniBand RoCE v2 iWARP
物理层 专用 IB 光纤 以太网 以太网
传输层 IB 协议 UDP TCP
无损网络 是(信用流控) 需要 PFC/ECN TCP 保证
延迟 最低 略高
带宽 HDR 200Gb/s+ 取决于以太网 取决于以太网
成本 高(专用设备) 低(标准以太网)

2. 整体架构

+-----------------------------------------------------------+
|                    用 户 空 间                             |
|  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

3. IB Verbs 核心数据结构

3.1 ib_device — 设备抽象

定义于 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 个操作函数)
};

3.2 ib_pd — 保护域

定义于 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;
}

3.3 ib_qp — 队列对

定义于 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;
};

3.4 ib_cq — 完成队列

定义于 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:调用者直接轮询(无硬件中断)

3.5 ib_mr — 内存区域

定义于 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
};

3.6 ib_wc — 工作完成

定义于 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",
    // ...
};

4. QP 类型与传输服务

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 类型
};

4.1 各类型特性对比

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

5. 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(错误状态)
};

5.1 RC QP 状态机

                  +----------+
                  |  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 号
    // ...
};

5.2 UD QP 状态机(简化)

RESET --> INIT --> RTR --> RTS
  (无需配置 PATH_MTU、DEST_QPN 等,每次发送通过 AH 指定目标)

6. 发送与接收操作

6.1 Work Request 结构体

发送 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;
};

6.2 操作码(ib_wr_opcode)

定义于第 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(仅内核)
};

6.3 post_send / post_recv 接口

内核态通过 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);
}

6.4 完成队列轮询

// 轮询 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);
}

6.5 内核态典型发送流程

应用 / 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

7. 内存注册(MR)

7.1 注册权限标志

定义于 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), // 放宽写顺序(提升性能)
};

7.2 MR 注册类型

① 用户 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;

7.3 On-Demand Paging(ODP)

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 机制响应内核页面迁移/回收事件。


8. RDMA Read/Write 操作流程

8.1 RDMA Write(单向写,无需对端 CPU 参与)

发起方(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

8.2 RDMA Read(发起方主动拉取对端数据)

发起方(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 = 实际读取字节数

8.3 RDMA Write with Immediate

IB_WR_RDMA_WRITE_WITH_IMM 将 RDMA Write 和 32 位即时数据组合:

  • 目标端不需要提供接收缓冲区(RDMA Write 直接写到已知内存)
  • 但目标端会生成一个 Recv WCIB_WC_RECV_RDMA_WITH_IMM),携带即时数据
  • 常用于通知对端"数据已就绪并带有序号/标志"

9. 连接管理(rdma_cm)

rdma_cm 提供与 socket 类似的连接建立抽象,屏蔽底层 IB CM / iWARP CM 差异。

9.1 核心数据结构

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",
    // ...
};

9.2 服务端连接流程

服务端
------
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)

9.3 客户端连接流程

客户端
------
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)

9.4 CMA 内部实现细节

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_paramrdma_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;
};

10. SoftRoCE(rxe)以太网上的 RoCE v2 软件实现

SoftRoCE 是一个完全用软件实现的 RoCE v2 驱动,允许在任何标准以太网网卡上使用 RDMA verbs。

10.1 模块初始化(rxe.c 第 248 行)

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 设备。

10.2 设备参数初始化(rxe.c 第 42 行)

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);
}

10.3 资源池(rxe.c 第 156 行)

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);   // 内存窗口池
}

10.4 SoftRoCE 数据包处理架构

发送路径(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()            用户读取完成事件

10.5 RoCE v2 报文格式

+------------------+
| 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

11. 用户态接口

11.1 /dev/infiniband/uverbsX

每个 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;
};

11.2 libibverbs 调用流程

用户空间 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!

11.3 mmap 机制

硬件 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 到物理地址)。

11.4 uapi 头文件

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 定义

12. 上层协议应用

12.1 NVMe over RDMA(nvme-rdma)

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

12.2 iSCSI Extensions for RDMA(iSER)

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 缓冲区。

12.3 IP over InfiniBand(IPoIB)

drivers/infiniband/ulp/ipoib/ 实现在 InfiniBand 网络上承载 IP 流量:

  • 使用 UD QP(Unreliable Datagram)进行 IP 包传输
  • 也支持 Connected Mode(使用 RC QP,提升性能但增加连接数)
  • InfiniBand 多播用于 ARP 等广播协议

13. 关键内核机制详解

13.1 设备发现与注册

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 等)

13.2 GID 管理

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 封装)
};

13.3 速率配置

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)组合计算实际带宽。

13.4 事件处理

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",
};

13.5 cgroup RDMA 控制

内核支持通过 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
};

13.6 动态中断调节(DIM)

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 算法状态机
    // ...
};

14. 调试与可观测性

14.1 rdma 工具(iproute2)

# 列出 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

14.2 sysfs 接口

/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

14.3 tracepoints

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-cmdperf 捕获

14.4 调试工具

# 查看 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_0

14.5 常见问题排查

QP 进入 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_mribv_query_device 查询)

RNR(Receiver Not Ready)

  • 接收方未 post_recv,发送方重试超限
  • 增大接收队列深度或提高 post_recv 频率
  • 调整 min_rnr_timerrnr_retry 参数

15. 完成队列深度解析

15.1 CQ 分配内核路径

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;
    // ...
}

15.2 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 轮询的批次大小和让权时机,对延迟/吞吐的平衡至关重要。

15.3 DIM 动态中断调节

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) 更新硬件中断聚合参数。

15.4 CQ 池(共享 CQ)

内核提供 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 亲和性分布。

15.5 CQ 的 irq_poll 集成

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 饥饿。


16. 用户内存注册与 ib_umem

16.1 ib_umem 结构

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;  // 是否可写
};

16.2 ib_umem_get 实现分析

drivers/infiniband/core/umem.c:164ib_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(可用于数据传输)

16.3 ib_umem_find_best_pgsz

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 倍,显著提升注册性能。

16.4 umem 释放路径

umem.c:284ib_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);
}

17. On-Demand Paging 深度分析

ODP(On-Demand Paging)允许注册 MR 时不预先 pin 住所有页面,而是在 HCA 访问时触发缺页处理。

17.1 ODP umem 结构

include/rdma/ib_umem_odp.hib_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(整个地址空间)
};

17.2 ODP 初始化(umem_odp.c:58)

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), ...);
}

17.3 ODP 缺页处理流程

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()
  --> 驱动可以在下次访问时重新建立映射

17.4 隐式 ODP vs 显式 ODP

特性 显式 ODP 隐式 ODP
注册粒度 特定虚拟地址范围 整个进程地址空间(0 到 ULONG_MAX)
MR 数量 每块内存一个 MR 全局唯一一个 MR
典型场景 普通应用 UCX/MPI 的零配置 RDMA
代码位置 ib_umem_odp_alloc_child() ib_umem_odp_alloc_implicit()

18. uverbs 用户态 Verbs 内核实现

18.1 uverbs 设备驱动初始化

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));
}

18.2 uverbs 文件结构体

打开 /dev/infiniband/uverbs0
  --> ib_uverbs_open()
  --> 分配 ib_uverbs_file
        .device       = ib_uverbs_device
        .ucontext     = NULL(懒初始化)
        .ref          = kref
        .umap_lock    = mutex
  --> 后续 ioctl 操作通过此 file 访问 device 和 ucontext

18.3 ioctl 路由

新版 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 写回用户空间

18.4 异步事件文件

用户进程可以通过 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() 获取这些事件。

18.5 uverbs 与 ucontext 懒初始化

ib_ucontext 不在 open() 时创建,而是在第一次 alloc_pdget_context 命令时初始化(通过 ib_uverbs_get_ucontext_file() 懒获取)。这允许同一个进程的多个 ib_uverbs_file 共享设备,而无需立即分配驱动资源。


19. iWARP RDMA over TCP/IP

iWARP 在标准 TCP/IP 网络上实现 RDMA,适合无需专用无损以太网的场景。

19.1 iWARP CM(iwcm.c)

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},
};

19.2 iWARP 与 RoCE 的关键差异

+---------------------------+---------------------------+
|         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)    |
+---------------------------+---------------------------+

19.3 iWARP MR 特殊要求

由于 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。

19.4 SoftiWARP(siw)

drivers/infiniband/sw/siw/ 是纯软件的 iWARP 实现,与 SoftRoCE 类似:

siw 架构:
  RDMA Verbs API
       |
  siw 驱动(纯内核 TCP)
       |
  内核 TCP/IP 协议栈
       |
  任意以太网网卡

SoftiWARP 可用于在没有 iWARP 硬件的情况下测试 iWARP 兼容性。


20. RDMA RW API 内核封装层

20.1 rw.c 的作用

drivers/infiniband/core/rw.c 提供了一个高层次的 RDMA Read/Write 封装 API,供 NVMe-oF、iSER 等内核 ULP 使用,屏蔽了底层的 Fast Registration 细节。

20.2 rdma_rw_ctx 结构

// 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 池获取
};

20.3 rdma_rw_init_one_mr(rw.c:92)

对于需要 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;
}

20.4 force_mr 模块参数

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 路径,用于测试该代码路径的正确性。


21. MR 池(mr_pool)与 Fast Registration

21.1 mr_pool.c 实现

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);
}

21.2 MR 池工作流程

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()

21.3 rxe_mr.c 的 rkey 生成机制

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 的不可预测性,防止越权访问。


22. SoftRoCE 请求端与响应端状态机

22.1 rxe_task 调度机制

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,确保下一次循环再处理。

22.2 响应端(rxe_responder)状态机

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 不保证有序,直接丢弃乱序包
            ...
    }
}

22.3 请求端重传机制

drivers/infiniband/sw/rxe/rxe_req.c:37req_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);
}

23. RoCE 无损网络与流量控制

23.1 为什么 RoCE 需要无损网络

InfiniBand 协议基于"无丢包"假设设计:

  • 重传超时(QP_TIMEOUT)为指数退避,设计为处理偶发错误
  • 高丢包率会导致大量 WC 错误(IB_WC_RETRY_EXC_ERR
  • 与 TCP 不同,RDMA 没有滑动窗口拥塞控制

23.2 PFC(Priority Flow Control,IEEE 802.1Qbb)

以太网帧格式(带 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。

23.3 ECN(Explicit Congestion Notification)

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 中实现,对内核透明。

23.4 无损 RoCE 的 DSCP 标记

在 RoCE v2 报文中,通过 IP DSCP 字段标记流量优先级,确保 PFC 正确工作:

IP TOS/DSCP 字段(6 位)
  --> 通过 iptables/tc 规则映射到以太网 CoS(VLAN PCP)
  --> 交换机根据 CoS 应用 PFC

Linux rdma_cm 通过 QP 的 AH 属性中的 traffic_class 字段设置 DSCP

24. 设备管理与资源追踪

24.1 device.c 设备注册机制

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() 回调,实现模块化的设备探测通知机制。

24.2 全局工作队列

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 上运行。

24.3 restrack 资源追踪

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_pdib_qpib_cqib_mr 都嵌入一个 rdma_restrack_entry,在创建时通过 rdma_restrack_add() 注册,销毁时通过 rdma_restrack_del() 注销。

24.4 net namespace 支持

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 设备隔离。


25. NVMe-oF RDMA 深度分析

25.1 NVMe-oF 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                                 |
|                                                     |
+---------------------------------------------------+

25.2 NVMe-oF 队列创建过程

// 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);
}

25.3 NVMe-oF 与普通 RDMA 应用的关键差异

特性 普通 RDMA 应用 NVMe-oF RDMA
MR 管理 静态注册(长期有效) 动态 Fast Reg(每 I/O
内存来源 用户应用缓冲区 blk-mq 的 bio 缓冲区
连接数 应用决定 每个 nvme queue 一个 QP
传输方向 双向 读操作由 Target RDMA Write
错误恢复 应用层处理 自动重连(fabric error)

25.4 T10-DIF 数据完整性集成

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 参与,实现硬件加速的端到端数据完整性。


26. MPI 与 RDMA 集成

26.1 MPI 的 RDMA 使用模式

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 网卡

26.2 UCX 的 RDMA 使用策略

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 和延迟/吞吐权衡)

26.3 RDMA 在 MPI 中的性能分析

集群规模对 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):减少接收队列内存

26.4 MPI 集体操作的 RDMA 优化

MPI_Allreduce(全规约)的 RDMA 优化

传统实现:
  MPI_Send + MPI_Recv 的树形归约

RDMA 优化(使用原子操作):
  对支持 IB_WR_ATOMIC_FETCH_AND_ADD 的设备:
    所有节点并发写入共享内存区域
    使用原子 FAA 实现无锁规约
    延迟从 O(log N) 降至接近 O(1)

27. 性能调优与最佳实践

27.1 NUMA 亲和性

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

27.2 QP 参数优化

超时与重传参数

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

27.3 内存注册策略

策略对比:
  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 池 + 按需注册)

27.4 CQ 轮询策略

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)使用此策略

27.5 大页内存优化

# 分配大页(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 条目减少,转发效率提升)

28. 安全机制

28.1 保护域隔离

PD(Protection Domain)是 RDMA 安全的第一层防线:

同一 PD 内的 QP 才能使用同一 PD 内的 MR(lkey)
不同 PD 的 QP 无法使用对方 MR,即使知道 lkey 值

实现原理:
  HCA 在验证 WR 时检查:
    qp.pd == lkey 所属 mr.pd   (本地访问检查)
    rkey 中编码了 PD 信息      (远端访问检查)

28.2 rkey 安全性

远端 rkey 防止未授权的 RDMA Read/Write:

  1. rkey 是 32 位随机数,不可猜测(SoftRoCE 使用 CSPRNG)
  2. rkey 与 iova(虚拟地址)+ length 绑定,越界访问被拒绝
  3. 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;
}

28.3 MAD 安全(子网管理)

IB 子网管理通过 MAD(Management Datagram,QP0/QP1)进行,内核通过 ib_mad.cuser_mad.c 提供接口:

  • user_mad.c/dev/infiniband/umad0,允许用户空间子网管理器(如 OpenSM)直接发送 MAD
  • 访问需要 CAP_NET_ADMIN 权限
  • rdma_dev_has_raw_cap() 检查 CAP_NET_RAW(用于 Raw Packet QP)

28.4 RDMA cgroup 资源限制

通过 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 分析生成