├── .clang-format ├── .gitignore ├── .vscode └── settings.json ├── README.md ├── TODO.md ├── blog ├── InfiniBand, Verbs, RDMA _ The Geek in the Corner.pdf ├── 【RDMA】RDMA技术详解.pdf └── 参考.md ├── docs ├── ibverbs_api.md ├── images │ └── Markdown-image-2021-12-04-10-39-50.png ├── 性能测试.md ├── 术语.md ├── 核心概念.md ├── 注意和优化.md └── 编程流程.md ├── manual └── RDMA_Aware_Programming_user_manual.pdf ├── tests └── basic │ ├── Makefile │ ├── build │ ├── pingpong │ └── rdma_device │ ├── pingpong.cc │ ├── rc_pingpong_test.sh │ └── rdma_device.cc └── tmp.cc /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: Google 4 | AccessModifierOffset: -1 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveMacros: false 7 | AlignConsecutiveAssignments: false 8 | AlignConsecutiveDeclarations: false 9 | AlignEscapedNewlines: Left 10 | AlignOperands: true 11 | AlignTrailingComments: true 12 | AllowAllArgumentsOnNextLine: true 13 | AllowAllConstructorInitializersOnNextLine: true 14 | AllowAllParametersOfDeclarationOnNextLine: true 15 | AllowShortBlocksOnASingleLine: Never 16 | AllowShortCaseLabelsOnASingleLine: false 17 | AllowShortFunctionsOnASingleLine: All 18 | AllowShortLambdasOnASingleLine: All 19 | AllowShortIfStatementsOnASingleLine: WithoutElse 20 | AllowShortLoopsOnASingleLine: true 21 | AlwaysBreakAfterDefinitionReturnType: None 22 | AlwaysBreakAfterReturnType: None 23 | AlwaysBreakBeforeMultilineStrings: true 24 | AlwaysBreakTemplateDeclarations: Yes 25 | BinPackArguments: true 26 | BinPackParameters: true 27 | BraceWrapping: 28 | AfterCaseLabel: false 29 | AfterClass: false 30 | AfterControlStatement: false 31 | AfterEnum: false 32 | AfterFunction: false 33 | AfterNamespace: false 34 | AfterObjCDeclaration: false 35 | AfterStruct: false 36 | AfterUnion: false 37 | AfterExternBlock: false 38 | BeforeCatch: false 39 | BeforeElse: false 40 | IndentBraces: false 41 | SplitEmptyFunction: true 42 | SplitEmptyRecord: true 43 | SplitEmptyNamespace: true 44 | BreakBeforeBinaryOperators: None 45 | BreakBeforeBraces: Attach 46 | BreakBeforeInheritanceComma: false 47 | BreakInheritanceList: BeforeColon 48 | BreakBeforeTernaryOperators: true 49 | BreakConstructorInitializersBeforeComma: false 50 | BreakConstructorInitializers: BeforeColon 51 | BreakAfterJavaFieldAnnotations: false 52 | BreakStringLiterals: true 53 | ColumnLimit: 100 54 | CommentPragmas: '^ IWYU pragma:' 55 | CompactNamespaces: false 56 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 57 | ConstructorInitializerIndentWidth: 4 58 | ContinuationIndentWidth: 4 59 | Cpp11BracedListStyle: true 60 | DeriveLineEnding: true 61 | DerivePointerAlignment: true 62 | DisableFormat: false 63 | ExperimentalAutoDetectBinPacking: false 64 | FixNamespaceComments: true 65 | ForEachMacros: 66 | - foreach 67 | - Q_FOREACH 68 | - BOOST_FOREACH 69 | IncludeBlocks: Regroup 70 | IncludeCategories: 71 | - Regex: '^' 72 | Priority: 2 73 | SortPriority: 0 74 | - Regex: '^<.*\.h>' 75 | Priority: 1 76 | SortPriority: 0 77 | - Regex: '^<.*' 78 | Priority: 2 79 | SortPriority: 0 80 | - Regex: '.*' 81 | Priority: 3 82 | SortPriority: 0 83 | IncludeIsMainRegex: '([-_](test|unittest))?$' 84 | IncludeIsMainSourceRegex: '' 85 | IndentCaseLabels: true 86 | IndentGotoLabels: true 87 | IndentPPDirectives: None 88 | IndentWidth: 4 89 | IndentWrappedFunctionNames: false 90 | JavaScriptQuotes: Leave 91 | JavaScriptWrapImports: true 92 | KeepEmptyLinesAtTheStartOfBlocks: false 93 | MacroBlockBegin: '' 94 | MacroBlockEnd: '' 95 | MaxEmptyLinesToKeep: 1 96 | NamespaceIndentation: None 97 | ObjCBinPackProtocolList: Never 98 | ObjCBlockIndentWidth: 2 99 | ObjCSpaceAfterProperty: false 100 | ObjCSpaceBeforeProtocolList: true 101 | PenaltyBreakAssignment: 2 102 | PenaltyBreakBeforeFirstCallParameter: 1 103 | PenaltyBreakComment: 300 104 | PenaltyBreakFirstLessLess: 120 105 | PenaltyBreakString: 1000 106 | PenaltyBreakTemplateDeclaration: 10 107 | PenaltyExcessCharacter: 1000000 108 | PenaltyReturnTypeOnItsOwnLine: 200 109 | PointerAlignment: Left 110 | RawStringFormats: 111 | - Language: Cpp 112 | Delimiters: 113 | - cc 114 | - CC 115 | - cpp 116 | - Cpp 117 | - CPP 118 | - 'c++' 119 | - 'C++' 120 | CanonicalDelimiter: '' 121 | BasedOnStyle: google 122 | - Language: TextProto 123 | Delimiters: 124 | - pb 125 | - PB 126 | - proto 127 | - PROTO 128 | EnclosingFunctions: 129 | - EqualsProto 130 | - EquivToProto 131 | - PARSE_PARTIAL_TEXT_PROTO 132 | - PARSE_TEST_PROTO 133 | - PARSE_TEXT_PROTO 134 | - ParseTextOrDie 135 | - ParseTextProtoOrDie 136 | CanonicalDelimiter: '' 137 | BasedOnStyle: google 138 | ReflowComments: true 139 | SortIncludes: true 140 | SortUsingDeclarations: true 141 | SpaceAfterCStyleCast: false 142 | SpaceAfterLogicalNot: false 143 | SpaceAfterTemplateKeyword: true 144 | SpaceBeforeAssignmentOperators: true 145 | SpaceBeforeCpp11BracedList: false 146 | SpaceBeforeCtorInitializerColon: true 147 | SpaceBeforeInheritanceColon: true 148 | SpaceBeforeParens: ControlStatements 149 | SpaceBeforeRangeBasedForLoopColon: true 150 | SpaceInEmptyBlock: false 151 | SpaceInEmptyParentheses: false 152 | SpacesBeforeTrailingComments: 2 153 | SpacesInAngles: false 154 | SpacesInConditionalStatement: false 155 | SpacesInContainerLiterals: true 156 | SpacesInCStyleCastParentheses: false 157 | SpacesInParentheses: false 158 | SpacesInSquareBrackets: false 159 | SpaceBeforeSquareBrackets: false 160 | Standard: Auto 161 | StatementMacros: 162 | - Q_UNUSED 163 | - QT_REQUIRE_VERSION 164 | TabWidth: 8 165 | UseCRLF: false 166 | UseTab: Never 167 | ... 168 | 169 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "cassert": "cpp" 4 | } 5 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RDMA-Programming 2 | rdma编程学习 3 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | 3 | 1. ODP: 4 | 2. ibv_alloc_dm 设备内存怎么使用, 实验室的网卡不支持 5 | 3. 测试参数max_qp_rd_atom的影响,即作为目标,同时RDMA READ的个数 6 | 4. 好好测试各个参数对性能的影响 7 | 8 | 国外写的有关rdma编程的论文 9 | 有一篇关于流控的可以参考 10 | 11 | ## 可能影响性能的参数 12 | 13 | QP的MTU 14 | max_qp_rd_atom(设置qp的属性,查询device的属性) 15 | -------------------------------------------------------------------------------- /blog/InfiniBand, Verbs, RDMA _ The Geek in the Corner.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zzh-wisdom/RDMA-Programming/466ff9ad7dfc50d579ef09984501182b746ab2ce/blog/InfiniBand, Verbs, RDMA _ The Geek in the Corner.pdf -------------------------------------------------------------------------------- /blog/【RDMA】RDMA技术详解.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zzh-wisdom/RDMA-Programming/466ff9ad7dfc50d579ef09984501182b746ab2ce/blog/【RDMA】RDMA技术详解.pdf -------------------------------------------------------------------------------- /blog/参考.md: -------------------------------------------------------------------------------- 1 | # 参考 2 | 3 | 【RDMA】RDMA 学习资料总目录: 4 | 5 | 1. 【RDMA】技术详解(一):RDMA概述: 6 | 2. 【RDMA】技术详解(二):Send Receive操作: 7 | 3. 【RDMA】技术详解(三):理解RDMA Scatter Gather List|聚散表: 8 | 4. 【RDMA】技术详解(四):RDMA之Verbs和编程步骤: (包含一些文档的获得方法) 9 | 10 | -------------------------------------------------------------------------------- /docs/ibverbs_api.md: -------------------------------------------------------------------------------- 1 | # ibverbs 2 | 3 | 参考: 4 | 5 | - [1. 初始化](#1-初始化) 6 | - [1.1. ibv_fork_init](#11-ibv_fork_init) 7 | - [2. RDMA设备](#2-rdma设备) 8 | - [2.1. ibv_get_device_list](#21-ibv_get_device_list) 9 | - [2.2. ibv_free_device_list](#22-ibv_free_device_list) 10 | - [2.3. ibv_get_device_name](#23-ibv_get_device_name) 11 | - [2.4. ibv_get_device_guid](#24-ibv_get_device_guid) 12 | - [2.5. 设备打开与关闭](#25-设备打开与关闭) 13 | - [2.6. 特定设备的查询函数](#26-特定设备的查询函数) 14 | - [3. QP 创建](#3-qp-创建) 15 | - [3.1. 保护域 PD](#31-保护域-pd) 16 | - [3.2. 完成队列 cq](#32-完成队列-cq) 17 | - [4. 内存区域 MR](#4-内存区域-mr) 18 | - [5. 交换QP信息](#5-交换qp信息) 19 | - [6. 发送和接受](#6-发送和接受) 20 | - [7. CQ events](#7-cq-events) 21 | - [8. 错误/意外事件](#8-错误意外事件) 22 | - [9. Address Handles](#9-address-handles) 23 | 24 | ibverbs API 总览 25 | 26 | ## 1. 初始化 27 | 28 | ### 1.1. [ibv_fork_init](https://www.rdmamojo.com/2012/05/24/ibv_fork_init/) 29 | 30 | 前提:该函数应该在 libibverbs 中的其他任何函数调用前调用 31 | 32 | ibv_fork_init()初始化 libibverbs 的数据结构以正确处理fork()函数调用并避免数据损坏,无论fork()是显式调用还是隐式调用(例如在system()、popen()等中)。 33 | 34 | 这个函数不常用。 35 | 36 | ## 2. RDMA设备 37 | 38 | ### 2.1. [ibv_get_device_list](https://www.rdmamojo.com/2012/05/31/ibv_get_device_list/) 39 | 40 | ```cpp 41 | struct ibv_device** ibv_get_device_list(int *num_devices); 42 | ``` 43 | 44 | 返回当前可用的**以 NULL 结尾**的 RDMA 设备数组。应该使用`ibv_free_device_list()`释放数组。 45 | 46 | 不应直接访问数组条目。相反,它们应该与以下服务动词一起使用: ibv_get_device_name()、ibv_get_device_guid()和ibv_open_device()。 47 | 48 | **参数:** 49 | 50 | num_devices: (可选)如果不为NULL,则设置为数组中返回的设备数。 51 | 52 | **返回值:** 53 | 54 | 在成功时返回可用 RDMA 设备的数组,如果请求失败则返回 NULL 并设置errno。如果未找到设备,则将 num_devices 设置为 0,并返回非 NULL。 55 | 56 | 可能的errno值是: 57 | 58 | - EPERM - 权限被拒绝。 59 | - ENOMEM - 内存不足,无法完成操作。 60 | - ENOSYS - 没有对 RDMA 的内核支持。 61 | 62 | ### 2.2. [ibv_free_device_list](https://www.rdmamojo.com/2012/06/07/ibv_free_device_list/) 63 | 64 | ibv_free_device_list() 释放 RDMA 设备数据列表。 65 | 66 | 释放数组后,指向未使用 ibv_open_device() 打开的设备的指针将不再有效。客户端代码必须打开所有设备,它打算在调用ibv_free_device_list()之前使用。 67 | 68 | ### 2.3. ibv_get_device_name 69 | 70 | man ibv_get_device_name 71 | 72 | ```cpp 73 | const char *ibv_get_device_name(struct ibv_device *device); 74 | ``` 75 | 76 | 返回与 RDMA 设备设备关联的用户可读名称。 77 | 78 | ### 2.4. ibv_get_device_guid 79 | 80 | ```cpp 81 | uint64_t ibv_get_device_guid(struct ibv_device *device); 82 | ``` 83 | 84 | 返回 RDMA 设备device的全局唯一标识符 (GUID) 85 | 86 | 按网络字节顺序排列的设备 GUID。 87 | 88 | ### 2.5. 设备打开与关闭 89 | 90 | ```cpp 91 | #include 92 | struct ibv_context *ibv_open_device(struct ibv_device *device); 93 | int ibv_close_device(struct ibv_context *context); 94 | ``` 95 | 96 | ibv_open_device() 打开设备 device 并创建上下文以供进一步使用。 97 | 失败时返回 NULL 98 | 99 | ibv_close_device() 关闭上下文,成功返回0,失败则返回-1; 100 | 101 | 注意: 102 | 103 | ibv_close_device() 不会释放使用上下文context分配的所有资源。 为避免资源泄漏,**用户应在关闭上下文之前释放所有关联的资源**。 104 | 105 | 为防止资源(如内存、文件描述符、RDMA 对象编号)泄漏。使用这些孤立资源可能会导致分段错误。但是,当该过程结束时,操作系统将自动清理这些资源。 106 | 107 | ### 2.6. 特定设备的查询函数 108 | 109 | ```cpp 110 | int ibv_query_device(struct ibv_context *context, 111 | struct ibv_device_attr *device_attr); 112 | ``` 113 | 114 | ibv_query_device() 返回具有上下文context的设备属性。 参数 device_attr 指向ibv_device_attr结构的指针,如 <infiniband/verbs.h> 中所定义。 115 | 116 | 成功时返回 0,失败时返回 errno 的值(指示失败原因)。 117 | 118 | **注意:** 119 | 120 | 此函数返回的最大值是设备支持的资源的上限。 但是,可能无法使用这些最大值,因为可以创建的任何资源的实际数量可能会受到计算机配置、主机内存量、用户权限以及其他用户/进程已在使用的资源数量的限制。 121 | 122 | 注意:该函数已经过时,新函数如下: 123 | 124 | ```cpp 125 | int ibv_query_device_ex(struct ibv_context *context, 126 | struct ibv_device_attr_ex *attr); 127 | ``` 128 | 129 | 返回具有上下文上下文的设备属性。 参数 attr 是指向ibv_device_attr_ex结构的指针,如 <infiniband/verbs.h> 中所定义。 130 | 131 | 成功时返回 0,失败时返回 errno 的值(指示失败原因)。 132 | 133 | ```cpp 134 | int ibv_query_port(struct ibv_context *context, uint8_t port_num, 135 | struct ibv_port_attr *port_attr); 136 | ``` 137 | 138 | ibv_query_port() 通过指针port_attr返回设备上下文context的端口port_num 属性。 参数 port_attr 是一个ibv_port_attr结构,如 <infiniband/verbs.h> 中所定义。 139 | 140 | 成功时返回 0,失败时返回 errno 的值(指示失败原因)。 141 | 142 | ```cpp 143 | int ibv_query_pkey(struct ibv_context *context, 144 | uint8_t port_num, 145 | int index, 146 | uint16_t *pkey); 147 | ``` 148 | 149 | ibv_query_pkey() 通过指针 pkey 返回端口 port_num 的条目 index 中的P_Key值(按网络字节顺序)用于设备上下文context。 150 | 151 | 返回 RDMA 设备端口的 P_Key 表中的索引值。 152 | 153 | 成功时返回 0,出错时返回 -1。 154 | 155 | ```cpp 156 | int ibv_query_gid(struct ibv_context *context, 157 | uint8_t port_num, 158 | int index, 159 | union ibv_gid *gid); 160 | ``` 161 | 162 | 查询 InfiniBand 端口的 GID 表 163 | 164 | returns 0 on success, and -1 on error. 165 | 166 | ## 3. QP 创建 167 | 168 | ### 3.1. 保护域 PD 169 | 170 | ```cpp 171 | struct ibv_pd *ibv_alloc_pd(struct ibv_context *context); 172 | int ibv_dealloc_pd(struct ibv_pd *pd); 173 | ``` 174 | 175 | ibv_alloc_pd() 为 RDMA 设备上下文context分配 PD。失败时返回null。 176 | 177 | ibv_dealloc_pd() 解除分配 PD pd。成功返回0,否则返回错误码。 178 | 179 | ### 3.2. 完成队列 cq 180 | 181 | ```cpp 182 | struct ibv_cq *ibv_create_cq(struct ibv_context *context, int cqe, 183 | void *cq_context, 184 | struct ibv_comp_channel *channel, 185 | int comp_vector); 186 | 187 | int ibv_destroy_cq(struct ibv_cq *cq); 188 | 189 | int ibv_resize_cq(struct ibv_cq *cq, int cqe); 190 | ``` 191 | 192 | 为 RDMA 设备上下文创建完成队列 (CQ)。 193 | 194 | 当发送或接收队列中未完成的工作请求完成时,工作完成将添加到该工作队列的 CQ。此工作完成表明未完成的工作请求已完成(不再被视为未完成)并提供有关它的详细信息(状态、方向、操作码等)。 195 | 196 | 可以**共享单个 CQ 以在多个 QP 之间发送、接收和共享。工作完成保存信息以指定 QP 编号和它来自的队列(发送或接收)**。 197 | 198 | 用户可以定义 CQ 的最小尺寸。实际创建的大小可以等于或大于此值。 199 | 200 | 参数: 201 | 202 | - cqe: CQ的最小容量,可以为 [1..dev_cap.max_cqe] 203 | - cq_context: 可选地,用户设置的、且被用于设置 struct ibv_cq 的 cq_context 的值。使用谓词 ibv_get_cq_event() 等待完成事件通知时,将返回此值。 204 | - channel: 可选地,将用于指示**新的工作完成**已添加到此 CQ 的完成事件通道。NULL 表示不会使用任何完成事件通道。 205 | - comp_vector: 将用于对**完成事件**发出信号的 MSI-X 完成向量。如果这些中断的 IRQ 关联掩码已配置为将每个 MSI-X 中断分散到由不同内核处理,则**此参数**可用于将完成工作负载分散到多个内核上。值可以是 [0..context->num_comp_vectors)。 206 | 207 | 返回值: 208 | 209 | 成功则返回非NULL,否则设置相应的 errno: 210 | 211 | - EINVAL Invalid cqe, channel or comp_vector 212 | - ENOMEM Not enough resources to complete this operation 213 | 214 | ibv_destroy_cq() 成功时返回 0,失败时返回 errno 的值(指示失败原因)。 215 | 216 | 如果**任何队列对仍与此 CQ 关联,则 ibv_destroy_cq() 将失败**。 217 | 218 | ```cpp 219 | struct ibv_qp *ibv_create_qp(struct ibv_pd *pd, 220 | struct ibv_qp_init_attr *qp_init_attr); 221 | 222 | int ibv_destroy_qp(struct ibv_qp *qp); 223 | ``` 224 | 225 | 226 | 创建的qp属性,由qp_init_attr指定。 227 | 228 | **对于内敛的数据大小:** 229 | 230 | 对于这些设备,建议尝试创建具有所需消息大小的 QP,并在 QP 创建失败时继续减小它。 231 | 232 | qp_init_attr 同时是传入和传出参数。 233 | 234 | 大多参数都需要通过不断设置和重试。 235 | 236 | **使用反复试验,应该可以获得此特定 RDMA 设备的正确属性。** 237 | 238 | ## 4. 内存区域 MR 239 | 240 | ```cpp 241 | struct ibv_mr *ibv_reg_mr(struct ibv_pd *pd, void *addr, 242 | size_t length, int access); 243 | 244 | struct ibv_mr *ibv_reg_mr_iova(struct ibv_pd *pd, void *addr, 245 | size_t length, uint64_t hca_va, 246 | int access); 247 | 248 | int ibv_dereg_mr(struct ibv_mr *mr); 249 | ``` 250 | 251 | ibv_reg_mr() 注册与保护域 pd 关联的内存区域 (MR)。 MR 的起始地址是addr,其大小是length。 参数 access 描述了所需的内存保护属性;它是 0 或以下一个或多个标志的按位 OR:参考 man 文档。 252 | 253 | ibv_reg_mr_iova() 与普通reg_mr相同,只是允许用户在通过 lkey 或 rkey 时指定 MR 的虚拟基址。内存区域中的偏移量计算为"addr + (iova - hca_va)"。为hca_va指定 0 与 IBV_ACCESS_ZERO_BASED 具有相同的效果。 254 | 255 | ibv_dereg_mr() 注销 MR mr。 256 | 257 | **返回值**: 258 | 259 | ibv_reg_mr() / ibv_reg_mr_iova() 返回指向已注册 MR 的指针,**如果请求失败,则返回 NULL**。 本地键 (L_Key) 字段 lkey 在发布带有 ibv_post_* 谓词的缓冲区时用作结构体 ibv_sge 的 lkey 字段,远程进程使用远程键 (R_Key) 字段 rkey 来执行 Atomic 和 RDMA 操作。 远程进程将此 rkey 作为传递给 ibv_post_send 函数的结构体ibv_send_wr rkey 字段。 260 | 261 | ibv_dereg_mr() 在成功时返回 0,或在失败时返回 errno 的值(指示失败原因)。 262 | 263 | 如果任何内存窗口仍绑定到此 MR,则 ibv_dereg_mr() 将失败。 264 | 265 | ## 5. 交换QP信息 266 | 267 | ```cpp 268 | int ibv_modify_qp(struct ibv_qp *qp, struct ibv_qp_attr *attr, 269 | int attr_mask); 270 | ``` 271 | 272 | 273 | 274 | 修改队列对 (QP) 的属性 275 | 276 | 更改的属性描述了 QP 的发送和接收属性。在UC和RC QP中,这意味着将QP与远程QP连接。 277 | 278 | 在 Infiniband 中,应向子网管理员 (SA) 执行路径查询,以确定 QP 应配置哪些属性或作为最佳解决方案,请使用通信管理器 (CM) 或通用 RDMA CM 代理 (CMA)连接 QP。但是,有些应用程序更喜欢自己连接 QP,并通过**套接字**交换数据来决定要使用的 QP 属性。 279 | 280 | **返回值:** 281 | 282 | Value Description 283 | 0 On success 284 | errno On failure and no change will be done to the QP 285 | EINVAL Invalid value provided in attr or in attr_mask 286 | ENOMEM Not enough resources to complete this operation 287 | 288 | > 关于qp的状态: 289 | > SQD:SQ_DRAINED。在 IBV_EVENT_SQ_DRAINED 事件之后,并确保 QP 处于 IBV_QPS_SQD 状态后,用户可以安全地开始修改发送队列属性,因为不再有任何正在进行的发送消息。因此,现在可以安全地修改 QP 的操作特性并将其转换回完全操作的 RTS 状态。 290 | 291 | ## 6. 发送和接受 292 | 293 | ```cpp 294 | int ibv_post_recv(struct ibv_qp *qp, struct ibv_recv_wr *wr, 295 | struct ibv_recv_wr **bad_wr); 296 | ``` 297 | 298 | 299 | 300 | ibv_post_recv() 逐个检查**链表**中的所有条目,检查它是否有效,从中生成特定于硬件的接收请求,并将其添加到QP的接收队列的尾部,而无需执行任何上下文切换。RDMA 设备将在传入操作码后立即接收其中一个工作请求,该 QP 将使用接收请求 (RR)。如果由于接收队列已满或 WR 中的某个属性损坏而导致其中一个 WR 出现故障,它将立即停止并将指针返回到该 WR。 301 | 302 | 不与 SRQ 关联的 QP 将根据以下规则处理接收队列中的工作请求: 303 | 304 | - 如果 QP 处于 RESET 状态,则应立即返回错误。但是,它们可能是一些不遵循此规则的低级驱动程序(以消除对数据路径的额外检查,从而提供更好的性能)并且在此状态下发布接收请求可能会被默默忽略。 305 | - 如果 QP 处于 INIT 状态,则可以发布接收请求,但不会处理它们。 306 | - 如果 QP 处于 RTR、RTS、SQD 或 SQE 状态,则可以发布接收请求并对其进行处理。 307 | - 如果 QP 处于 ERROR 状态,则可以发布 Receive Requests 并且它们将在错误状态下完成。 308 | 309 | 如果 QP 与共享接收队列 (SRQ) 相关联,则必须调用ibv_post_srq_recv()而不是ibv_post_recv(),因为不会使用 QP 自己的接收队列。 310 | 311 | **返回值:** 312 | 313 | Value Description 314 | 0 On success 315 | errno On failure and no change will be done to the QP and bad_wr points to the RR that failed to be posted 316 | EINVAL Invalid value provided in wr 317 | ENOMEM Receive Queue is full or not enough resources to complete this operation 318 | EFAULT Invalid value provided in qp 319 | 320 | 常见问题: 321 | 322 | 我可以知道工作队列中有多少 WR 未完成吗? 323 | 不,你不能。您应该根据**已发布的 WR 数量和您轮询的工作完成数量**来跟踪未完成的 WR 数量。 324 | 325 | 哪些操作会消耗RR? 326 | 如果远程端使用以下操作码之一发布send请求,将消耗一个 RR: 327 | 328 | - Send 329 | - Send with Immediate 330 | - RDMA Write with immediate 331 | 332 | ibv_post_recv() 返回后,我可以(重新)使用接收请求吗? 333 | 是的。该动词将接收请求从 libibverbs 抽象转换为特定于硬件的接收请求,您可以(重新)使用接收请求和其中的 s/g 列表。 334 | 335 | ```cpp 336 | int ibv_post_send(struct ibv_qp *qp, struct ibv_send_wr *wr, 337 | struct ibv_send_wr **bad_wr); 338 | ``` 339 | 340 | 341 | 342 | ibv_post_send() 将工作请求 (WR) 的链接列表发布到队列对的发送队列 (QP)。ibv_post_send()逐个检查链表中的所有条目,检查它是否有效,从中生成特定于硬件的发送请求,并将其添加到QP的发送队列的尾部,而无需执行任何上下文切换。RDMA 设备将(稍后)以异步方式处理它。**如果其中一个 WR 由于发送队列已满或 WR 中的某个属性不正确而导致故障,它将立即停止并返回指向该 WR 的指针**。QP 将根据以下规则处理"发送"队列中的工作请求: 343 | 344 | - 如果 QP 处于 RESET、INIT 或 RTR 状态,则应立即返回错误。但是,它们可能是一些不遵循此规则的低级驱动程序(以消除数据路径中的额外检查,从而提供更好的性能),并且在一种或所有这些状态下发布发送请求可能会被静默忽略。 345 | - 如果 QP 处于 RTS 状态,则可以发布发送请求并对其进行处理。 346 | - 如果 QP 处于 SQE 或 ERROR 状态,则可以发布发送请求,这些请求将**在错误的情况下完成**。 347 | - 如果 QP 处于 SQD 状态,则可以发布发送请求,**但不会处理这些请求**。 348 | 349 | ```cpp 350 | int ibv_poll_cq(struct ibv_cq *cq, int num_entries, 351 | struct ibv_wc *wc); 352 | ``` 353 | 354 | 355 | 356 | "Work Completion"表示工作队列中的工作请求以及所有与 CQ 关联的工作队列中未完成的未发送信号的工作请求**已完成**。任何"接收请求"、"发送请求"和以错误结尾的"发送请求"将在处理结束后生成"工作完成"。 357 | 358 | ```cpp 359 | const char *ibv_wc_status_str(enum ibv_wc_status status); 360 | ``` 361 | 362 | 返回一个字符串,该字符串描述"工作完成状态"枚举值。 363 | 364 | ## 7. CQ events 365 | 366 | ```cpp 367 | struct ibv_comp_channel *ibv_create_comp_channel(struct ibv_context 368 | *context); 369 | 370 | int ibv_destroy_comp_channel(struct ibv_comp_channel *channel); 371 | ``` 372 | 373 | 374 | 375 | ibv_create_comp_channel() 为 RDMA 设备上下文创建完成事件通道。 376 | 377 | 此完成事件通道是由 libibverbs 引入的抽象,在 InfiniBand 架构谓词规范或 RDMA 协议谓词规范中不存在。完成事件通道实质上是文件描述符,用于将工作完成通知传递到用户空间进程。为完成队列 (CQ) 生成工作完成事件时,该事件将通过(附加到该 CQ 的)完成事件通道传递。这对于通过使用多个完成事件通道将完成事件引导到不同的线程或为不同的 CQ 提供不同的优先级可能很有用。 378 | 379 | **一个或多个完成队列可以与同一个完成事件通道相关联**。 380 | 381 | ```cpp 382 | int ibv_req_notify_cq(struct ibv_cq *cq, int solicited_only); 383 | ``` 384 | 385 | ibv_req_notify_cq() 请求完成队列 (CQ) 上的完成通知 386 | 387 | ibv_req_notify_cq() 请求在将**特定请求类型的下一个工作完成**添加到 CQ 时发出通知。在调用 ibv_req_notify_cq() 之前 CQ 中存在的任何工作完成都不会导致创建完成通知。完成通知将使用ibv_get_cq_event()读取。 388 | 389 | 可以请求两种类型的完成事件: 390 | 391 | - 请求的Solicited完成事件 - 当带有请求(Solicited)事件指示器集set的传入send或 RDMA 写入带立即数消息(即,远程端发布了一个发送请求,并在send_flags中设置了IBV_SEND_SOLICITED)导致生成一个**成功的接收工作完成**或任何不成功的(发送的接收)工作完成添加到 CQ 时发生。 392 | - 未经请求的Unsolicited完成事件 - 当任何工作完成被添加到 CQ 时发生,无论它是发送还是接收工作完成,以及它是成功还是不成功完成工作。 393 | 394 | 如果请求完成通知是挂起的,即调用了ibv_req_notify_cq()并且没有发生完成通知,则对具有相同 CQ 请求相同完成事件类型的ibv_req_notify_cq()的后续调用将不起作用;**只会生成一个完成通知**。调用 ibv_req_notify_cq() 仅在发生完成通知后才生效。 395 | 396 | 对于同一 CQ,对下一个完成事件的 Request Completion Notification 优先于 solicited 的 Request Completion Notification。也就是前面所说的,后者优先于前者。 397 | 398 | 一旦完成通知发生,如果一个人希望获得更多的完成通知,他必须再次调用ibv_req_notify_cq()。 399 | 400 | 参数:0 则是非 solicited,非0则是 solicited。 401 | 402 | 使用事件处理工作完成将减少应用程序的 CPU 消耗;您的进程将休眠,直到新的工作完成将添加到 CQ 中。 403 | 404 | 请求有关 Solicited 完成项的通知仅适用于与**接收队列关联的 CQ**。 405 | 406 | ```cpp 407 | int ibv_get_cq_event(struct ibv_comp_channel *channel, 408 | struct ibv_cq **cq, void **cq_context); 409 | 410 | void ibv_ack_cq_events(struct ibv_cq *cq, unsigned int nevents); 411 | ``` 412 | 413 | ibv_get_cq_event() 等待下一个完成事件, 根据使用 ibv_req_notify_cq() 为特定完成事件通道请求的完成事件类型。 414 | 415 | 默认情况下,ibv_get_cq_event() 是一个阻塞函数,如果没有任何要读取的完成事件,它将等到生成下一个完成事件。使用专用线程等待下一个完成事件发生会很有用。但是,如果希望以非阻塞方式读取事件,则可以执行此操作。可以使用 fcntl() 将完成事件通道通道中事件文件的文件描述符配置为非阻塞,然后使用 read()/poll()/epoll()/select() 读取此文件描述符,以确定是否存在任何等待读取的完成事件。在这篇文章中有一个关于如何做到这一点的例子。 416 | 417 | 使用 ibv_get_cq_event() 接收的所有完成事件都必须使用 **ibv_ack_cq_events()** 进行确认。 418 | 419 | 使用完成事件的典型用法如下: 420 | 421 | 第一阶段:准备 422 | 423 | 1. 创建与完成事件通道关联的 CQ 424 | 2. 在新的(第一个)完成事件时发出通知的请求 425 | 426 | 第二阶段:完成处理例程 427 | 428 | 3. 等待完成事件并确认它 429 | 4. 请求在下一个完成事件时发出通知 430 | 5. Empty the CQ 431 | 432 | 请注意,在 CQ 中没有相应的"工作完成"条目的情况下,可能会触发额外的事件。如果在步骤 4 和步骤 5 之间将完成条目添加到 CQ,然后在步骤 5 中清空(轮询)CQ,则会发生这种情况。就是触发了事件,但cq却没有对应的工作完成条目。 433 | 434 | ## 8. 错误/意外事件 435 | 436 | ```cpp 437 | int ibv_get_async_event(struct ibv_context *context, 438 | struct ibv_async_event *event); 439 | 440 | void ibv_ack_async_event(struct ibv_async_event *event); 441 | ``` 442 | 443 | ibv_get_async_event() 读取 RDMA 设备上下文context的下一个异步事件。 444 | 445 | 调用 ibv_open_device() 后,所有**异步事件都将排队到此上下文中**,并且调用 ibv_get_async_event() 将按顺序逐个读取它们。即使ibv_get_async_event() 将在事件生成后很长一段时间内被调用,它仍然会首先读取较旧的事件。不幸的是,事件没有任何时间概念,用户无法知道事件发生的时间。 446 | 447 | 默认情况下,ibv_get_async_event() 是一个阻塞函数,如果没有任何要读取的异步事件,它将一直等到生成下一个事件。使用专用线程等待下一个事件发生会很有用。但是,如果希望以非阻塞方式读取事件,则可以执行此操作。可以使用 fcntl() 将设备上下文中事件文件的文件描述符配置为非阻塞,然后使用 read()/poll()/epoll()/select() 读取此文件描述符,以确定是否存在等待读取的事件。在这篇文章中有一个关于如何做到这一点的例子。 448 | 449 | 调用 ibv_get_async_event() 是原子的,即使在多个线程中调用它,也可以保证同一事件不会被多个线程读取。 450 | 451 | 使用 ibv_get_async_event() 接收的每个事件都必须使用 ibv_ack_async_event() 进行确认。 452 | 453 | 这里有异步读事件的例子。 454 | 455 | ## 9. Address Handles 456 | 457 | ```cpp 458 | struct ibv_ah *ibv_create_ah(struct ibv_pd *pd, 459 | struct ibv_ah_attr *attr); 460 | 461 | int ibv_destroy_ah(struct ibv_ah *ah); 462 | ``` 463 | 464 | 创建与保护域关联的地址句柄 (AH)。 465 | 466 | 稍后,当发送请求 (SR) 发布到不可靠的数据报 QP 时,将使用此 AH。 467 | -------------------------------------------------------------------------------- /docs/images/Markdown-image-2021-12-04-10-39-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zzh-wisdom/RDMA-Programming/466ff9ad7dfc50d579ef09984501182b746ab2ce/docs/images/Markdown-image-2021-12-04-10-39-50.png -------------------------------------------------------------------------------- /docs/性能测试.md: -------------------------------------------------------------------------------- 1 | # 性能测试 2 | 3 | - [1. 设备端口号](#1-设备端口号) 4 | - [2. mtu](#2-mtu) 5 | - [3. rx_depth 每次 post RR 的个数](#3-rx_depth-每次-post-rr-的个数) 6 | - [4. 是否使用 event](#4-是否使用-event) 7 | - [5. odp & dm](#5-odp--dm) 8 | - [6. ts](#6-ts) 9 | - [其他](#其他) 10 | 11 | 使用 ibv_rc_pingpong 可运行程序 12 | 13 | ## 1. 设备端口号 14 | 15 | 同一个设备(同一台机器),不同 port: 16 | 17 | ```shell 18 | ibv_rc_pingpong --ib-port=1 --size=64 --iters=1000000 19 | ibv_rc_pingpong --ib-port=2 --size=64 --iters=1000000 localhost 20 | ``` 21 | 22 | ```txt 23 | 128000000 bytes in 3.29 seconds = 311.36 Mbit/sec 24 | 1000000 iters in 3.29 seconds = 3.29 usec/iter 25 | ``` 26 | 27 | 同一个设备(同一台机器),相同 port: 28 | 29 | ```shell 30 | ibv_rc_pingpong --ib-port=1 --size=64 --iters=1000000 31 | ibv_rc_pingpong --ib-port=1 --size=64 --iters=1000000 localhost 32 | ``` 33 | 34 | ```txt 35 | 128000000 bytes in 2.63 seconds = 389.83 Mbit/sec 36 | 1000000 iters in 2.63 seconds = 2.63 usec/iter 37 | ``` 38 | 39 | | size | 不同端口 | 相同端口 | 40 | | ---- | -------- | -------- | 41 | | 64 | 3.29 | 2.63 | 42 | | 1024 | 4.29 | 3.97 | 43 | | 16k | 9.53 | 9.99 | 44 | 45 | **结论**:**不同的端口号延迟会高一些,吞吐量有待测试**。 46 | 47 | > 不过这个测试没啥用,分布式环境下,端口号肯定都不同。 48 | 49 | ## 2. mtu 50 | 51 | ```shell 52 | ibv_rc_pingpong --ib-port=1 --size=4096 --iters=1000000 -m 1024 53 | ibv_rc_pingpong --ib-port=2 --size=4096 --iters=1000000 -m 1024 localhost 54 | ``` 55 | 56 | | size | mtu:256 | mtu:512 | mtu:1024 | mtu:2048 | mtu:4096 | 57 | | ---- | ------- | ------- | -------- | -------- | -------- | 58 | | 256 | 3.59 | 3.60 | 3.62 | 3.57 | 3.63 | 59 | | 512 | 3.74 | 3.84 | 3.87 | 3.78 | 3.90 | 60 | | 1k | 3.95 | 4.08 | 4.34 | 4.25 | 4.30 | 61 | | 2k | 4.37 | 4.42 | 4.74 | 5.21 | 5.21 | 62 | | 4k | 5.17 | 5.15 | 5.44 | 5.92 | 7.10 | 63 | | 8k | 6.81 | 6.61 | 6.86 | 7.29 | 8.51 | 64 | | 16k | 9.98 | 9.53 | 9.49 | 10.03 | 11.16 | 65 | 66 | ![](images/Markdown-image-2021-12-04-10-39-50.png) 67 | 68 | **结论**:**对于双边原语,发送小数据时(如小16k),mtu选择1024或者512时,性能是比较好的。但对于小于1k的数据,mtu的选取没有太大关系**。 69 | 70 | ibv_rc_pingpong 默认使用的mtu是 path MTU (default 1024) 71 | 72 | 对于基于send的rpc,或许可以适当设置小一点的mtu。不过如果是单双边结合的,这个优化就无所谓了。 73 | 74 | ## 3. rx_depth 每次 post RR 的个数 75 | 76 | size: 1024 77 | 78 | | rx_depth | 时延(us) | 79 | | -- | -- | 80 | | 1 | 16.12 | 81 | | 10 | 5.35 | 82 | | 100 | 4.33 | 83 | | 200 | 4.29 | 84 | | 400 | 4.28 | 85 | | 800 | 4.28 | 86 | 87 | 结论: **rx_depth 取 >200,时延保持较低**。 88 | 89 | ## 4. 是否使用 event 90 | 91 | | 使用事件 | 不使用事件 | 92 | | -- | -- | 93 | | 10.05 | 4.31 | 94 | 95 | 结论: 对于大型系统来说,**如果网络不是瓶颈,建议使用事件,减少对cpu的消耗**。否则使用轮询,减少网络延迟。 96 | 97 | ## 5. odp & dm 98 | 99 | 实验室设备不支持,不测试 100 | 101 | ## 6. ts 102 | 103 | 没差别,只是多一些统计信息而已 104 | 105 | ```shell 106 | --no ts, size: 1024 107 | local address: LID 0x0004, QPN 0x000487, PSN 0xad861a, GID :: 108 | local address: LID 0x0008, QPN 0x000488, PSN 0x1859c8, GID :: 109 | remote address: LID 0x0008, QPN 0x000488, PSN 0x1859c8, GID :: 110 | remote address: LID 0x0004, QPN 0x000487, PSN 0xad861a, GID :: 111 | 2048000000 bytes in 4.28 seconds = 3828.29 Mbit/sec 112 | 2048000000 bytes in 4.28 seconds = 3828.43 Mbit/sec 113 | 1000000 iters in 4.28 seconds = 4.28 usec/iter 114 | 1000000 iters in 4.28 seconds = 4.28 usec/iter 115 | --ts, size: 1024 116 | local address: LID 0x0004, QPN 0x000489, PSN 0x7097fe, GID :: 117 | local address: LID 0x0008, QPN 0x00048a, PSN 0xb53886, GID :: 118 | remote address: LID 0x0008, QPN 0x00048a, PSN 0xb53886, GID :: 119 | remote address: LID 0x0004, QPN 0x000489, PSN 0x7097fe, GID :: 120 | 2048000000 bytes in 4.27 seconds = 3841.27 Mbit/sec 121 | 2048000000 bytes in 4.27 seconds = 3841.42 Mbit/sec 122 | 1000000 iters in 4.27 seconds = 4.27 usec/iter 123 | 1000000 iters in 4.27 seconds = 4.27 usec/iter 124 | Max receive completion clock cycles = 281474976646913 125 | Max receive completion clock cycles = 281474976646935 126 | Min receive completion clock cycles = 1684 127 | Min receive completion clock cycles = 1708 128 | Average receive completion clock cycles = 3659180177.943563 129 | Average receive completion clock cycles = 4222130694.312377 130 | ``` 131 | 132 | ## 其他 133 | 134 | 对于send/recv, 这个设置越小,延迟就越小。 135 | 单边原语还没测试过 136 | 137 | uint32_t cfg_max_send_sge = 1; // 32, 138 | uint32_t cfg_max_recv_sge = 1; // 32 139 | 140 | -------------------------------------------------------------------------------- /docs/术语.md: -------------------------------------------------------------------------------- 1 | # 术语 2 | 3 | 1. Fabric: A local-area RDMA network is usually referred to as a fabric. 所谓Fabric,就是支持RDMA的局域网(LAN)。 4 | 2. Channel-IO:RDMA 在本端应用和远端应用间创建的一个消息通道; 5 | 3. Queue Pairs(QP):每个消息通道两端是两对QP; 6 | 4. Send Queue(SQ): 发送队列,队列中的内容为WQE; 7 | 5. Receive Queue(RQ):接收队列,队列中的内容为WQE; 8 | 6. Work Queue Element(WQE):工作队列元素,WQE指向一块用于存储数据的Buffer; 9 | 7. Work Queue(WQ): 工作队列,在发送过程中 WQ = SQ; 在接收过程中WQ = WQ; 10 | 8. Complete Queue(CQ): 完成队列,CQ用于告诉用户WQ上的消息已经被处理完成; 11 | 9. Work Request(WR):传输请求,WR描述了应用希望传输到Channel对端的消息内容,在WQ中转化为 WQE 格式的信息; 12 | 13 | CA 是Channel Adapter(通道适配器)的缩写。那么,CA就是将系统连接到Fabric的硬件组件。 在IBTA中,一个CA就是IB子网中的一个终端结点(End Node)。分为两种类型,一种是HCA, 另一种叫做TCA, 它们合称为xCA。其中, HCA(Host Channel Adapter)是支持"verbs"接口的CA, TCA(Target Channel Adapter)可以理解为"weak CA", 不需要像HCA一样支持很多功能。 而在IEEE/IETF中,CA的概念被实体化为RNIC(RDMA Network Interface Card), iWARP就把一个CA称之为一个RNIC。 14 | 15 | 简言之,在IBTA阵营中,CA即HCA或TCA; 而在iWARP阵营中,CA就是RNIC。 总之,无论是HCA、 TCA还是RNIC,它们都是CA, 它们的基本功能本质上都是生产或消费数据包(packet) 16 | 17 | ODP: on demand paging 18 | LRH: Local Route Headers 19 | GRH: Global Route Headers,对于用到子网的网络有用 20 | MTU: Maximum Transfer Unit 21 | AH: address handles. 22 | 23 | qkey: 只与 UD QP 有关 24 | lmc: LID Mask control (used when multiple LIDs are assigned to 25 | port) 26 | -------------------------------------------------------------------------------- /docs/核心概念.md: -------------------------------------------------------------------------------- 1 | # 核心概念 2 | 3 | ## Memory Registration(MR) | 内存注册 4 | 5 | RDMA 就是用来对内存进行数据传输。那么怎样才能对内存进行传输,很简单,注册。 因为RDMA硬件对用来做数据传输的内存是有特殊要求的。 6 | 7 | - 在数据传输过程中,应用程序**不能修改数据所在的内存**。 8 | - 操作系统不能对数据所在的内存进行page out操作 – **物理地址和虚拟地址的映射必须是固定不变**的。 9 | - 注意无论是DMA或者RDMA都要求物理地址连续,这是由DMA引擎所决定的。 10 | 11 | 那么怎么进行内存注册呢? 12 | 13 | - 创建两个key (local和remote)指向需要操作的内存区域 14 | - 注册的keys是数据传输请求的一部分 15 | 16 | 注册一个Memory Region之后,这个时候这个Memory Region也就有了它自己的属性: 17 | 18 | - context : RDMA操作上下文 19 | - addr : MR被注册的Buffer地址 20 | - length : MR被注册的Buffer长度 21 | - lkey:MR被注册的本地key 22 | - rkey:MR被注册的远程key 23 | 24 | ## Queues | 队列 25 | 26 | - 发送队列(SQ) 27 | - 接收队列(RQ) 28 | - 完成队列(CQ) 29 | 30 | > SQ和RQ通常成对创建,被称为Queue Pairs(QP) 31 | 32 | DMA是基于消息的传输协议,数据传输都是异步操作。 RDMA操作其实很简单,可以理解为: 33 | 34 | > 1. Host提交工作请求(WR)到工作队列(WQ): 工作队列包括发送队列(SQ)和接收队列(RQ)。工作队列的每一个元素叫做WQE, 也就是WR。 35 | > 2. Host从完成队列(CQ)中获取工作完成(WC): 完成队列里的每一个叫做CQE, 也就是WC。 36 | > 3. 具有RDMA引擎的硬件(hardware)就是一个队列元素处理器。 RDMA硬件不断地从工作队列(WQ)中去取工作请求(WR)来执行,执行完了就给完成队列(CQ)中放置工作完成(WC)。 37 | 38 | 从生产者-消费者的角度理解就是: 39 | 40 | > Host生产WR, 把WR放到WQ中去 41 | > RDMA硬件消费WR 42 | > RDMA硬件生产WC, 把WC放到CQ中去 43 | > Host消费WC 44 | 45 | ## 传输 46 | 47 | RDMA共有三种底层数据传输模式。 48 | 49 | 1. SEND/RECEIVE是双边操作,即必须要远端的应用感知参与才能完成收发。 50 | 2. READ和WRITE是单边操作,只需要本端明确信息的源和目的地址,远端应用不必感知此次通信,数据的读或存都通过远端的DMA在RNIC与应用buffer之间完成,再由远端RNIC封装成消息返回到本端。 51 | 3. 在实际中,SEND/RECEIVE多用于连接控制类报文,而数据报文多是通过READ/WRITE来完成的。 52 | 53 | ### 双边 54 | 55 | 对于双边操作为例,A向B发送数据的流程如下: 56 | 57 | 1. 首先,A和B都要创建并初始化好各自的QP,CQ 58 | 2. A和B分别向自己的WQ中注册WQE,对于A,WQ=SQ,WQE描述指向一个等到被发送的数据;对于B,WQ=RQ,WQE描述指向一块用于存储数据的buffer。 59 | 3. A的RNIC异步调度轮到A的WQE,解析到这是一个SEND消息,从buffer中直接向B发出数据。数据流到达B的RNIC后,B的WQE被消耗,并把数据直接存储到WQE指向的存储位置。 60 | 4. AB通信完成后,A的CQ中会产生一个完成消息CQE表示发送完成。与此同时,B的CQ中也会产生一个完成消息表示接收完成。每个WQ中WQE的处理完成都会产生一个CQE。 61 | 62 | ### 单边 63 | 64 | 对于单边操作,以存储网络环境下的存储为例(A作为文件系统,B作为存储介质): 65 | 66 | 1. 首先A、B建立连接,QP已经创建并且初始化。 67 | 2. 数据被存档在A的buffer地址VA,注意VA应该提前注册到A的RNIC,并拿到返回的local key,相当于RDMA操作这块buffer的权限。 68 | 3. A把数据地址VA,key封装到专用的报文传送到B,这相当于A把数据buffer的操作权交给了B。同时A在它的WQ中注册进一个WR,以用于接收数据传输的B返回的状态。 69 | 4. B在收到A的送过来的数据VA和R_key后,RNIC会把它们连同存储地址VB(VB应该也需要提前注册到B的RNIC)到封装RDMA READ,这个过程A、B两端不需要任何软件参与,就可以将A的数据存储到B的VB虚拟地址。 70 | 5. B在存储完成后,会向A返回整个数据传输的状态信息。 71 | 72 | ### RDMA 双边操作和单边操作的不同 73 | 74 | RC:面向连接的可靠服务 75 | 76 | UC:面向连接的不可靠服务 77 | 78 | UD:面向数据报的不可靠服务 79 | 80 | **面向连接 vs 面向数据报** 81 | 82 | 相同点:两者的通信均包括双方QP对的参与 83 | 不同点:面向连接的通信若有N个节点与之通信,本机需要N个QP对; 84 | 85 | 面向数据报的通信可以做到N个节点与之通信,本机仅需一个QP队; 86 | 87 | **one-sided RDMA VS two sided RDMA** 88 | 89 | 相同点:两者均绕过内核 90 | 不同点:**one-sided RDMA还绕过远端服务器的CPU** 91 | 92 | 93 | -------------------------------------------------------------------------------- /docs/注意和优化.md: -------------------------------------------------------------------------------- 1 | # 注意和优化 2 | 3 | 1. 发送整数(如立即数),注意网络字节顺序的转换: 4 | htonl 5 | 6 | 2. 选择性通知优化,对于很久没有新的send请求到来的情况,可以发送一个空的send请求,从而产生一个CQE,告诉用户之前的send已经完成。 7 | 8 | -------------------------------------------------------------------------------- /docs/编程流程.md: -------------------------------------------------------------------------------- 1 | # RDMA 编程流程 2 | 3 | ## 1. 硬件 4 | 5 | 两种硬件可以使用RDMA: 6 | 7 | 1. infiniband 8 | 2. RDMA over Ethernet 9 | 10 | 由于IB的成本较高,所以RoCE成为一种趋势。 11 | 12 | RoCE可以在以太网上运行RDMA协议,时延比普通以太网可以提升30%以上,也可以支持双协议栈,同时用TCP和RDMA,编程过程类似IB。 13 | 14 | ## 2. 建立 QP 连接 15 | 16 | 两种建链方式: 17 | 18 | 1. 通过 RDMA_CM 建链 19 | 2. 先通过 TCP 建链,通过tcp通道交换双方的设备信息,QP 信息,建立RDMA链路,然后关闭tcp链路 20 | 21 | 第二种更常用。 22 | 23 | ## 3. 编程流程 24 | 25 | ### 3.1. 初始化RDMA设备 26 | 27 | ibv_get_device_list()获取使用可以使用RDMA传输的设备个数,可以根据ibv_get_device_list结构中的dev_name找到需要使用的设备; 28 | 29 | struct ibv_device **ibv_get_device_list(int *num_devices); 30 | 31 | ibv_open_device()打开设备,获取设备句柄; 32 | 33 | ibv_query_device()查询设备,获取设备属性 34 | 35 | ibv_query_port()查询设备端口属性 36 | 37 | 如果类型为Ethernet,ibv_query_gid()获取设备GID,用于交换双方信息使用 38 | 39 | ### 3.2. 创建QP信息 40 | 41 | ibv_alloc_pd() 用于创建qp接口的参数 42 | 43 | ibv_create_cq() 创建CQ,一个CQ可以完成的CQE的个数,CQE与队列个数有关,队列多,CQE个数就设置多,否则设置少,一个CQ可以对应一个QP,也可以两个CQ对应一个QP。一个CQ包含发送和接收队列。 44 | 45 | ibv_create_qp() 创建 QP。类似 tcp 的 socket 46 | 47 | ### 3.3. 注册MR信息 48 | 49 | ibv_reg_mr()注册网卡内存信息,把操作系统物理内存注册到网卡 这一步可以提前到创建pd之后,然后可以根据需要创建cq和qp; 50 | 51 | ### 3.4. 交换QP信息 52 | 53 | ibv_modify_qp()交换双方QP信息,修改QP信息状态级 54 | 55 | Client端:先创建QP,修改状态级reset到INIT,修改INIT到RTR,然后发送到server端,server端创建QP,修改状态机有INIT到RTR,然后发送到客户端,客户端修改状态机有RTR到RTS,发送到server端,server端修改状态机有RTR到RTS,这样rmda链路简建立成功。 56 | 57 | ### 3.5. 发送和接收 58 | 59 | ibv_post_recv()接收消息接口 60 | 61 | ibv_post_send()发送消息接口 62 | 63 | ibv_poll_cq()用于查询cq队列是否有事件产生,如果有调用recv接口接收。 64 | 65 | ## Errors记录 66 | 67 | **IBV_WC_WR_FLUSH_ERR(5/0x5)** 68 | 69 | Work Request Flushed Error 70 | 71 | 当 QP 的传送状态处于 Error 状态时,任何操作都会引发该错误。 72 | 73 | **IBV_WC_RNR_RETRY_EXC_ERR(13/0xd)** 74 | 75 | Receiver-Not-Ready Retry Error 76 | 77 | 当接收端没有准备好 Recv Request 时发送端产生了一个 Send Request 就会发生 RNR_RETRY 错误。 78 | 79 | 要求 ibv_post_recv() 必须在 ibv_post_send 之前完成,所以一种基本的思路就是**一开始就 Post 一堆 Recv Request 到队列中去,然后检查当队列中的 Recv Request 少于一定数量时补充**,保证不管发送端什么时候 Post Send Request 时,接收端都有足够的 Recv Request 来接收。 80 | 81 | 问题是如果发送端毫无顾忌地可以任意发送数据,尤其是在 RDMA_WRITE 方式,接收端这边会不会来不及取走数据,就被发送端传过来的新数据覆盖掉了? 82 | 83 | 或者设置 **ibv_modify_qp() 参数中的 min_rnr_timer 以及 rnr_retry,前者是重试间隔时间,后者是重试次数**,当 rnr_retry 为 7 时表示重试无限次。这种方法可用于重试直到接收端确认取走数据,并且准备好下一次的 Recv Request,然后发送端再进行发送。 84 | 85 | 当发送端发生 RNR_RETRY 错误时,重新调用 ibv_post_send() 是没用的,因为此时 QP 已经进入错误状态,接下来不管什么样的操作都会继续引发 IBV_WC_WR_FLUSH_ERR 错误。 86 | 87 | 除非另外使用一种流控制的方式,不然上面的两种解决方案都总会存在一定的局限性。 88 | 89 | ## 编程示例 90 | 91 | rdma-core的源码目录下,为libibverbs和librdmacm都提供了简单的示例程序,大家编程时可以参考。 92 | 93 | **libibverbs** 94 | 95 | 位于rdma-core/libibverbs/examples/目录下,都使用最基础的IB_VERBS接口实现,所以建链方式都是基于Socket的。 96 | 97 | asyncwatch.c 查询指定RDMA设备是否有异步事件上报 98 | device_list.c 列出本端RDMA设备列表 99 | devinfo.c 查询并打印本端RDMA设备详细信息,没有双端数据交互 100 | rc_pingpong.c 基于RC服务类型双端数据收发示例 101 | srq_pingpong.c 基于RC服务类型双端数据收发示例,与上一个示例程序的差异是使用了SRQ而不是普通的RQ。 102 | ud_pingpong.c 基于UD服务类型双端数据收发示例 103 | ud_pingpong.c 基于UC服务类型双端数据收发示例 104 | xsrq_pingpong.c 基于XRC服务类型双端数据收发示例 105 | 106 | **librdmacm** 107 | 108 | 位于rdma-core/librdmacm/examples/目录下: 109 | 110 | rdma_client/server.c 基础示例,通过CM建链并使用CM VERBS进行数据收发。 111 | 112 | ## 常用工具 113 | 114 | 安装libibverbs后,会自带很多工具,如下所示。利用这些工具可以快速查看属性和测试性能。 115 | 116 | ```shell 117 | zzh@ted:~/github/rdma-core$ ib 118 | ibaddr ibping ibsysstat 119 | ib_atomic_bw ibportstate ibtracert 120 | ib_atomic_lat ibqueryerrors ibv_asyncwatch 121 | ibcacheedit ib_read_bw ibv_devices 122 | ibccconfig ib_read_lat ibv_devinfo 123 | ibccquery ibroute ibv_rc_pingpong 124 | ibfindnodesusing ibrouters ibv_srq_pingpong 125 | ibhosts ib_send_bw ibv_uc_pingpong 126 | ibidsverify ib_send_lat ibv_ud_pingpong 127 | iblinkinfo ibstat ibv_xsrq_pingpong 128 | ibnetdiscover ibstatus ib_write_bw 129 | ibnodes ibswitches ib_write_lat 130 | ``` 131 | -------------------------------------------------------------------------------- /manual/RDMA_Aware_Programming_user_manual.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zzh-wisdom/RDMA-Programming/466ff9ad7dfc50d579ef09984501182b746ab2ce/manual/RDMA_Aware_Programming_user_manual.pdf -------------------------------------------------------------------------------- /tests/basic/Makefile: -------------------------------------------------------------------------------- 1 | # 适合单元测试 2 | 3 | # 自动每个.cc 文件生成对应名称的可执行文件 4 | # 自动创建bin文件夹 5 | .PHONY: all clean 6 | 7 | SOURCE = $(wildcard *.cc) # 当前目录的所有 .cc 文件 8 | TARGETS = $(patsubst %.cc,%,$(SOURCE)) # 将源文件的.cc后缀名去掉 9 | 10 | CC = gcc 11 | CXX = g++ 12 | CXXFLAGS += --std=c++11 -g -Iinclude -Isrc -DNDEBUG # 这里可以添加头文件寻找的路径 13 | LDFLAGS = -lpthread -lpmem2 -lgtest -libverbs # 链接库,指定搜索路径 -rpath=. -L.. 14 | # 关于依赖库,参考 https://blog.csdn.net/q1302182594/article/details/42102961/ 15 | 16 | # DEPENCES = ../cpu.h ../chunk_allocator.h ../pm_allocator.h # 依赖头文件,只用来检测更新关系 17 | # OBJ_SOURCE = ../chunk_allocator.cc ../pm_allocator.cc # 被测试的源文件 18 | 19 | BUILD_DIR := ./build 20 | 21 | all: ${BUILD_DIR} $(TARGETS) 22 | 23 | %:%.cc # $(OBJ_SOURCE) $(DEPENCES) 24 | $(CXX) $(CXXFLAGS) -o $(BUILD_DIR)/$@ $< $(LDFLAGS) 25 | 26 | # 创建build目录 27 | ${BUILD_DIR}: 28 | $(shell if [ ! -e $(BUILD_DIR) ];then mkdir -p $(BUILD_DIR); fi) 29 | 30 | clean: 31 | rm -rf $(BUILD_DIR) *.o 32 | -------------------------------------------------------------------------------- /tests/basic/build/pingpong: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zzh-wisdom/RDMA-Programming/466ff9ad7dfc50d579ef09984501182b746ab2ce/tests/basic/build/pingpong -------------------------------------------------------------------------------- /tests/basic/build/rdma_device: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zzh-wisdom/RDMA-Programming/466ff9ad7dfc50d579ef09984501182b746ab2ce/tests/basic/build/rdma_device -------------------------------------------------------------------------------- /tests/basic/pingpong.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | const uintptr_t test_cq_context = 0; // 0x12345678 14 | 15 | // 对应: 16 | // ibv_rc_pingpong --size 4096 --iters 1000000 17 | // ibv_rc_pingpong --size 4096 --iters 1000000 localhost 18 | 19 | unsigned int cfg_iters = 1000000; 20 | int cfg_buf_size = 400; 21 | uint8_t cfg_ib_device_port = 1; 22 | unsigned int cfg_listen_port = 18515; 23 | 24 | uint32_t cfg_cq_len = 928; // 512 25 | uint32_t cfg_max_send_wr = 512; // 512 26 | uint32_t cfg_max_recv_wr = 512; // 512 27 | uint32_t cfg_max_send_sge = 32; // 32, 这个设置越小,延迟就越小 28 | uint32_t cfg_max_recv_sge = 1; // 32 29 | uint32_t cfg_max_inline_data = 1024; 30 | enum ibv_mtu cfg_mtu_enum = IBV_MTU_1024; 31 | uint8_t cfg_max_dest_rd_atomic = 1; // 自身作为目标的最大RDMA Reads & atomic操作数 32 | int cfg_max_rd_atomic = 1; // 自身作为发起者的最大RDMA Reads & atomic操作数 33 | // unsigned int cfg_send_flags = IBV_SEND_SIGNALED | IBV_SEND_INLINE; 34 | unsigned int cfg_send_flags = IBV_SEND_SIGNALED; 35 | 36 | bool cfg_use_event = false; 37 | uint8_t cfg_sl = 0; 38 | 39 | int global_page_size = 4096; 40 | 41 | struct my_context_t { 42 | struct ibv_context *ibv_ctx_; 43 | struct ibv_comp_channel *ibv_comp_channel_; 44 | struct ibv_pd *ibv_pd_; 45 | struct ibv_mr *ibv_mr_; 46 | struct ibv_cq *ibv_cq_; 47 | struct ibv_qp *ibv_qp_; 48 | struct ibv_qp_init_attr ibv_qp_init_attr_; 49 | struct ibv_port_attr portinfo; 50 | 51 | char *buf; 52 | uint32_t buf_size; 53 | unsigned int send_flags; 54 | int pending; // 标记将要完成的事情 55 | }; 56 | 57 | const char *ibv_qp_state_str(enum ibv_qp_state qp_state) { 58 | switch (qp_state) { 59 | case IBV_QPS_RESET: 60 | return "IBV_QPS_RESET"; 61 | break; 62 | case IBV_QPS_INIT: 63 | return "IBV_QPS_INIT"; 64 | break; 65 | case IBV_QPS_RTR: 66 | return "IBV_QPS_RTR"; 67 | break; 68 | case IBV_QPS_RTS: 69 | return "IBV_QPS_RTS"; 70 | break; 71 | case IBV_QPS_SQD: 72 | return "IBV_QPS_SQD"; 73 | break; 74 | case IBV_QPS_SQE: 75 | return "IBV_QPS_SQE"; 76 | break; 77 | case IBV_QPS_ERR: 78 | return "IBV_QPS_ERR"; 79 | break; 80 | case IBV_QPS_UNKNOWN: 81 | return "IBV_QPS_UNKNOWN"; 82 | break; 83 | 84 | default: 85 | break; 86 | } 87 | return nullptr; 88 | } 89 | 90 | int ibv_mtu_enum_to_value(enum ibv_mtu mtu) { 91 | switch (mtu) { 92 | case IBV_MTU_256: 93 | return 256; 94 | break; 95 | case IBV_MTU_512: 96 | return 512; 97 | break; 98 | case IBV_MTU_1024: 99 | return 1024; 100 | break; 101 | case IBV_MTU_2048: 102 | return 2048; 103 | break; 104 | case IBV_MTU_4096: 105 | return 4096; 106 | break; 107 | 108 | default: 109 | break; 110 | } 111 | return -1; 112 | } 113 | 114 | void print_qp_attr(struct ibv_qp_attr *attr, struct ibv_qp_init_attr *init_attr) { 115 | if (attr == nullptr) { 116 | if (init_attr == nullptr) return; 117 | printf("%-16s\tibv_qp_init_attr\n", "item"); 118 | printf("%-16s\t----------------\n", "----------------"); 119 | printf("%-16s\t%d\n", "max_send_wr", init_attr->cap.max_send_wr); 120 | printf("%-16s\t%d\n", "max_send_sge", init_attr->cap.max_send_sge); 121 | printf("%-16s\t%d\n", "max_recv_wr", init_attr->cap.max_recv_wr); 122 | printf("%-16s\t%d\n", "max_recv_sge", init_attr->cap.max_recv_sge); 123 | printf("%-16s\t%d\n", "max_inline_data", init_attr->cap.max_inline_data); 124 | printf("-------------------------------------------\n"); 125 | return; 126 | } 127 | printf( 128 | "ibv_qp_attr:\n" 129 | "-------------------\n" 130 | "qp_state: %s\n" 131 | "cur_qp_state: %s\n" 132 | "path_mtu: %d\n" 133 | "rq_psn: %u\n" 134 | "sq_psn: %u\n" 135 | "max_rd_atomic: %u\n" 136 | "max_dest_rd_atomic: %u\n" 137 | "port_num: %u\n" 138 | "retry_cnt: %u\n" 139 | "sq_sig_all: %d\n" 140 | "-------------------\n", 141 | ibv_qp_state_str(attr->qp_state), ibv_qp_state_str(attr->cur_qp_state), attr->path_mtu, 142 | attr->rq_psn, attr->sq_psn, attr->max_rd_atomic, attr->max_dest_rd_atomic, attr->port_num, 143 | attr->retry_cnt, init_attr->sq_sig_all); 144 | printf("%-16s\t%-16s\tibv_qp_init_attr\n", "item", "ibv_qp_attr"); 145 | printf("%-16s\t%-16s\t----------------\n", "----------------", "----------------"); 146 | printf("%-16s\t%16d\t%d\n", "max_send_wr", attr->cap.max_send_wr, init_attr->cap.max_send_wr); 147 | printf("%-16s\t%16d\t%d\n", "max_send_sge", attr->cap.max_send_sge, 148 | init_attr->cap.max_send_sge); 149 | printf("%-16s\t%16d\t%d\n", "max_recv_wr", attr->cap.max_recv_wr, init_attr->cap.max_recv_wr); 150 | printf("%-16s\t%16d\t%d\n", "max_recv_sge", attr->cap.max_recv_sge, 151 | init_attr->cap.max_recv_sge); 152 | printf("%-16s\t%16d\t%d\n", "max_inline_data", attr->cap.max_inline_data, 153 | init_attr->cap.max_inline_data); 154 | printf("-----------------------------------------------------------------\n"); 155 | } 156 | 157 | // 使用第一个设备 158 | bool my_ctx_init(struct my_context_t *ctx, int buf_size, uint8_t port, bool use_event, 159 | uint32_t max_inline_data = cfg_max_inline_data) { 160 | int access_flags = IBV_ACCESS_LOCAL_WRITE | IBV_ACCESS_REMOTE_WRITE | IBV_ACCESS_REMOTE_READ; 161 | 162 | ctx->buf_size = buf_size; 163 | ctx->send_flags = cfg_send_flags; 164 | 165 | global_page_size = sysconf(_SC_PAGESIZE); 166 | ctx->buf = (char *)memalign(global_page_size, buf_size); 167 | if (!ctx->buf) { 168 | fprintf(stderr, "Couldn't allocate work buf.\n"); 169 | return false; 170 | } 171 | 172 | int num_devices; 173 | auto device_list = ibv_get_device_list(&num_devices); 174 | if (device_list == nullptr) { 175 | perror("[ibv_get_device_list]"); 176 | goto clean_buffer; 177 | } 178 | if (num_devices == 0) { 179 | fprintf(stderr, "No Device\n"); 180 | goto clean_device_list; 181 | } 182 | printf(" %-16s\t GUID\n", "device"); 183 | printf(" %-16s\t----------------\n", "--------"); 184 | for (int i = 0; i < num_devices; i++) { 185 | printf(" %-16s\t%016llx\n", ibv_get_device_name(device_list[i]), 186 | (unsigned long long)be64toh(ibv_get_device_guid(device_list[i]))); 187 | } 188 | printf("----------------------------------------\n"); 189 | 190 | ctx->ibv_ctx_ = ibv_open_device(device_list[0]); 191 | if (ctx->ibv_ctx_ == nullptr) { 192 | fprintf(stderr, "Couldn't get context for %s\n", ibv_get_device_name(device_list[0])); 193 | goto clean_device_list; 194 | } 195 | 196 | if (use_event) { 197 | ctx->ibv_comp_channel_ = ibv_create_comp_channel(ctx->ibv_ctx_); 198 | if (ctx->ibv_comp_channel_ == nullptr) { 199 | fprintf(stderr, "Couldn't create completion channel\n"); 200 | goto clean_device; 201 | } 202 | } 203 | 204 | ctx->ibv_pd_ = ibv_alloc_pd(ctx->ibv_ctx_); 205 | if (ctx->ibv_pd_ == nullptr) { 206 | fprintf(stderr, "ibv_alloc_pd fail\n"); 207 | goto clean_comp_channel; 208 | } 209 | 210 | ctx->ibv_mr_ = ibv_reg_mr(ctx->ibv_pd_, ctx->buf, buf_size, access_flags); 211 | if (!ctx->ibv_mr_) { 212 | fprintf(stderr, "Couldn't register MR\n"); 213 | goto clean_pd; 214 | } 215 | 216 | ctx->ibv_cq_ = ibv_create_cq(ctx->ibv_ctx_, cfg_cq_len, (void *)test_cq_context, 217 | ctx->ibv_comp_channel_, 0); 218 | if (!ctx->ibv_cq_) { 219 | fprintf(stderr, "Couldn't create CQ\n"); 220 | goto clean_mr; 221 | } 222 | 223 | { 224 | struct ibv_qp_attr attr; 225 | struct ibv_qp_init_attr init_attr = {.send_cq = ctx->ibv_cq_, 226 | .recv_cq = ctx->ibv_cq_, 227 | .cap = 228 | { 229 | .max_send_wr = cfg_max_send_wr, 230 | .max_recv_wr = cfg_max_recv_wr, 231 | .max_send_sge = cfg_max_send_sge, 232 | .max_recv_sge = cfg_max_recv_sge, 233 | }, // .max_inline_data = max_inline_data 234 | .qp_type = IBV_QPT_RC, 235 | .sq_sig_all = 0}; 236 | // do{ 237 | // ctx->ibv_qp_ = ibv_create_qp(ctx->ibv_pd_, &init_attr); 238 | // printf("max_inline_data: %u\n", init_attr.cap.max_inline_data); 239 | // --init_attr.cap.max_inline_data; 240 | // } while(!ctx->ibv_qp_); 241 | ctx->ibv_qp_ = ibv_create_qp(ctx->ibv_pd_, &init_attr); 242 | if (!ctx->ibv_qp_) { 243 | fprintf(stderr, "Couldn't create QP\n"); 244 | goto clean_cq; 245 | } 246 | print_qp_attr(nullptr, &init_attr); 247 | } 248 | 249 | { 250 | struct ibv_qp_attr attr = { 251 | .qp_state = IBV_QPS_INIT, 252 | .qp_access_flags = 0, 253 | .pkey_index = 0, 254 | .port_num = port, 255 | 256 | }; 257 | 258 | if (ibv_modify_qp(ctx->ibv_qp_, &attr, 259 | IBV_QP_STATE | IBV_QP_PKEY_INDEX | IBV_QP_PORT | IBV_QP_ACCESS_FLAGS)) { 260 | fprintf(stderr, "Failed to modify QP to INIT\n"); 261 | goto clean_qp; 262 | } 263 | 264 | int attr_mask = IBV_QP_STATE | IBV_QP_CUR_STATE | IBV_QP_ACCESS_FLAGS | IBV_QP_PORT | 265 | IBV_QP_PATH_MTU | IBV_QP_RETRY_CNT | IBV_QP_RQ_PSN | 266 | IBV_QP_MAX_QP_RD_ATOMIC | IBV_QP_SQ_PSN | IBV_QP_MAX_DEST_RD_ATOMIC | 267 | IBV_QP_CAP; 268 | errno = ibv_query_qp(ctx->ibv_qp_, &attr, attr_mask, &ctx->ibv_qp_init_attr_); 269 | if (errno == 0) { 270 | print_qp_attr(&attr, &ctx->ibv_qp_init_attr_); 271 | // if (ctx->ibv_qp_init_attr_.cap.max_inline_data >= buf_size) { 272 | // ctx->send_flags |= IBV_SEND_INLINE; 273 | // printf("IBV_SEND_INLINE------\n"); 274 | // } 275 | } else { 276 | perror("ibv_query_qp"); 277 | } 278 | } 279 | 280 | ibv_free_device_list(device_list); 281 | return true; 282 | 283 | clean_qp: 284 | errno = ibv_destroy_qp(ctx->ibv_qp_); 285 | if (errno) { 286 | perror("ibv_destroy_qp"); 287 | } 288 | 289 | clean_cq: 290 | errno = ibv_destroy_cq(ctx->ibv_cq_); 291 | if (errno) { 292 | perror("ibv_destroy_cq"); 293 | } 294 | 295 | clean_mr: 296 | errno = ibv_dereg_mr(ctx->ibv_mr_); 297 | if (errno) { 298 | perror("ibv_dereg_mr"); 299 | } 300 | 301 | clean_pd: 302 | errno = ibv_dealloc_pd(ctx->ibv_pd_); 303 | if (errno) { 304 | perror("ibv_dealloc_pd"); 305 | } 306 | 307 | clean_comp_channel: 308 | if (ctx->ibv_comp_channel_) { 309 | errno = ibv_destroy_comp_channel(ctx->ibv_comp_channel_); 310 | if (errno) { 311 | perror("[ibv_destroy_comp_channel]"); 312 | } 313 | } 314 | 315 | clean_device: 316 | errno = ibv_close_device(ctx->ibv_ctx_); 317 | if (errno) { 318 | perror("[ibv_close_device]"); 319 | } 320 | 321 | clean_device_list: 322 | ibv_free_device_list(device_list); 323 | 324 | clean_buffer: 325 | free(ctx->buf); 326 | return false; 327 | } 328 | 329 | void my_ctx_destory(struct my_context_t *ctx) { 330 | errno = ibv_destroy_qp(ctx->ibv_qp_); 331 | if (errno) { 332 | perror("ibv_destroy_qp"); 333 | } 334 | 335 | errno = ibv_destroy_cq(ctx->ibv_cq_); 336 | if (errno) { 337 | perror("ibv_destroy_cq"); 338 | } 339 | 340 | errno = ibv_dereg_mr(ctx->ibv_mr_); 341 | if (errno) { 342 | perror("ibv_dereg_mr"); 343 | } 344 | 345 | errno = ibv_dealloc_pd(ctx->ibv_pd_); 346 | if (errno) { 347 | perror("ibv_dealloc_pd"); 348 | } 349 | 350 | if (ctx->ibv_comp_channel_) { 351 | errno = ibv_destroy_comp_channel(ctx->ibv_comp_channel_); 352 | if (errno) { 353 | perror("[ibv_destroy_comp_channel]"); 354 | } 355 | } 356 | 357 | errno = ibv_close_device(ctx->ibv_ctx_); 358 | if (errno) { 359 | perror("[ibv_close_device]"); 360 | } 361 | 362 | free(ctx->buf); 363 | } 364 | 365 | enum { 366 | PINGPONG_RECV_WRID = 1, 367 | PINGPONG_SEND_WRID = 2, 368 | }; 369 | 370 | static int pp_post_recv(struct my_context_t *ctx, int n) { 371 | struct ibv_sge list = { 372 | .addr = (uintptr_t)ctx->buf, .length = ctx->buf_size, .lkey = ctx->ibv_mr_->lkey}; 373 | struct ibv_recv_wr wr = { 374 | .wr_id = PINGPONG_RECV_WRID, 375 | .sg_list = &list, 376 | .num_sge = 1, 377 | }; 378 | struct ibv_recv_wr *bad_wr; 379 | int i; 380 | 381 | for (i = 0; i < n; ++i) 382 | if (ibv_post_recv(ctx->ibv_qp_, &wr, &bad_wr)) break; 383 | 384 | return i; 385 | } 386 | 387 | static int pp_post_send(struct my_context_t *ctx) { 388 | struct ibv_sge list = { 389 | .addr = (uintptr_t)ctx->buf, .length = ctx->buf_size, .lkey = ctx->ibv_mr_->lkey}; 390 | struct ibv_send_wr wr = { 391 | .wr_id = PINGPONG_SEND_WRID, 392 | .sg_list = &list, 393 | .num_sge = 1, 394 | .opcode = IBV_WR_SEND, 395 | .send_flags = ctx->send_flags, 396 | }; 397 | struct ibv_send_wr *bad_wr; 398 | 399 | return ibv_post_send(ctx->ibv_qp_, &wr, &bad_wr); 400 | } 401 | 402 | // ibv_devinfo -v -d ibp102s0 -i 1 403 | void print_ibv_port_attr(struct ibv_port_attr &portinfo) { 404 | printf("print_ibv_port_attr:\n"); 405 | printf("%-16s\t----------------\n", "----------------"); 406 | printf("%-16s\t%d\n", "max_mtu", portinfo.max_mtu); 407 | printf("%-16s\t%d\n", "active_mtu", portinfo.active_mtu); 408 | printf("%-16s\t%d\n", "lid", portinfo.lid); 409 | printf("%-16s\t%d\n", "active_width", portinfo.active_width); 410 | printf("%-16s\t%d\n", "active_speed", portinfo.active_speed); 411 | printf("%-16s\t%d\n", "link_layer", portinfo.link_layer); 412 | printf("-------------------------------------------\n"); 413 | return; 414 | } 415 | 416 | struct pingpong_dest { 417 | uint16_t lid; 418 | uint32_t qpn; 419 | uint32_t psn; 420 | union ibv_gid gid; // 暂时不明确用法 421 | }; 422 | 423 | void wire_gid_to_gid(const char *wgid, union ibv_gid *gid) { 424 | char tmp[9]; 425 | __be32 v32; 426 | int i; 427 | uint32_t tmp_gid[4]; 428 | 429 | for (tmp[8] = 0, i = 0; i < 4; ++i) { 430 | memcpy(tmp, wgid + i * 8, 8); 431 | sscanf(tmp, "%x", &v32); 432 | tmp_gid[i] = be32toh(v32); 433 | } 434 | memcpy(gid, tmp_gid, sizeof(*gid)); 435 | } 436 | 437 | void gid_to_wire_gid(const union ibv_gid *gid, char wgid[]) { 438 | uint32_t tmp_gid[4]; 439 | int i; 440 | 441 | memcpy(tmp_gid, gid, sizeof(tmp_gid)); 442 | for (i = 0; i < 4; ++i) sprintf(&wgid[i * 8], "%08x", htobe32(tmp_gid[i])); 443 | } 444 | 445 | static int pp_connect_ctx(struct my_context_t *ctx, uint8_t port, int my_psn, enum ibv_mtu mtu, uint8_t sl, 446 | struct pingpong_dest *dest, int sgid_idx) { 447 | struct ibv_qp_attr attr = { 448 | .qp_state = IBV_QPS_RTR, 449 | .path_mtu = mtu, 450 | .rq_psn = dest->psn, // rq的psn需要跟远端的psn对应 451 | .dest_qp_num = dest->qpn, 452 | .ah_attr = { 453 | .dlid = dest->lid, .sl = sl, .src_path_bits = 0, .is_global = 0, .port_num = port}, 454 | .max_dest_rd_atomic = cfg_max_dest_rd_atomic, 455 | .min_rnr_timer = 12 // Minimum RNR NAK timer (valid only for RC QPs) 456 | // 接受到请求,但RQ中没有对应WR时的重试次数,应该是本地重试吧 457 | }; 458 | 459 | if (dest->gid.global.interface_id) { 460 | attr.ah_attr.is_global = 1; 461 | attr.ah_attr.grh.hop_limit = 1; // 设置为1确保不会离开子网 462 | attr.ah_attr.grh.dgid = dest->gid; 463 | attr.ah_attr.grh.sgid_index = sgid_idx; 464 | attr.ah_attr.grh.flow_label = 0; 465 | } 466 | if (ibv_modify_qp(ctx->ibv_qp_, &attr, 467 | IBV_QP_STATE | IBV_QP_AV | IBV_QP_PATH_MTU | IBV_QP_DEST_QPN | IBV_QP_RQ_PSN | 468 | IBV_QP_MAX_DEST_RD_ATOMIC | IBV_QP_MIN_RNR_TIMER)) { 469 | fprintf(stderr, "Failed to modify QP to RTR\n"); 470 | return 1; 471 | } 472 | 473 | attr.qp_state = IBV_QPS_RTS; 474 | attr.timeout = 14; // 0.0671 sec, 等待回复的超时时间 475 | attr.retry_cnt = 7; // 远端没有应答时的重试次数 476 | attr.rnr_retry = 7; // 接受到 RNR NACK 时的重试次数,7表示无限重试 477 | attr.sq_psn = my_psn; // 自身生成的psn 478 | attr.max_rd_atomic = cfg_max_rd_atomic; 479 | if (ibv_modify_qp(ctx->ibv_qp_, &attr, 480 | IBV_QP_STATE | IBV_QP_TIMEOUT | IBV_QP_RETRY_CNT | IBV_QP_RNR_RETRY | 481 | IBV_QP_SQ_PSN | IBV_QP_MAX_QP_RD_ATOMIC)) { 482 | fprintf(stderr, "Failed to modify QP to RTS\n"); 483 | return 1; 484 | } 485 | 486 | return 0; 487 | } 488 | 489 | static struct pingpong_dest *pp_client_exch_dest(const char *servername, int port, 490 | const struct pingpong_dest *my_dest) { 491 | struct addrinfo *res, *t; 492 | struct addrinfo hints = {.ai_family = AF_UNSPEC, .ai_socktype = SOCK_STREAM}; 493 | char *service; 494 | char msg[sizeof "0000:000000:000000:00000000000000000000000000000000"]; 495 | int n; 496 | int sockfd = -1; 497 | struct pingpong_dest *rem_dest = NULL; 498 | char gid[33]; 499 | 500 | if (asprintf(&service, "%d", port) < 0) return NULL; 501 | 502 | n = getaddrinfo(servername, service, &hints, &res); 503 | 504 | if (n < 0) { 505 | fprintf(stderr, "%s for %s:%d\n", gai_strerror(n), servername, port); 506 | free(service); 507 | return NULL; 508 | } 509 | 510 | for (t = res; t; t = t->ai_next) { 511 | sockfd = socket(t->ai_family, t->ai_socktype, t->ai_protocol); 512 | if (sockfd >= 0) { 513 | if (!connect(sockfd, t->ai_addr, t->ai_addrlen)) // 连接成功了直接退出 514 | break; 515 | close(sockfd); 516 | sockfd = -1; 517 | } 518 | } 519 | 520 | freeaddrinfo(res); 521 | free(service); 522 | 523 | if (sockfd < 0) { 524 | fprintf(stderr, "Couldn't connect to %s:%d\n", servername, port); 525 | return NULL; 526 | } 527 | 528 | gid_to_wire_gid(&my_dest->gid, gid); 529 | sprintf(msg, "%04x:%06x:%06x:%s", my_dest->lid, my_dest->qpn, my_dest->psn, gid); 530 | if (write(sockfd, msg, sizeof msg) != sizeof msg) { 531 | fprintf(stderr, "Couldn't send local address\n"); 532 | goto out; 533 | } 534 | 535 | if (read(sockfd, msg, sizeof msg) != sizeof msg || 536 | write(sockfd, "done", sizeof "done") != sizeof "done") { 537 | perror("client read/write"); 538 | fprintf(stderr, "Couldn't read/write remote address\n"); 539 | goto out; 540 | } 541 | 542 | rem_dest = (struct pingpong_dest *)malloc(sizeof *rem_dest); 543 | if (!rem_dest) goto out; 544 | 545 | sscanf(msg, "%hx:%x:%x:%s", &rem_dest->lid, &rem_dest->qpn, &rem_dest->psn, gid); 546 | wire_gid_to_gid(gid, &rem_dest->gid); 547 | 548 | out: 549 | close(sockfd); 550 | return rem_dest; 551 | } 552 | 553 | static struct pingpong_dest *pp_server_exch_dest(struct my_context_t *ctx, int ib_port, 554 | enum ibv_mtu mtu, int port, int sl, 555 | const struct pingpong_dest *my_dest, 556 | int sgid_idx) { 557 | struct addrinfo *res, *t; 558 | struct addrinfo hints = { 559 | .ai_flags = AI_PASSIVE, .ai_family = AF_UNSPEC, .ai_socktype = SOCK_STREAM}; 560 | char *service; 561 | char msg[sizeof "0000:000000:000000:00000000000000000000000000000000"]; 562 | int n; 563 | int sockfd = -1, connfd; 564 | struct pingpong_dest *rem_dest = NULL; 565 | char gid[33]; 566 | 567 | // 内部分配空间 568 | if (asprintf(&service, "%d", port) < 0) return NULL; 569 | // service 指定端口号 570 | // node 指定为NULL,表示用于本地通信的本地地址 571 | n = getaddrinfo(NULL, service, &hints, &res); 572 | 573 | if (n < 0) { 574 | fprintf(stderr, "%s for port %d\n", gai_strerror(n), port); 575 | free(service); 576 | return NULL; 577 | } 578 | 579 | for (t = res; t; t = t->ai_next) { 580 | sockfd = socket(t->ai_family, t->ai_socktype, t->ai_protocol); 581 | if (sockfd >= 0) { 582 | n = 1; 583 | 584 | setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &n, sizeof n); 585 | 586 | if (!bind(sockfd, t->ai_addr, t->ai_addrlen)) break; 587 | close(sockfd); 588 | sockfd = -1; 589 | } 590 | } 591 | 592 | freeaddrinfo(res); 593 | free(service); 594 | 595 | if (sockfd < 0) { 596 | fprintf(stderr, "Couldn't listen to port %d\n", port); 597 | return NULL; 598 | } 599 | 600 | listen(sockfd, 1); 601 | connfd = accept(sockfd, NULL, NULL); 602 | close(sockfd); 603 | if (connfd < 0) { 604 | fprintf(stderr, "accept() failed\n"); 605 | return NULL; 606 | } 607 | 608 | n = read(connfd, msg, sizeof msg); 609 | if (n != sizeof msg) { 610 | perror("server read"); 611 | fprintf(stderr, "%d/%d: Couldn't read remote address\n", n, (int)sizeof msg); 612 | goto out; 613 | } 614 | 615 | rem_dest = (struct pingpong_dest *)malloc(sizeof *rem_dest); 616 | if (!rem_dest) goto out; 617 | 618 | sscanf(msg, "%hx:%x:%x:%s", &rem_dest->lid, &rem_dest->qpn, &rem_dest->psn, gid); 619 | wire_gid_to_gid(gid, &rem_dest->gid); 620 | 621 | if (pp_connect_ctx(ctx, ib_port, my_dest->psn, mtu, sl, rem_dest, sgid_idx)) { 622 | fprintf(stderr, "Couldn't connect to remote QP\n"); 623 | free(rem_dest); 624 | rem_dest = NULL; 625 | goto out; 626 | } 627 | 628 | gid_to_wire_gid(&my_dest->gid, gid); 629 | // 把自身的地址返回 630 | sprintf(msg, "%04x:%06x:%06x:%s", my_dest->lid, my_dest->qpn, my_dest->psn, gid); 631 | if (write(connfd, msg, sizeof msg) != sizeof msg || 632 | read(connfd, msg, sizeof msg) != sizeof "done") { 633 | fprintf(stderr, "Couldn't send/recv local address\n"); 634 | free(rem_dest); 635 | rem_dest = NULL; 636 | goto out; 637 | } 638 | 639 | out: 640 | close(connfd); 641 | return rem_dest; 642 | } 643 | 644 | static inline int parse_single_wc(struct my_context_t *ctx, int *scnt, int *rcnt, int *routs, 645 | int iters, uint64_t wr_id, enum ibv_wc_status status) { 646 | if (status != IBV_WC_SUCCESS) { 647 | fprintf(stderr, "Failed status %s (%d) for wr_id %d\n", ibv_wc_status_str(status), status, 648 | (int)wr_id); 649 | return 1; 650 | } 651 | 652 | switch ((int)wr_id) { 653 | case PINGPONG_SEND_WRID: 654 | ++(*scnt); 655 | break; 656 | 657 | case PINGPONG_RECV_WRID: 658 | if (--(*routs) <= 1) { 659 | *routs += pp_post_recv(ctx, cfg_max_recv_wr - *routs); 660 | if (*routs < cfg_max_recv_wr) { 661 | fprintf(stderr, "Couldn't post receive (%d)\n", *routs); 662 | return 1; 663 | } 664 | } 665 | 666 | ++(*rcnt); 667 | 668 | break; 669 | 670 | default: 671 | fprintf(stderr, "Completion for unknown wr_id %d\n", (int)wr_id); 672 | return 1; 673 | } 674 | 675 | ctx->pending &= ~(int)wr_id; 676 | if (*scnt < iters && !ctx->pending) { 677 | if (pp_post_send(ctx)) { 678 | fprintf(stderr, "Couldn't post send\n"); 679 | return 1; 680 | } 681 | ctx->pending = PINGPONG_RECV_WRID | PINGPONG_SEND_WRID; 682 | } 683 | 684 | return 0; 685 | } 686 | 687 | int main(int argc, char *argv[]) { 688 | bool ret = false; 689 | int gidx = -1; 690 | char gid[33]; 691 | 692 | my_context_t ctx; 693 | ret = my_ctx_init(&ctx, cfg_buf_size, cfg_ib_device_port, cfg_use_event, cfg_max_inline_data); 694 | assert(ret); 695 | 696 | // 预先 post 足够多的 wr 697 | int routs = pp_post_recv(&ctx, cfg_max_recv_wr); 698 | if (routs < cfg_max_recv_wr) { 699 | fprintf(stderr, "Couldn't post receive (%d)\n", routs); 700 | return 1; 701 | } 702 | 703 | if (cfg_use_event) 704 | if (ibv_req_notify_cq(ctx.ibv_cq_, 0)) { 705 | fprintf(stderr, "Couldn't request CQ notification\n"); 706 | return 1; 707 | } 708 | 709 | if (errno = ibv_query_port(ctx.ibv_ctx_, cfg_ib_device_port, &ctx.portinfo)) { 710 | perror("[ibv_query_port]"); 711 | return 1; 712 | } 713 | print_ibv_port_attr(ctx.portinfo); 714 | if (ctx.portinfo.link_layer != IBV_LINK_LAYER_ETHERNET && !ctx.portinfo.lid) { 715 | fprintf(stderr, "Couldn't get local LID\n"); 716 | return 1; 717 | } 718 | 719 | struct pingpong_dest my_dest; 720 | if (gidx >= 0) { 721 | if (ibv_query_gid(ctx.ibv_ctx_, cfg_ib_device_port, gidx, &my_dest.gid)) { 722 | fprintf(stderr, "can't read sgid of index %d\n", gidx); 723 | return 1; 724 | } 725 | } else 726 | memset(&my_dest.gid, 0, sizeof my_dest.gid); 727 | 728 | srand48(getpid() * time(NULL)); 729 | my_dest.lid = ctx.portinfo.lid; 730 | my_dest.qpn = ctx.ibv_qp_->qp_num; 731 | my_dest.psn = lrand48() & 0xffffff; 732 | inet_ntop(AF_INET6, &my_dest.gid, gid, sizeof gid); 733 | printf(" local address: LID 0x%04x, QPN 0x%06x, PSN 0x%06x, GID %s\n", my_dest.lid, 734 | my_dest.qpn, my_dest.psn, gid); 735 | 736 | char *servername = NULL; 737 | if (argc >= 2) { 738 | servername = strdupa(argv[argc - 1]); 739 | } 740 | 741 | struct pingpong_dest *rem_dest; 742 | if (servername) 743 | rem_dest = pp_client_exch_dest(servername, cfg_listen_port, &my_dest); 744 | else { 745 | rem_dest = pp_server_exch_dest(&ctx, cfg_ib_device_port, cfg_mtu_enum, cfg_listen_port, 746 | cfg_sl, &my_dest, gidx); 747 | } 748 | if (!rem_dest) return 1; 749 | 750 | inet_ntop(AF_INET6, &rem_dest->gid, gid, sizeof gid); 751 | printf(" remote address: LID 0x%04x, QPN 0x%06x, PSN 0x%06x, GID %s\n", rem_dest->lid, 752 | rem_dest->qpn, rem_dest->psn, gid); 753 | 754 | if (servername) 755 | if (pp_connect_ctx(&ctx, cfg_ib_device_port, my_dest.psn, cfg_mtu_enum, cfg_sl, rem_dest, 756 | gidx)) 757 | return 1; 758 | ctx.pending = PINGPONG_RECV_WRID; 759 | 760 | if (servername) { 761 | for (int i = 0; i < cfg_buf_size; i += global_page_size) { 762 | ctx.buf[i] = i / global_page_size % sizeof(char); 763 | } 764 | 765 | if (pp_post_send(&ctx)) { 766 | fprintf(stderr, "Couldn't post send\n"); 767 | return 1; 768 | } 769 | ctx.pending |= PINGPONG_SEND_WRID; 770 | } 771 | 772 | int rcnt = 0, scnt = 0; 773 | int num_cq_events = 0; 774 | struct timeval start, end; 775 | if (gettimeofday(&start, NULL)) { 776 | perror("gettimeofday"); 777 | return 1; 778 | } 779 | 780 | while (rcnt < cfg_iters || scnt < cfg_iters) { 781 | int ret; 782 | 783 | if (cfg_use_event) { 784 | struct ibv_cq *ev_cq; 785 | void *ev_ctx; 786 | 787 | if (ibv_get_cq_event(ctx.ibv_comp_channel_, &ev_cq, &ev_ctx)) { 788 | fprintf(stderr, "Failed to get cq_event\n"); 789 | return 1; 790 | } 791 | 792 | ++num_cq_events; 793 | 794 | if (ev_cq != ctx.ibv_cq_) { 795 | fprintf(stderr, "CQ event for unknown CQ %p\n", ev_cq); 796 | return 1; 797 | } 798 | // assert(ev_ctx == ev_cq->cq_context); 799 | // assert((uintptr_t)ev_ctx == test_cq_context); 800 | 801 | if (ibv_req_notify_cq(ctx.ibv_cq_, 0)) { 802 | fprintf(stderr, "Couldn't request CQ notification\n"); 803 | return 1; 804 | } 805 | } 806 | 807 | int ne, i; 808 | struct ibv_wc wc[2]; // 因为最多的情况会有两个完成元素,但只有一个时也会正常处理 809 | 810 | do { 811 | ne = ibv_poll_cq(ctx.ibv_cq_, 2, wc); 812 | if (ne < 0) { 813 | fprintf(stderr, "poll CQ failed %d\n", ne); 814 | return 1; 815 | } 816 | } while (!cfg_use_event && ne < 1); // 如果使用事件 ne 可能为0 817 | 818 | for (i = 0; i < ne; ++i) { 819 | ret = parse_single_wc(&ctx, &scnt, &rcnt, &routs, cfg_iters, wc[i].wr_id, wc[i].status); 820 | if (ret) { 821 | fprintf(stderr, "parse WC failed %d\n", ne); 822 | return 1; 823 | } 824 | } 825 | } 826 | 827 | if (gettimeofday(&end, NULL)) { 828 | perror("gettimeofday"); 829 | return 1; 830 | } 831 | 832 | { 833 | float usec = (end.tv_sec - start.tv_sec) * 1000000 + 834 | (end.tv_usec - start.tv_usec); 835 | long long bytes = (long long) cfg_buf_size * cfg_iters * 2; 836 | 837 | printf("%lld bytes in %.2f seconds = %.2f Mbit/sec\n", 838 | bytes, usec / 1000000., bytes * 8. / usec); 839 | printf("%d iters in %.2f seconds = %.2f usec/iter\n", 840 | cfg_iters, usec / 1000000., usec / cfg_iters); 841 | 842 | if ((!servername)) { 843 | for (int i = 0; i < cfg_buf_size; i += global_page_size) 844 | if (ctx.buf[i] != i / global_page_size % sizeof(char)) 845 | printf("invalid data in page %d\n", 846 | i / global_page_size); 847 | } 848 | } 849 | 850 | ibv_ack_cq_events(ctx.ibv_cq_, num_cq_events); 851 | 852 | my_ctx_destory(&ctx); 853 | // if (servername) { 854 | // free(servername); 855 | // } 856 | free(rem_dest); 857 | return 0; 858 | } -------------------------------------------------------------------------------- /tests/basic/rc_pingpong_test.sh: -------------------------------------------------------------------------------- 1 | # size_less_1k=(64 256) 2 | 3 | # size=${size_less_1k[0]} 4 | # for((i=0;i<2;i++)); do 5 | # size=${size_less_1k[${i}]} 6 | # echo "size: ${size}" 7 | # ibv_rc_pingpong --ib-port=1 --size=${size} --iters=1000000 & 8 | # ibv_rc_pingpong --ib-port=2 --size=${size} --iters=1000000 localhost 9 | # wait 10 | # done 11 | 12 | # wait 13 | 14 | # for((i=1;i<=1024;i*=4)); do 15 | # size=$((i*1024)) 16 | # echo "size: ${size}" 17 | # ibv_rc_pingpong --ib-port=1 --size=${size} --iters=1000000 & 18 | # ibv_rc_pingpong --ib-port=2 --size=${size} --iters=1000000 localhost 19 | # wait 20 | # done 21 | 22 | # mtu=${1} 23 | # size=$((${2})) 24 | # echo "mtu: ${mtu}, size: ${size}" 25 | # ibv_rc_pingpong --ib-port=1 --iters=1000000 --mtu=${mtu} --size=${size} & 26 | # ibv_rc_pingpong --ib-port=2 --iters=1000000 --mtu=${mtu} --size=${size} localhost 27 | # wait 28 | 29 | # rx_depth=${1} 30 | # size=$((${2})) 31 | # echo "rx_depth: ${rx_depth}, size: ${size}" 32 | # ibv_rc_pingpong --ib-port=1 --iters=1000000 --rx-depth=${rx_depth} --size=${size} & 33 | # ibv_rc_pingpong --ib-port=2 --iters=1000000 --rx-depth=${rx_depth} --size=${size} localhost 34 | # wait 35 | 36 | # 使用事件 37 | 38 | # size=$((${1})) 39 | # echo "--events, size: ${size}" 40 | # ibv_rc_pingpong --ib-port=1 --iters=1000000 -e --size=${size} & 41 | # ibv_rc_pingpong --ib-port=2 --iters=1000000 -e --size=${size} localhost 42 | # wait 43 | 44 | # echo "--no events, size: ${size}" 45 | # ibv_rc_pingpong --ib-port=1 --iters=1000000 --size=${size} & 46 | # ibv_rc_pingpong --ib-port=2 --iters=1000000 --size=${size} localhost 47 | # wait 48 | 49 | # odp 50 | 51 | # size=$((${1})) 52 | # echo "--no odp, size: ${size}" 53 | # ibv_rc_pingpong --ib-port=1 --iters=1000000 --size=${size} & 54 | # ibv_rc_pingpong --ib-port=2 --iters=1000000 --size=${size} localhost 55 | # wait 56 | 57 | # size=$((${1})) 58 | # echo "--odp, size: ${size}" 59 | # ibv_rc_pingpong --ib-port=1 --iters=1000000 --odp --size=${size} & 60 | # ibv_rc_pingpong --ib-port=2 --iters=1000000 --odp --size=${size} localhost 61 | # wait 62 | 63 | # size=$((${1})) 64 | # echo "--iodp, size: ${size}" 65 | # ibv_rc_pingpong --ib-port=1 --iters=1000000 --iodp --size=${size} & 66 | # ibv_rc_pingpong --ib-port=2 --iters=1000000 --iodp --size=${size} localhost 67 | # wait 68 | 69 | 70 | # dm 不支持 71 | 72 | # size=$((${1})) 73 | # echo "--no odp, size: ${size}" 74 | # ibv_rc_pingpong --ib-port=1 --iters=1000000 --dm --size=${size} & 75 | # ibv_rc_pingpong --ib-port=2 --iters=1000000 --dm --size=${size} localhost 76 | # wait 77 | 78 | 79 | # ts 80 | 81 | # size=$((${1})) 82 | # echo "--no ts, size: ${size}" 83 | # ibv_rc_pingpong --ib-port=1 --iters=1000000 --size=${size} & 84 | # ibv_rc_pingpong --ib-port=2 --iters=1000000 --size=${size} localhost 85 | # wait 86 | 87 | # size=$((${1})) 88 | # echo "--ts, size: ${size}" 89 | # ibv_rc_pingpong --ib-port=1 --iters=1000000 --ts --size=${size} & 90 | # ibv_rc_pingpong --ib-port=2 --iters=1000000 --ts --size=${size} localhost 91 | # wait 92 | 93 | 94 | # new_send 实验室的网卡使用不了 95 | 96 | size=$((${1})) 97 | echo "--no new_send, size: ${size}" 98 | ibv_rc_pingpong --ib-port=1 --iters=1000000 --size=${size} & 99 | ibv_rc_pingpong --ib-port=2 --iters=1000000 --size=${size} localhost 100 | wait 101 | 102 | size=$((${1})) 103 | echo "--new_send, size: ${size}" 104 | ibv_rc_pingpong --ib-port=1 --iters=1000000 --new_send --size=${size} & 105 | ibv_rc_pingpong --ib-port=2 --iters=1000000 --new_send --size=${size} localhost 106 | wait 107 | -------------------------------------------------------------------------------- /tests/basic/rdma_device.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | 7 | int main() 8 | { 9 | struct ibv_device **dev_list; 10 | int num_devices, i; 11 | 12 | dev_list = ibv_get_device_list(&num_devices); 13 | if (!dev_list) 14 | { 15 | perror("Failed to get IB devices list"); 16 | return 1; 17 | } 18 | 19 | printf(" %-16s\t node GUID\n", "device"); 20 | printf(" %-16s\t----------------\n", "------"); 21 | 22 | for (i = 0; i < num_devices; ++i) 23 | { 24 | printf(" %-16s\t%016llx\n", 25 | ibv_get_device_name(dev_list[i]), 26 | (unsigned long long)be64toh(ibv_get_device_guid(dev_list[i]))); 27 | } 28 | // for (i = 0; i < num_devices; ++i) 29 | // { 30 | // printf(" %-16s\t%016llx\n", 31 | // ibv_get_device_name(dev_list[i]), 32 | // ibv_get_device_guid(dev_list[i])); 33 | // } 34 | if(num_devices == 0) return 0; 35 | printf("\n"); 36 | 37 | ibv_context* ibv_ctx = ibv_open_device(dev_list[0]); 38 | if(ibv_ctx == nullptr) { 39 | fprintf(stderr, "ibv_open_device fail."); 40 | return -1; 41 | } 42 | 43 | ibv_device_attr device_attr; 44 | int err = ibv_query_device(ibv_ctx, &device_attr); 45 | if(err) { 46 | fprintf(stderr, "ibv_query_device error: %d\n", err); 47 | return -1; 48 | } 49 | printf("phys_port_cnt: %u\n", device_attr.phys_port_cnt); 50 | printf("max_cq: %u\n", device_attr.max_cq); 51 | printf("max_mr: %u\n", device_attr.max_mr); 52 | printf("max_mr_size: %lu\n", device_attr.max_mr_size); 53 | printf("max_qp: %u\n", device_attr.max_qp); 54 | printf("max_qp_wr: %u\n", device_attr.max_qp_wr); 55 | printf("max_sge: %u\n", device_attr.max_sge); 56 | printf("max_sge_rd: %u\n", device_attr.max_sge_rd); 57 | 58 | ibv_free_device_list(dev_list); 59 | 60 | return 0; 61 | } 62 | -------------------------------------------------------------------------------- /tmp.cc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zzh-wisdom/RDMA-Programming/466ff9ad7dfc50d579ef09984501182b746ab2ce/tmp.cc --------------------------------------------------------------------------------