工作提交成功后,设备可能还在读源缓冲;远端内存更新后,应用可能尚未消费。RDMA 减少特定数据路径的处理和复制,仍需明确资源授权、完成与复用 。
查阅目标 阅读位置
传输、队列与内存注册 第 1~3 节
操作与完成语义 第 4~5 节
应用协议、旁路与观察 第 6~8 节
RDMA 操作需要先建立内存与队列授权
发起端应用
准备本地缓冲与 WR
QP 与 RNIC
校验 key、范围和权限
远端注册内存
地址范围 + rkey 授权
本地 CQ 完成
检查 status,管理本地寿命
远端应用协议
通知、消费、版本与确认
不自动代表业务完成
普通 RDMA WRITE 不自动生成远端应用通知;本地完成也不等于业务提交。
1. InfiniBand、RoCE 与 iWARP
传输 网络基础 需要理解的条件
InfiniBand IB 网络及适配器 链路、子网管理和相应路由/服务
RoCEv1 以太网二层 与基于 IP 路由的 RoCEv2 不同
RoCEv2 UDP/IP,标准目的端口 4791 拥塞、丢包恢复及网卡能力影响表现
iWARP TCP 上的 RDMA 协议栈 TCP 传输与 RDMA 资源/完成语义共同作用
RoCE 部署常配合 ECN、拥塞控制和适当的 PFC 策略,但 PFC 不是“有 RoCEv2 就必须无条件全网开启”的逻辑前提。丢包及暂停策略的实际效果依硬件和网络设计,需要明确验证;PFC 自身还可能引起队头阻塞和拥塞传播。
Linux 的 RXE、SIW 等软件实现有助于学习/测试相应 RDMA 接口,数据面仍消耗 CPU 并经过软件处理。不能把软件 RDMA 测得的行为直接当成硬件 RNIC 的性能或旁路证明。
2. QP、CQ 和 PD 是分别管理的对象
QP 通常包含 SQ 和 RQ:SQ 接收主动发起的 SEND、READ、WRITE 等工作;RQ 提供接收 SEND 等操作需要的缓冲区。CQ 是单独创建并关联 的对象,不是 QP 结构定义里必然私有的第三条队列。
多个 QP 可以共享 CQ;send_cq 与 recv_cq 可相同或不同。使用 SRQ 时,多个 QP 又可共享接收资源。创建时请求的能力与 provider 返回的实际能力要核实,尤其最大 WR、SGE 和 inline 大小。见 ibv_create_qp 。
PD(protection domain)参与关联 QP、MR 等资源的访问控制。把对象放入正确 PD,并满足地址、key 和访问标志等要求,才能合法使用注册内存;不是知道一个指针数值就可以发 DMA。
RC QP 常经历 RESET、INIT、RTR、RTS,需要配置地址、路径、PSN、访问权限和重试等参数。RDMA CM 可帮助连接管理,也可以由程序另行交换并配置参数;并非所有应用都必须使用同一种 CM 流程。UD 等 QP 类型的连接、可靠性和支持操作又不同。
3. Memory Region:注册建立什么承诺
普通 MR 注册给 provider/RNIC 建立内存地址范围及权限,通常涉及页固定、DMA 映射和硬件转换表,并消耗受限制的资源。lkey 用于本地 SGE 访问验证,rkey 用于相应远端访问验证。
ODP 等机制可在支持的环境中按需处理页映射,不需要把所有页面从创建到销毁一直固定。因此“注册必然把所有页永久锁住”与“RDMA 永远不能缺页”都太绝对。
远端收到的地址含义取决于 MR 的 IOVA/zero-based 等设置,不能始终当成未经转换的本地 CPU 指针。访问标志还决定 remote read/write/atomic 是否被允许;需要远端写时,本地写权限等关联要求也要按接口设置。见 ibv_reg_mr 。
Key 不是加密密钥
rkey 是访问授权相关标识,不提供传输加密、身份认证或应用数据的保密封装。它与地址、长度等信息应通过可信的协议交换,限制暴露范围,并在资源撤销时处理远端仍在途的操作。
注册整个进程的大地址范围虽然可能减少管理次数,也会扩大错误远端写入的影响。MR/MW 的范围、权限、key 寿命和连接身份要共同设计,而不是只追求减少注册开销。
4. SEND、WRITE、READ 的职责差异
操作 发起端提供 远端需要准备 通知含义
SEND 本地数据 SGE 已 post 的 receive 缓冲区 接收侧 CQE 可报告消息到达
RDMA WRITE 本地源、远端地址与 rkey 可远端写的内存范围 普通 WRITE 不自动产生远端 receive CQE
RDMA READ 本地目标、远端地址与 rkey 可远端读的内存范围 发起端完成后才按契约读取结果
WRITE_WITH_IMM WRITE 参数和 immediate data 数据 MR 及接收通知资源 写数据到指定地址,并产生相应接收完成
RC SEND 在远端接收资源不足时可能触发 RNR 和重试,最终也可能失败。WRITE_WITH_IMM 的数据写入指定远端地址,不能把它误认为普通 SEND 将数据写进 receive SGE;但接收通知仍消耗相应 RQ/SRQ 工作资源。
RDMA atomic 操作也只提供所支持大小、对齐、操作和作用域的原子性,不使任意多字段数据结构自动具备事务一致性。
5. Post 成功与完成成功
ibv_post_send() 成功说明工作已被提交接受,不说明设备已经结束使用数据。批量 WR 链中途失败时,bad_wr 指向首个失败项,前面的项可能已成功提交;不能一看到错误就释放整批缓冲区。
非 inline 发送缓冲通常要存活到相应完成条件成立;inline 数据被复制进提交结构后,可按 inline 契约更早复用原数据,但仍有队列资源和完成管理。见 ibv_post_send 。
CQ 返回数量与 WC 状态分开检查
poll 结果 含义 下一步
负数 调用失败 处理调用错误
0 此刻没有完成 按轮询/通知策略继续
正数 取到若干 WC 逐条检查 status,不能统一视为成功
完成记录数量与每项工作成功与否是两层判断。
失败完成只保证部分字段有效,例如 wr_id、status、qp_num、vendor_err。CQ 事件通知也不是 WC 本身:事件提示应用来 poll,poll 才取走完成记录。应按通知接口约定重新请求事件并确认事件,避免睡眠与新完成之间的竞争。
选择 unsignaled 发送可降低 CQE 数量,但不意味着可以任意立即复用所有缓冲区。需要依 QP 顺序、操作/fence 语义和后续 signaled 完成建立回收水位,错误路径也必须考虑。CQ 长期不消费可能 overrun,不能把它当作只丢一条统计日志的小问题。见 ibv_poll_cq 。
6. 远端内存更新不是应用事务提交
把远端记录槽看成一个需要完整管理的对象:
阶段 协议必须回答
取得槽位 由谁分配,旧读者是否结束?
写入与发布 怎样判断新数据已完整可见?
读取与验证 服务器怎样确认版本和一致性?
归还与覆盖 谁通知可以再次复用?
RNIC 搬运内容并不会自动替应用补齐这些状态转换。
“先 WRITE payload,再 WRITE ready=1”不能脱离 QP、操作顺序、内存可见性和 relaxed ordering 等条件直接当成通用发布协议。跨 QP 操作也没有自动的全局顺序。需要按 transport/provider 的完成和 fence 契约,并结合 CPU 的同步方式设计通知。
远端更新、消费与持久化分别确认
普通 WRITE 的本地成功完成不等于服务器应用已消费,也不等于服务器已将数据持久化到磁盘。若业务需要持久化确认,应由服务端完成相应存储和元数据步骤后回复,或采用明确支持持久化语义的专门方案。
RDMA READ 同样不自动获得一致快照:远端可能在读取期间修改多字段对象。版本号校验、锁或复制协议仍有必要。
下面依次可见的是请求、传输、CQE、远端应用消费和持久保存,但每个动作只兑现自己的条件。可以在本地完成之后立即复用源,再查看远端内容是否已经持久化。
正在呈现知识画面 重新加载
本地 CQE、远端消费和持久化是三个确认 post 成功不是设备已停止使用源内存;本地成功完成允许按契约复用源,不表示远端应用已消费,也不表示已持久化。
{"id":"rdma-completion-boundaries","title":"本地 CQE、远端消费和持久化是三个确认","summary":"post 成功不是设备已停止使用源内存;本地成功完成允许按契约复用源,不表示远端应用已消费,也不表示已持久化。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"actions\"><button id=\"submit\" data-i18n=\"submit\"></button><button id=\"transfer\" data-i18n=\"transfer\"></button></div><div class=\"actions\"><button id=\"cqe\" data-i18n=\"cqe\"></button><button id=\"reuse\" data-i18n=\"reuse\"></button></div><div class=\"track-row\"><span data-i18n=\"source\"></span><div id=\"source\" class=\"track\"></div></div><div class=\"track-row\"><span data-i18n=\"work\"></span><div id=\"work\" class=\"track\"></div></div><div class=\"track-row\"><span data-i18n=\"remote\"></span><div id=\"remote\" class=\"track\"></div></div><div class=\"actions\"><button id=\"consume\" data-i18n=\"consume\"></button><button id=\"persist\" data-i18n=\"persist\"></button></div><div class=\"track-row\"><span data-i18n=\"application\"></span><div id=\"application\" class=\"track\"></div></div><div class=\"track-row\"><span data-i18n=\"stable\"></span><div id=\"stable\" class=\"track\"></div></div><div class=\"actions\"><button id=\"crash\" data-i18n=\"crash\"></button></div><div class=\"copy\"><p id=\"explanation\" role=\"status\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"note\"></p></div><div class=\"actions\"><button id=\"reset\" data-i18n=\"reset\"></button></div><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:500;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:500 20px var(--viz-font-ui)}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:var(--viz-font-mono);font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px var(--viz-font-ui);background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}let phase=0,source='N',remote='O',app='—',stable='O',dead=false;function draw(){cells('source',[{text:source,cls:phase===1||phase===2?'gold':'on'}]);cells('work',[{text:t(['ready','inflight','completed','done'][phase]),cls:phase===3?'on':'empty'}]);cells('remote',[{text:remote,cls:'on'}]);cells('application',[{text:app,cls:app==='—'?'empty':'on'}]);cells('stable',[{text:stable,cls:'gold'}]);$('#submit').disabled=phase!==0;$('#transfer').disabled=phase!==1;$('#cqe').disabled=phase!==2;$('#reuse').disabled=phase!==3||source==='X';$('#consume').disabled=$('#persist').disabled=phase<2||dead;$('#crash').disabled=phase!==3||dead;set('explanation',t('note'));}$('#submit').onclick=()=>{phase=1;draw();};$('#transfer').onclick=()=>{remote=source;phase=2;draw();};$('#cqe').onclick=()=>{phase=3;draw();};$('#reuse').onclick=()=>{source='X';draw();};$('#consume').onclick=()=>{app=remote;draw();};$('#persist').onclick=()=>{stable=remote;draw();};$('#crash').onclick=()=>{remote=stable;app='—';dead=true;draw();};$('#reset').onclick=()=>{phase=0;source='N';remote=stable='O';app='—';dead=false;draw();};draw();","audio":false,"strings":{"application":"远端应用读到的值","completed":"传输完成;CQE 待取","consume":"远端应用读取","cqe":"消费本地成功 CQE","crash":"远端崩溃并恢复","done":"本地成功 CQE 已取得","heading":"本地 CQE、远端消费和持久化是三个确认","inflight":"在途;源缓冲仍受约束","intro":"提交一条非 inline RDMA WRITE,分别推进传输、本地完成、远端消费和持久化。尝试过早复用源缓冲,并比较远端崩溃前后的值。","note":"只取得本地 CQE 就崩溃,远端仍可能恢复旧内容 O。先让远端稳定保存 N,再崩溃,才有这里的持久恢复依据。","persist":"远端保存并确认","ready":"尚未提交","remote":"远端内存","reset":"重置","reuse":"把源缓冲改为 X","scope":"一个 RC QP、有效 MR/key、非 inline 且 signaled 的 WRITE,无传输错误。传输完成动作包含本模型所需顺序与远端可见性同步;普通 WRITE 不生成远端 receive CQE。远端消费/保存是额外应用协议动作;崩溃仅在本地 CQE 后模拟,省略故障中的 QP 恢复。","scopeLabel":"模型范围与约定","source":"本地源缓冲","stable":"远端稳定内容","submit":"post WRITE","transfer":"完成传输","work":"WQE / 本地完成"}}
7. “内核旁路”的实际范围
硬件用户态 verbs 快路径可由 provider 直接访问映射队列/doorbell,减少每次操作的系统调用。内核仍处理资源创建、映射授权、事件、异常、ODP 和设备生命周期等,应用也仍执行提交和 poll 逻辑。
比较性能时,先固定测量边界
忙轮询常用 CPU 换低延迟;事件驱动降低空转但引入通知成本。buffer 是否预注册、消息大小、NUMA、队列深度、拥塞与 provider 都会影响效果。比较 TCP 与 RDMA 时,要说明是单次操作延迟、吞吐、CPU/字节还是业务端到端完成时间。
部分硬件 RoCE 数据路径不经过常规主机 IP socket 栈,不能假设 nftables 的 socket/转发规则或 tcpdump 自动覆盖所有 RDMA 流量。资源权限、网卡/交换机计数和 RDMA 事件需要共同观测。见用户态 verbs 。
8. 只读查看 RDMA 设备与端口
from pathlib import Path
root = Path ( ' /sys/class/infiniband' )
devices = sorted ( root . glob ( ' *' ) )
if not devices :
print ( ' no RDMA devices visible in this environment' )
for dev in devices :
print ( dev . name )
for port in sorted ( ( dev / ' ports' ) . glob ( ' *' ) ) :
for field in ( ' state' , ' phys_state' , ' link_layer' ) :
try :
print ( ' ' , port . name , field , ( port / field ) . read_text ( ) . strip ( ) )
except OSError :
pass
rdma link show 等工具可补充内核 RDMA 对象信息。设备可见不证明远端 QP、MR、路由或应用协议已经配置成功;无可见设备也只描述当前环境,不能用它验证实际传输。
深入实现可从 rdma-core 的 libibverbs、librdmacm 与 provider,以及内核 drivers/infiniband/ 阅读。优先跟踪资源创建、一次完成与错误清理,而不是只看 post 函数。