数据链路层:数据包格式、序列号、CRC与重播
数据链路层,我习惯叫它“PCIe的快递”。事务层打包好内容,交给数据链路层来封装、校验和传输。说白了,这一层负责保证数据包在两端之间“无损到达”。
你想想看,一条PCIe链路上可能有几十个不同设备同时在发数据,怎么保证不乱、不丢、不错?这就全靠数据链路层的几个核心机制了。
数据包格式:DLLP与TLP的封装
数据链路层主要处理两种包:数据链路层数据包(DLLP)和事务层数据包(TLP)。TLP是真正干活的数据,DLLP是管理链路的“信令包”。
在发送端,数据链路层给TLP加上序列号和CRC尾巴,形成数据链路层协议数据单元(DLLP PDU)。接收端则反过来,检查CRC、去掉头尾,再把TLP递交给事务层。
关键点:DLLP用于链路管理(如ACK/NAK、流控更新),不涉及上层业务。抓包时看到DLLP可以直接跳过,除非你在调试链路稳定性。
序列号:顺序与追踪的“身份证”
序列号(Sequence Number)是数据链路层最巧妙的设计之一。每个TLP在发送时会被分配一个递增的序列号,从0开始,最大2047,循环使用。
为什么要序列号?两个原因:
- 顺序保证:接收端按序列号排序,确保乱序到达时能正确重组。
- 确认依据:接收端用序列号回复ACK/NAK,告诉发送端哪些包收到了、哪些丢了。
我在项目中遇到过一个问题:某款FPGA实现的PCIe EP在压力测试下偶尔卡死。抓包发现序列号出现了跳变——中间丢了一个包,但接收端没触发重播。后来定位到是序列号管理逻辑的边界条件没处理好。
调试技巧:抓包时重点关注序列号是否连续。如果出现gap,要么是丢包了,要么是NAK/重播正在进行。连续丢多个包通常是链路质量问题。
CRC校验:数据完整性的“哨兵”
TLP包裹上序列号后,数据链路层会计算一个32位CRC(LCRC)附加在TLP末尾。这个CRC覆盖TLP的全部内容,包括事务层头和数据负载。
接收端收到后重新计算CRC,与收到的LCRC比对。一致则通过,不一致则丢弃并回复NAK。嗯,这里要注意:CRC校验是强制的,但重播是依赖配置的。
| 校验点 | 覆盖范围 | 校验方式 |
|---|---|---|
| LCRC(数据链路层) | 整个TLP | 32位多项式,硬件自动计算 |
| ECRC(事务层) | TLP头+数据(可选) | 端到端校验,需软件支持 |
我曾经踩过一个坑:某次压力测试经常报CRC错误,但链路看起来正常。后来发现是接收端FIFO设计有bug,数据在拷贝过程中被踩坏了。CRC检测出来了,但问题根源不在链路上。抓包能告诉你CRC错,但具体原因还是得看硬件设计。
避坑指南:我曾经在调试中把CRC错误直接归因于链路质量问题,结果查了三天发现是板上的电源纹波导致接收端逻辑不稳定。CRC是症状,不是病因。看到CRC逃逸时先排除本地硬件问题。
重播机制:容错的“后悔药”
数据链路层引入了一个重播缓冲区。发送端发出TLP后,不会立即丢弃副本,而是保留在缓冲区里,直到收到对应的ACK。
重播流程大致如下:
- 发送端发出TLP N,将副本保存到重播缓冲区
- 接收端收到TLP N,CRC校验通过后,回复ACK(携带序列号N)
- 发送端收到ACK后,释放TLP N的缓冲区空间
- 如果收到NAK或者超时未收到ACK,发送端从缓冲区重播所有未确认的TLP
我习惯把重播缓冲区看作一个“滑动窗口”,窗口大小由链路层配置决定。窗口太小容易触发重播风暴,窗口太大占用过多缓冲区资源。一般推荐设为8~16个TLP。
抓包时你会看到什么?如果链路正常,抓到的是连续TLP+ACK的流水。如果出现NAK,你会看到同一个序列号的TLP重复出现,中间夹着一个NAK帧。这时候就是重播了。
关键理解:重播机制不是无损的。如果缓冲区溢出、或者同一个TLP重播太多次(超过重试次数),数据链路层会报告“不可恢复错误”,链路可能直接Reset。所以尽量别让它走到那一步。
实战经验:如果你在抓包中看到大量NAK帧,别急着怀疑硬件。先把重播缓冲区调大试试,很多情况下是软件配置太激进导致的。我曾经遇到过客户因为默认窗口太小,在高带宽场景下疯狂重播,带宽直接掉了一截。
讲到这里,你会不会觉得数据链路层的东西有点多?其实抓住两条主线就好:一条是发送路径(打包→加序列号→算CRC→发出去),一条是确认路径(收包→验CRC→回ACK/NAK→必要时重播)。抓包时沿着这两条路看,基本不会漏掉关键信息。