ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

IB协议栈五层结构与报文解析:从LRH到BTH的RDMA实践指南

IB协议栈五层结构与报文解析:从LRH到BTH的RDMA实践指南 第一次在IB环境里抓到一条完整的RDMA报文时我盯着屏幕愣了好一会儿。不是说结构多复杂而是那种“原来数据是这么一层层套进去的”感觉和读协议文档完全是两回事。RDMA技术这几年在存储、数据库、高性能计算里出现得越来越频繁但很多人卡在第一关IB的协议栈到底是怎么组织的报文长什么样QP又是怎么回事这篇笔记不打算照着规范念PPT而是按我自己的学习路径把IB五层协议和报文结构掰开揉碎讲清楚顺便结合组网实操说说这些知识到底怎么用起来。1. 先搞懂IB在RDMA三大路线里的位置1.1 RDMA为什么需要一套全新的协议栈传统的TCP/IP网络里数据从应用发出去要经过内核协议栈数据要拷贝多次CPU要参与中断处理、校验和计算延迟和CPU开销都很高。RDMA的核心思路是绕过内核让网卡直接读写应用内存把数据传输这件事从CPU手里解放出来。但问题是光有硬件还不够你得有一套协议来管理“谁的数据发给谁”“对方内存地址在哪”“数据丢了怎么重传”。IB协议栈天生就是为这种场景设计的它从物理层到传输层都对RDMA做了专门优化。而RoCE和iWARP则是在以太网上做类似的事但底层的思路各有不同。1.2 IB、RoCE、iWARP的协议栈对比搞懂IB之前先说清它和另外两条路线的差别。RoCERDMA over Converged Ethernet直接在以太网上跑IB的报文格式分RoCEv1和RoCEv2两个版本前者在二层跑后者用UDP封装、能跨三层路由iWARP用TCP做传输兼容性好但性能上限不如前两者IB则是端到端的专用方案从网卡到交换机都是专门设计的。三者的协议栈大致对比特性IBRoCEv2iWARP底层网络IB专用网络以太网以太网传输层IB自有传输层IB自有传输层UDP封装TCP硬件要求IB网卡IB交换机RoCE网卡无损以太网iWARP网卡部署成本高中低性能上限最高接近IB受TCP影响现在的NVMe over Fabrics、GPUDirect等场景里IB和RoCE都在大规模使用。但从协议学习角度IB是最干净的模型把IB搞透了RoCE的理解基本就是顺手的事。1.3 学习IB协议栈的正确打开方式我个人体会是学IB协议不能一上来就啃规范原文那玩意几千页直接劝退。更有效的路径是先建立整体分层认知再拆报文结构然后回到命令行和抓包工具里对照验证。这套“自顶向下拆需求、自底向上看封装”的方法比死记字段要扎实得多。把IB协议想象成一个快递系统物理层是公路链路层是市内配送网络层是跨城干线传输层是快递单上的收发件信息上层协议是客户下单的流程。报文就是一层层套上去的包裹。下面我就按这个类比逐层拆。2. IB五层协议逐层拆解2.1 物理层链路速率与底层传输IB物理层定义了链路的基本规格包括信号速率、编码方式、链路宽度等。常见的速率有SDR2.5Gbps、DDR5Gbps、QDR10Gbps、FDR14.0625Gbps、EDR25Gbps、HDR50Gbps等每个速率还分1x、4x、12x等链路宽度。比如一块HDR100网卡本质是2条HDR 1x链路或者说一条HDR 2x链路。物理层还有个容易被忽略但很重要的东西自适应速率协商。两条链路互连时双方会协商到一个共同的速率和宽度。新手排障时经常遇到“两个口都是HDR但协商出来只有EDR”这种时候就要查线缆/光模块是不是不兼容或者两端端口配置不一致。在组网实操里ibstat命令可以快速看到物理层状态包括速率、状态Active/Init和链路层属性。比如输出里出现Rate: 100代表100Gb/s配合State: Active就说明物理层通了。如果状态是Init说明物理层还没协商完成这时候应用层报错根本到不了协议栈上层。2.2 链路层LRH、LID与虚拟通道链路层是IB最核心的层次之一负责子网内的寻址和转发。每个IB端口会分配一个16位的LIDLocal Identifier类似于以太网里的MAC地址但它是被子网管理器SM动态分配的重启后可能变。这也是IB和普通以太网一个显著区别IB网络里必须有SM在跑否则端口连LID都拿不到。链路层还定义了LRHLocal Routing Header包含源LID、目的LID、服务等级SL和虚拟通道VL等信息。SL用于QoS分级VL则是物理链路上的虚拟通道用于实现流控隔离。可以这么理解一条物理链路是一条大马路VL把这条路分成几条车道不同SL的流量走在不同车道上避免互相干扰。LRH里的关键字段包括字段长度作用VL4bit虚拟通道编号SL4bit服务等级源LID16bit发送端口LID目的LID16bit接收端口LID包长度11bit以4字节为单位的数据包长度链路层还负责CRC校验。IB报文尾部有VCRCVariant CRC覆盖整个报文的链路层部分由硬件逐跳校验。数据面报文还有ICRCInvariant CRC覆盖从LRH之后到数据末尾的内容是端到端的完整性校验。VCRC和ICRC配合使用是IB可靠性机制的基础之一。2.3 网络层GRH、GID与跨子网路由IB网络层的设计很有意思子网内只用LID二层转发就够了但跨子网通信时必须依赖GIDGlobal ID。GID是128位的由64位子网前缀加64位端口GUID组成相当于IPv6地址的IB版本。跨子网时报文里会多出一个40字节的GRHGlobal Routing Header包含源GID、目的GID、流量等级Traffic Class、Hop Limit等信息。GRH和LRH的关系可以类比成快递的“省内集散”和“跨省干线”LRH负责子网内的逐跳转发GRH负责从源子网到目的子网的端到端路由。这里有个细节报文是否携带GRH是由QP的通信配置决定的而不是自动加的。如果用UD不可靠数据报做跨子网通信就必须让GRH存在如果只是子网内RC通信完全可以不带GRH。两者报文长度不一样如果对端QP配置不匹配报文解析就会错位这是新手容易踩的坑。实际调试中ibv_devinfo可以看到端口GID列表比如GID[0]: fe80:0000:0000:0000:xxxx:xxxx:xxxx:xxxx是链路本地地址后面如果有192.168.100.1之类的全局子网前缀就说明这个端口有跨子网能力。而ibaddr命令可以查询端口的LID和GID辅助排查寻址问题。2.4 传输层BTH、QP与可靠性机制传输层是理解RDMA的关键也是报文结构里信息密度最大的地方。核心组件是QPQueue Pair它并不是一个“网络协议对象”而是网卡内部一组队列的抽象包括一个发送队列SQ和一个接收队列RQ。应用通过verbs接口把WQEWork Queue Element投递到队列里网卡硬件负责把WQE转成实际的报文发出去然后把完成情况写进CQECompletion Queue Entry。QP类型主要有UD、UC、RC、XRC几种UD不可靠数据报无需建立连接类似UDP支持多播但报文最大只能到MTU且可能丢包。UC不可靠连接建立连接但不可靠不提供重传适用于可容忍少量丢包的低延迟场景。RC可靠连接最常用提供数据完整性、排序和重传机制支持RDMA Read/Write/原子操作。XRC扩展可靠连接优化多对多通信场景减少QP数量常用于MPI等并行计算框架。传输层的可靠性建立在PSNPacket Sequence Number机制上。每个QP维护一个发送PSN和一个接收PSN发送端每发一个报文PSN加1接收端根据PSN判序和去重。如果接收端发现PSN不连续或者发了NAK发送端会触发重传。这就像快递包裹上的运单号丢件、错件都能通过运单号追查。BTHBase Transport Header是所有IB报文的固定头最关键的几个字段字段长度作用OpCode8bit操作码标识报文类型PKey16bit分区键用于隔离目的QP号24bit接收端QP编号A标志位1bit是否需要ACKPSN24bit包序列号OpCode是理解报文行为的钥匙。比如RC_RDMA_WRITE代表RDMA Write操作RC_SEND代表Send操作RC_ACK代表确认报文。不同的OpCode决定了BTH后面跟什么类型的扩展头。2.5 上层协议SMA、CM与verbsIB协议栈的最上层是各种管理代理和通信服务最重要的是子网管理代理SMA和连接管理CM。SMA负责与SM通信处理LID分配、路径查询等CM负责动态建立/销毁QP连接应用层可以用它来协商QP配置、交换内存地址等。还有性能管理PM代理用于收集端口计数器和错误统计。这个在实际排障里很实用比如perftest系列工具ib_write_bw、ib_read_lat等依赖这些机制测量带宽和延迟。再往上就是应用直接面对的verbs接口对应libibverbs库。应用创建PD保护域、MR内存注册、QP、CQ完成队列然后通过ibv_post_send下发WQE、ibv_poll_cq回收CQE。整个流程构成了RDMA编程的基础也决定了报文里的各种字段从哪来。3. IB报文结构深入剖析3.1 报文整体封装框架把五层协议叠到一条实际报文上结构是这样的--------------------- | Start Delimiter | 链路层控制字符 --------------------- | VCRC (2字节) | 链路层CRC实际在尾部 --------------------- | LRH (8字节) | 链路层头 --------------------- | GRH (40字节, 可选) | 网络层头 --------------------- | BTH (12字节) | 传输层基础头 --------------------- | 传输层扩展头 (可选) | RETH/AETH/原子ETH等 --------------------- | Payload (0-4096字节) | 数据负载 --------------------- | ICRC (4字节) | 不变CRC --------------------- | VCRC (2字节) | 链路层CRC实际在尾部 --------------------- | End Delimiter | 链路层控制字符 ---------------------注意VCRC的位置规范里它跟在ICRC后面但在逻辑上它是由硬件在发送时计算并追加的接收时先校验VCRC再校验ICRC。抓包工具里可能会显示VCRC在报文末尾这和以太网的FCS类似别被顺序搞混淆了。整个报文的封装顺序是严格确定的发送端从上层拿到数据依次加上BTH、可选的GRH、LRH然后硬件计算ICRC和VCRC最后加定界符发到链路上。接收端反向操作逐层剥离、校验最后通过DMA把数据写到应用指定的内存。3.2 BTH之后的传输层头RETH、AETH、原子ETHBTH只是传输层的基础头后面具体跟什么完全看OpCode。最常见的三种扩展头RETHRDMA Extended Transport Header用于RDMA Write和RDMA Read请求携带目标内存的关键信息。包含64位虚拟地址、32位RKey和32位DMA长度。RKey是内存注册时分配的钥匙接收端用它来验证发送端是否有权访问这段内存。这就是RDMA能实现“网卡直接读写对端内存”的硬件基础。AETHACK Extended Transport Header用于ACK报文包含8位Syndrome字段和24位MSN消息序号。Syndrome用于指示接收状态如果为0表示成功非0则是具体的错误类型。硬件依据AETH决定是否重传、是否需要调整发送窗口。原子ETH用于原子操作FetchAdd、CompareSwap包含64位虚拟地址、32位RKey、64位操作数等。原子操作在RDMA里比较特殊它让接收端网卡在本地内存上做原子运算再把结果返回全程不打扰CPU。这在分布式锁、计数器等场景里很有用。除此之外UD报文还会带一个DETHDatagram Extended Transport Header包含32位QKey和24位源QP号。QKey是UD通信的访问权限凭证类似于共享内存的key收发两端的QKey必须匹配。3.3 从WQE到报文一次RDMA Write的生命周期纸上谈兵没意思我们走一遍实际流程。假设App A要向远端App B的某段缓冲区写100KB数据使用RC QP和RDMA Write操作App A先注册一块内存ibv_reg_mr拿到local keylkey和remote keyrkey。这份rkey通过带外方式比如TCP连接告诉App B。App A构造一个ibv_send_wr设置opcode为IBV_WR_RDMA_WRITE填上目标虚拟地址、rkey、数据长度然后ibv_post_send投递到SQ。网卡硬件从SQ取出这个WQE把源内存的数据切成多个报文每个报文大小受MTU限制。比如MTU 4096的情况下100KB会被切成约25个报文。每个报文依次封装BTHOpCode标记为RDMA WritePSN递增 RETH目标地址、rkey、长度 payload然后加LRH、ICRC、VCRC发到链路上。接收端网卡收到报文后根据BTH里的目的QP号找到对应QP校验PSN、PKey、RKey校验通过就通过DMA把payload写入RETH指定的虚拟地址。接收端回复ACK报文AETHPSN等于收到的报文PSN发送端收到ACK后知道这个报文已经安全落地才释放对应的WQE资源。全部报文发送完毕后发送端会产生一个完成通知到CQApp A通过ibv_poll_cq拿到CQE知道整次操作完成。这个过程里CPU只参与了第1步和第7步中间的数据传输全程由硬件完成。这也是RDMA能大幅降低延迟和CPU开销的根本原因。理解了这条链路BTH、RETH、PSN、AETH这些字段就不再是孤立的定义了而是这条流水线上每一步的具体产物。3.4 拆一个真实报文看结构如果手头有Mellanox网卡可以用ibdump工具抓取IB链路层报文然后导出成Wireshark可读的pcap格式。抓到的IB报文通常是这样的InfiniBand Local Routing Header (LRH) Link Version: 0 Service Level: 0 Virtual Lane: 0 Link Next Header: IB Global Route Header present Destination LID: 0x0002 Packet Length: 1051 Source LID: 0x0001 Global Routing Header (GRH) IP Version: 6 Traffic Class: 0 Payload Length: 1036 Next Header: 0x1b (IB) Hop Limit: 1 Source GID: fe80::... Destination GID: fe80::... Base Transport Header (BTH) Opcode: RC RDMA WRITE (0x0e) SE: 0, M: 0, Pad: 0, Tver: 0 P_Key: 0xffff Destination QP: 0x000012 A: 1, 保留: 0 Packet Sequence Number (PSN): 123006 RDMA Extended Transport Header (RETH) Virtual Address: 0x7f... RKey: 0x00... DMA Length: 4096 Data (payload) Invariant CRC (ICRC) Variant CRC (VCRC)对照前面讲的层次这条报文各字段一一对应LRH说明源LID是0x0001、目的LID是0x0002GRH说明是跨子网包其实这里是链路本地但也带了GRHBTH的OpCode是RC RDMA WRITE说明这是一个可靠连接下的RDMA写请求RETH里是目标内存地址和长度。一条报文的完整生命周期就落在这一串字段上。实际抓包时如果只用Wireshark的普通版本IB解析器需要额外安装。Mellanox的ibdump工具在驱动配套里就有抓到之后用Wireshark打开可以逐层点击查看。4. 实操把协议理解用到组网和排障里4.1 组网必备SM、LID与opensmIB网络和以太网一个巨大的区别IB网络必须有一个子网管理器SM。SM负责发现网络拓扑、分配LID、计算转发路径。没有SMIB交换机和网卡之间只能处于Init状态数据包根本传不出去。在Mellanox的Linux发行包里最常用的SM实现是OpenSM。启动方式很简单opensm -F /etc/opensm/opensm.conf或者干脆不加载配置直接跑默认opensm启动后看日志会出现类似SUBNET UP的字样说明子网已经起来了。此时用ibstat查端口State应该变成ActivePhysicalState是LinkUp同时能看到分配到的LID比如Base LID: 0x0001。我踩过的坑曾经在测试机上改了OpenSM配置让它只管理一个端口导致另一个端口一直是Init状态应用层报错“QP not ready”之类。后来用sminfo查SM状态才发现问题。所以在组网实操里第一步永远是确认SM正常再看链路状态最后才查应用层。这个顺序不能乱。4.2 验证QP和路径从ibv_devinfo到ibtracert链路通了不代表QP能建起来。要验证协议栈上层是否就绪常用命令有这么几个ibv_devinfo可以看设备的完整能力包括固件版本、端口数、活跃端口、支持的原子操作和MTU。这些参数决定你能不能跑RDMA原子操作、能设置多大的MTU。ibping类似于ICMP ping但走的是IB协议栈能验证两端是否具备基本的QP通信能力。比如在节点A上ibping -S -C mlx5_0 -P 1节点B上ibping -c 1000 -C mlx5_0 -P 1 -L 0x0002这里的-L指定对端LID-C指定IB设备名-P指定端口号。如果网络不通会一直得不到响应。ibping是RDMA组网最基本的联通性测试工具就像以太网里的ping一样重要。ibtracert则用来查看从本地到目标LID的完整路径输出会显示每一跳端口的LID和GUID。如果路径里某段延迟异常高或者出现了意想不到的交换机这个命令非常有用。perftest系列ib_write_bw、ib_read_lat、ib_atomic_bw等则可以用来压测实际的带宽和延迟。比如ib_write_bw -d mlx5_0 -D 10这个会在默认端口上跑10秒的RDMA Write带宽测试。跑出来的数据能直观反映物理链路和协议栈的实际性能。4.3 报文级验证ibdump抓包分析如果你已经看懂了报文结构再去看实际抓包会非常直观。ibdump在Mellanox OFED环境里自带使用方式ibdump -d mlx5_0 -s 0 -o /tmp/ib.pcap然后另开一个窗口跑ib_write_bw之类的流量抓完CtrlC停掉再用Wireshark打开pcap。在Wireshark里过滤infiniband就能看到一条条完整的IB报文。我建议新手做一件事抓一次RDMA Write流量然后在Wireshark里勾选BTH所在的字段对照文档看OpCode、PSN、QP号这些信息。再抓一次RC连接建立过程的CM报文看看REQ/REP消息长什么样。这两个抓包做完对IB协议栈的理解能从“背定义”跃迁到“有画面”。4.4 常见问题与排查技巧实录排障经验是纯看书学不来的但整理成速查表就很实用。我把自己遇到的几类问题和排查方法列一下问题现象可能原因排查方式端口State为InitSM未启动或SM版本不匹配检查opensm日志确认SM在跑端口Active但ibping不通PKey不匹配或QP配置错误检查两端PKey和分区配置速率协商成低速线缆或光模块问题更换线缆用ibstat重新查看QP创建失败PD、MR、CQ未正确配置检查应用日志确认verbs调用顺序跨子网通信失败缺GRH或路由配置错误检查GID前缀确认路由可达MTU设置不一致两端或交换机MTU不匹配用ibv_devinfo对比MTU能力还有一个很多人容易忽略的PKey默认值通常配置为0xffff全子网互通。如果你改了PKey两端和交换机必须一致否则QP通信会直接失败。第一次遇到这种问题时我排查了很久最后才发现是某台机器上OpenSM配置里多了一个PKey条目导致部分端口加入了不同分区。另外IB的ibstat输出的Physical Port State和Port State是两个不同层级的指标——物理层通不通看PhysicalState子网通不通看State。很多人只看一个抓错方向这点也要注意。4.5 python与libibverbs快速写一个连通性验证脚本如果你不想每次都用命令行一堆参数可以用pyverbs库跑一个简单的RC通信验证脚本。pyverbs是Mellanox提供的Python绑定接口风格基本对应libibverbs。在安装了OFED的机器上通常可以直接安装pip install pyverbs一个小例子from pyverbs.addr import IBV_QPT_RC from pyverbs.pd import PD from pyverbs.mr import MR from pyverbs.cq import CQ from pyverbs.qp import QP, QPCap, QPInitAttr from pyverbs.device import Context # 打开设备 ctx Context(namemlx5_0) pd PD(ctx) # 注册内存区域 mr MR(pd, 4096, flagsIBV_ACCESS_LOCAL_WRITE) # 创建完成队列 cq CQ(ctx, 16) # 创建QP cap QPCap(max_send_wr8, max_recv_wr8, max_sge1) qp_init QPInitAttr(capcap, qp_typeIBV_QPT_RC) qp QP(pd, qp_init) # 后续还需要交换QP号、LID、PSN并做rtr/rts状态迁移 # 这里仅演示到创建阶段这个脚本不是为了替代perftest而是帮你理解QP对象的创建流程。实际双端通信时还要处理qp.modify()的状态机切换、交换对端QP号等信息这些逻辑正好能让你把报文结构里BTH的“目的QP号”和实际编程流程对应起来。5. 从协议到实践IB技术还能往哪走学完IB五层协议和报文结构再看其他RDMA技术就轻松多了。RoCEv2的报文结构和IB很像只是LRH换成了以太网头VLANGRH换成IPv4/IPv6头UDP头BTH一开始是完全一致的。所以你现在读一条RoCEv2报文基本能认出大部分字段。还有一个值得关注的趋势是现在很多云厂商在推“弹性RDMA”本质上还是用RoCE或者IB技术做底层但网络架构更复杂了。不管是裸金属场景还是虚拟化场景QP、CQ、MR这些概念始终是核心。你花在IB协议栈上的时间短期看是学了一个网络协议长期看是理解了一整套“把网络能力下沉到硬件”的设计哲学。如果你后续打算深入NVMe over Fabrics建议重点看IB的CM机制和RDMA Read/Write语义如果要搞MPI和HPCXRC和动态连接DCT会是绕不开的话题如果是做存储系统PKey隔离、多路径、SR-IOV这些和协议栈强相关的点值得花时间。最后分享一个小技巧学协议最好的方式不是背书而是“假装自己在设计这套系统”。比如你遇到一个需求——让两台机器之间高效传输数据且不想占用CPU你会怎么设计肯定要先定义地址LID/GID再定义通信实体QP再定义可靠性PSN/ACK再定义权限PKey/RKey最后定义数据传输语义Send/RDMA Write/RDMA Read。你会发现IB的报文结构就是这些问题答案的有序排列。想明白这一层下一次抓包时你看到的就不是乱码而是一行行有逻辑的工程决策。
返回列表