资讯动态

一文读懂PCIe三层架构:事务层、数据链路层与物理层

发布时间:2026/9/27 13:47:32 来源:尧图企业网站定制
做底层开发的人十有八九都跟 PCIe 打过交道。这个协议从 2003 年一路走到现在机械接口从插槽换到 M.2速率从 2.5GT/s 跳到 32GT/s但最核心的协议分层——事务层、数据链路层、物理层这套三层架构却稳稳当当地用了二十年。看懂这三层再看 PCIe 枚举、热插拔、链路训练、故障定位这些问题会顺非常多。这篇笔记适合刚接触驱动或 FPGA 设计的师弟师妹也适合被协议栈资料淹没、想系统补课的老手。我尽量用大白话把这套架构拆开讲清楚不堆术语但该有的细节一个不少。1. 协议分层到底帮我们解决了什么问题1.1 三层架构不是人为规定是被工程现实逼出来的很多人第一次看 PCIe 协议栈都会觉得分层是教科书式的古板设计。实际上PCIe 之所以长成这个结构完全是当年并行 PCI 总线被逼到墙角之后的必然选择。老 PCI 是共享并行总线地址线数据线一大堆频率提到 66MHz、133MHz 就再也上不去了。原因也很简单并行总线一旦频率拉高信号之间的串扰、时钟偏斜、时序收敛全都变成噩梦布线的人恨不得拿尺子量每一毫米。PCIe 换成了串行差分对一对发送、一对接收就是一个 Lane靠高频差分信号把速率顶上去。但串行方案有一个代价协议复杂度成倍上升。并行总线天然带有总线时钟时序关系一目了然串行链路没有全局时钟这个概念你得自己恢复时钟、自己同步数据、自己确认对方是不是还在线。这么复杂的协议如果不分层设计上就是一团乱麻。三层架构的真正意义是把业务逻辑和物理传输彻底解耦。事务层只关心 CPU 要读哪块地址、写什么数据物理层只关心这些数据怎么编码成 0/1、怎么在差分线上跑得又快又稳数据链路层夹在中间专门负责别让数据在半路丢了。这样每一层都能独立演进。你去看这些年 PCIe 的更新节奏就明白了Gen3 换掉 8b/10b 编码物理层大改但事务层几乎没有变化新增的原子操作、地址翻译服务只是在事务层加新的 TLP 类型物理层完全不用动。1.2 每一层都在做什么一句话版本先用一张表把三层职责钉死后文再逐个拆解。这张表建议保存下来以后看协议分析仪的抓包记录时先在脑子里把报文对号入座。层次核心对象主要职责典型报文/行为事务层TLPTransaction Layer Packet承接软件读写请求生成并解析事务报文MRd、MWr、CfgRd、Cpl、Msg数据链路层DLLP TLP 封装保证相邻设备间 TLP 可靠传输做错误检测、重传和流控ACK/NAK、InitFC、UpdateFC物理层串行符号/逻辑块链路训练、编码解码、并串转换、电气信号收发TS1/TS2 训练序列、8b/10b、128b/130b一句话概括就是事务层决定做什么数据链路层保证别弄丢物理层负责跑得快。当你碰到一个 PCIe 问题第一步永远是把它归到某一层。链路直接不 Up十有八九是物理层训练卡住性能忽高忽低先怀疑数据链路层的重传和流控某个寄存器读超时事务层的 TLP 没被正确回应更常见。2. 事务层离软件最近的一层2.1 TLP 是怎么产生的四个地址空间事务层的输入来自两处CPU 发起的 MMIO 访问或者 DMA 描述符以及设备侧 DMA 引擎对内存的读写。无论是哪一边最终都要被封装成一个或几个 TLP。TLP 是 PCIe 世界里真正承载业务的报文它和 DLLP 的区别后面会讲这里先记住只有 TLP 能跨过 Switch 在两个远端设备之间穿梭DLLP 只会出现在相邻两个设备之间。PCIe 定义了四种地址空间事务类型就是按地址空间划分的Memory 空间最常用访问设备 BAR 映射出来的寄存器、DMA 缓冲区都走这里。支持 32 位和 64 位地址上面的 NVMe、网卡、GPU 全部依赖它。I/O 空间兼容老设备用的现在新设计的 EP 基本都不再实现 I/O BAR因为慢而且容易出问题。Configuration 空间枚举阶段专用RC 通过 CfgRd/CfgWr 读取设备的 Vendor ID、Device ID、BAR、能力列表。这地址空间只在刚开始有用枚举完成后几乎不再碰。Message 空间不走地址走事件用来传递中断INTx、错误上报ERR_、电源管理事件PME等带内消息。你在调试时看到的 ERR_NONFATAL 日志底层就是一个 Message TLP。事务层还有一对绕不开的概念Posted 和 Non-Posted。Memory Write 是 Posted 的发出去就不用等回复效率高Memory Read、I/O Write、配置写都是 Non-Posted 的必须等对方回一个 CompletionCpl 或 CplD。这个设计对后面理解流控非常关键因为 Posted 和 Non-Posted 在接收端的缓冲区是分开管理的。2.2 TLP 包格式与事务类型TLP 的结构不复杂但抓包时最容易看花眼的就是头部那十几个字段。核心结构是三段Header12 字节或 16 字节 Data Payload可选 DigestECRC可选。Header 里你必须认识的字段有这几个Fmt/Type告诉接收端这是读、写、配置还是完成报文以及 Header 是 3DW 还是 4DW 格式。TCTraffic Class4 位用于 QoS 区分。TC 会映射到虚拟通道 VC多路 VC 可以独立做流控和调度。Requester ID发起者的 Bus/Device/Function 编号。跟踪一个报文是谁发的看这个字段。Tag事务标签。同一 Requester 发出去的多个未完成读请求靠 Tag 区分。如果调试中看到异常的 Tag 耗尽说明设备可能发了大量读没被完成。Address / Length读写的目标地址和长度。长度以 DW4 字节为单位最大载荷受协商的 MPSMax Payload Size限制。Completer ID / Status出现在 Completion 报文里表示谁完成了这个事务、完成状态是成功还是各种异常。事务层的坑最典型的就是 Tag 管理。RC 同一时刻支持大量 outstanding 读请求EP 一般只支持有限的 Tag 数量。某个驱动写得糙一个 Tag 没等到 Completion 又发起新一轮读Tag 不够用的时候直接表现为读超时而且问题极其难复现。所以调试无头绪的读超时先抓 TLP 看 Tag 分配情况这个方向比对照寄存器手册快得多。3. 数据链路层保证丢了能重来3.1 序列号 LCRC ACK/NAK 的可靠性机制事务层把 TLP 拼好之后交给数据链路层。数据链路层要干三件事加序列号、加 LCRC、做 ACK/NAK 重传管理。这在协议里是可靠性最核心的一环。发送端拿到 TLP 后会在头部前面加一个 2 字节的序列号在尾部追加 4 字节的 LCRCLink CRC。然后整个包才会交给物理层发出去。序列号的作用是让接收端能识别这个包是第几个LCRC 的作用是检测传输过程中有没有 bit 翻转。接收端收到之后先查 LCRC校验通过再看序列号是否连续。如果一切正常接收端会缓存这个 TLP 并向上提交同时回一个 ACK DLLP。如果 LCRC 失败或序列号跳号说明中间丢包了接收端会回 NAK DLLP同时把后面收到的包都丢掉。发送端这边维护着一个重传缓冲区Replay Buffer所有发出但还没收到 ACK 的 TLP 都留在里面。收到 ACK 就从缓冲区移除收到 NAK 或重传定时器超时就把缓冲区里的数据全部重发一遍。注意不是只重发丢的那一个而是从记录点开始的整段数据。这套机制的实际问题在于PCIe 链路物理错误本身不多但一个 bit 错误就能引发整批重传性能瞬间跳水。我曾经调试过一块 FPGA 板卡Gen3 链路跑分只有理论值的六成抓包一看全是 REPLAY_TIMER 超时。后来用示波器测眼图发现一对差分走线在连接器附近阻抗突变反射导致偶发误码。这种问题靠数据链路层根本藏不住但也恰恰说明 ACK/NAK 机制该派用场时就派用场。3.2 流控Flow Control和信用原理除了重传数据链路层还负责流量控制。PCIe 不用停等这种低效方式而是用信用Credit机制类似饭店发排队号接收端告诉发送端我的 Buffer 能装这么多人信用值你放心往里塞每处理完一个我再给你加回 1 个信用。信用值按 TLP 类型分成三本账Posted Header/Data、Non-Posted Header/Data、Completion Header/Data。每种类型独立统计。接收端在初始化时通过 InitFC1/InitFC2 DLLP 把初始信用发给对端之后每处理完一个 TLP就用 UpdateFC DLLP 更新信用。这个机制平时非常稳几乎不需要人管。但它带来的一个典型问题是如果接收端某个 Buffer 被占满又一直不发 UpdateFC常见于驱动没有及时读取完成队列发送端的可用信用耗尽对应的 TLP 就发不出去。表现就是某个设备访问突然挂死但链路状态看起来又完全正常。排查这类问题一定得用协议分析仪看 FC 更新频率纯看驱动日志很难定位。4. 物理层最脏最累的活都在这4.1 逻辑子层 vs 电气子层物理层是所有接触过 PCIe 调试的人最先碰到的一层也是话题最杂的一层。它内部还分了两个子层逻辑子层负责协议层面的物理层功能LTSSM 链路训练状态机、加扰/解扰、8b/10b 或 128b/130b 编码、弹性缓冲、符号对齐。这些是看得见摸得着的数字逻辑在 FPGA 里就是一大片 RTL。电气子层负责真正的模拟信号SerDes串并转换、差分驱动器/接收器、时钟恢复 CDR、阻抗匹配、AC 耦合电容。到了这一层很多事情已经不是数字逻辑能解释的了。热搜里那条host 侧 ck buffer disable 状态建议保持 vinpvinn0V就是典型的 PHY 模拟前端问题——时钟缓冲关闭时差分输入端如果悬空不接固定电平内部比较器可能处于不确定态额外耗电甚至产生毛刺。所以规格书才会明确要求强制拉到共模 0V。这类细节出了 PHY 领域几乎没人提但板卡功耗测试不过的时候往往就是这种毫不起眼的地方。Gen1/Gen2 时代物理层用 8b/10b 编码每 8 bit 数据变成 10 bit 发送多出的 2 bit 用来保证直流平衡和足够多的跳变沿。代价是 20% 带宽被编码开销吃掉。到了 Gen3链路速率直接翻到 8GT/s再用 8b/10b 的话一方面开销浪费已经不可接受另一方面 10 bit 符号对串行收发器来说也到了极限。因此 Gen3 换成 128b/130b 编码开销骤降到 1.5% 左右但代价是接收端必须靠训练阶段的同步头sync header来对齐数据块复杂度全转移到逻辑子层。4.2 LTSSM 和链路训练每个 PCIe 端口内部都跑着一个 LTSSM 状态机链路能不能 Up全看这个状态机走不走得顺。状态序列大致是Detect检测对端是否存在 → Polling发送训练序列 TS1/TS2协商速率 → Configuration确定链路宽度和 Lane 编号映射 → L0正常工作 → L0s/L1低功耗 / Recovery出错了重新训练 → L2休眠。Detect 阶段最简单就是检测接收端有没有被对端驱动电气上能感知到阻抗变化就说明对面有设备。Polling 阶段开始互相发 TS1 训练序列训练序列里带了很多协商信息比如速率是否匹配、端口号、链路号。Configuration 阶段则把 Lane 的编号顺序确定下来如果 PCB 布线出现了 Lane 反转或极性反转也会在这个阶段通过 TS2 协商解决。很多人调试链路不 Up第一步就盯 L0 之前卡在哪个子状态。以我的经验最常卡的是 Polling 或 Configuration。Polling 一直过不去十有八九是参考时钟有问题或差分对有一根虚焊Configuration 过不去要么是链路宽度协商不一致要么是 Lane 顺序反转没被正确识别。状态机卡住的位置直接指向物理故障方向这个排查路线比拿电表乱量高效太多。4.3 通道Lane与链路宽度PCIe 一条 Lane 由一对发送差分信号和一对接收差分信号组成共 4 根线。链路宽度就是并行工作的 Lane 数量x1、x2、x4、x8、x16 由此而来。一条 x16 插槽物理上有 16 组差分对但插一张 x8 卡上去链路会自动降为 x8协商过程就在 Configuration 阶段完成。链路宽度和速率共同决定理论带宽。拿 Gen3 x4 举例每 Lane 8GT/sx4 就是 32GT/s换算成字节带宽还要去掉编码开销128b/130b 的损耗约 1.5%实际单向带宽大约 3.94GB/s双向再加倍。这个公式搞明白了以后别人说NVMe 跑不满你第一时间就能算出瓶颈在哪。SM B3.0 多通道那种双口网卡掉速问题本质上也是多条 PCIe 通道共享同一个上行链路物理层的 Lane 资源是硬上限软件再怎么优化也突破不了。5. 一次读请求从 CPU 到 SSD 的完整旅程三层协同5.1 请求往下走发端的三次封装三层架构光看定义容易空必须跟一次真实事务走一遍。假设 CPU 要读 NVMe 设备 BAR 空间里的一个 4 字节寄存器地址是 0xFC000000。第一步发生在事务层。RC 的 PCIe 控制器把这次 MMIO 读翻译成一个 Memory Read TLP。TLP 头部填好 Requester IDRC 自己的 BDF、Tag这次读的编号、Address0xFC000000、Length1 DW。这个 TLP 会交给数据链路层。第二步是数据链路层封装。它给 TLP 加 2 字节序列号比如序列号 0x1234再对整包算 LCRC 并附在后面。此刻这个带着保险绳的包进入物理层。第三步是物理层发送。原始数据先被加扰避免信号有长串 0 或 1 导致时钟恢复困难再经过 128b/130b 编码拆成一块一块的 130 bit 块通过 SerDes 并串转换变成一对差分线上的高速信号以 8GT/s 的速率送出去。信号从 RC 的发送引脚出发经过 PCB 走线、连接器、Switch 引脚进入 Switch 的接收端。Switch 在这里扮演的是一个路由器角色。它先把信号从电气层恢复出来物理层解码、数据链路层校验 LCRC 和序列号确认无误后向 RC 回一个 ACK。Switch 看看 TLP 的目标地址发现不是自己于是重新把 TLP 做一层新的序列号/LCRC 封装从另一组 Lane 转发到 EP。这里关键点在于TLP 穿过 Switch 时内容完全不变但数据链路层的序列号和 LCRC 已经被换了一套。可靠性是逐段保证的不是端到端。5.2 响应往回走收端的逐层拆解EP 的物理层收到信号先是 CDR 恢复时钟、解扰、130b 解码还原出带序列号的 TLP交给数据链路层。数据链路层校验 LCRC发现没问题回 ACK把 TLP 提交给事务层。EP 的软件/固件看到这是一笔读请求去寄存器地址 0xFC000000 处取值构造一个 Completion with DataCplD返回。CplD 的 Header 里带着 Completer IDEP 的 BDF、Requester IDRC 的 BDF、Tag对应之前的 0x1234、Status成功后面挂着 4 字节数据。它沿着同样的路径往回走再次经过 Switch 逐段转发。RC 的事务层收到完 CplD 后发现 Tag 匹配就知道这次读成功把数据返回给 CPU。整个过程就是事务层负责识路数据链路层负责保镖物理层负责跑步。你以后看协议分析仪的抓包视图凡是看到三层报文交错出现不要慌按这个逻辑去认即可。看 TLP 头部的 Request/Completer ID 画出一条业务路径再看 DLLP 的 ACK/NAK 确认每一段链路都接住扔过来的货物。6. 常见问题速查搜得最多的 PCIe 疑问6.1 上电时序RC 和 EP 谁先启动这是一个我在不同场合被反复问过的问题。硬件时序上RC 必须先就绪EP 没有资格决定自己何时启动。平台设计有一个统一的复位源 PERST#RC 通过它对整个 PCIe 树做复位控制。EP 端通常要求 PERST# 有效时处于复位状态只有 PERST# 释放后才能开始 LTSSM 训练。所以 EP 哪怕供电比 RC 早也必须在 PERST# 有效时老老实实待着。但软件启动是另一回事。RC 的枚举逻辑和驱动加载一定发生在它自身初始化之后EP 只负责在 PERST# 释放后立即尝试训练不负责等操作系统的枚举程序。实际项目里 FPGA 做 EP、CPU 做主控时协调点就是 PERST# 的时序谁先上电不是问题问题永远是 PERST# 释放那一刻RC 那边是否已经能响应 Link Training。6.2 为什么 PCIe 网卡/显卡还要单独供电PCIe 插槽本身是有供电能力的。标准 x16 插槽能提供最大 75Wx1/x4 这类插槽供电能力更低。但现在的显卡动辄 200W、300W75W 完全不够用所以才有 6 pin、8 pin 外接供电。NVMe SSD 走 M.2 口供电来自 3.3V 引脚功耗高的盘在持续写入时也可能触发掉盘就是因为 3.3V 的电流余量不足。这个问题看着简单但对板卡设计很重要PCIe 链路训练和功耗模式切换会带来瞬态电流尖峰如果供电余量不够最直接的表现就是链路突然降速或断开。排查五花八门的 PCIe 异常之前先拿示波器看卡槽供电波形能省半天冤枉时间。6.3 热插拔、PERST 与半高/全高形态PCIe 协议标准层面是有热插拔能力的但能不能用取决于插槽硬件实现和软件配合。插槽需要有 PRESENT# 引脚检测卡是否在位PWR_EN 引脚控制供电时序固件配合做优雅断电驱动层还要监听热插拔事件。不是所有主板的 x16 插槽都实现了这套机制消费级平台按上去没反应很正常。热插拔的完整过程会经历链路进入 L2/L3 或 Disable 状态然后下电。半高和全高则是纯粹的机械规格与协议和电气特性无关。全高卡挡板高度约 120mm半高卡约 68mm矮机箱、工控机只能装半高卡。很多人在工程选型时搜这个记住一点半高卡占用槽位数量和电气协议完全一致只是外壳空间小散热能力受限。如果一块高功耗卡要改成半高别只看 PCB先算散热余量。6.4 怎么在 Linux 下确认 PCIe 速率想快速确认设备实际跑在 Gen3 还是 Gen4一条命令就够了lspci -vvv -s 01:00.0输出里关注两个字段LnkCap 表示设备支持的最大速率和宽度LnkSta 表示当前实际协商出来的值。LnkSta 显示 8GT/s 就是 Gen316GT/s 是 Gen432GT/s 是 Gen5Width 显示 x4、x8、x16 表示当前链路的实际 Lane 数。如果 LnkSta 比 LnkCap 低说明链路没有按最高能力协商出来要么卡没插满要么训练时降级了。调板卡的过程中这条命令几乎每天用。常见情况是板卡设计支持 Gen4但实际稳态掉到 Gen3通常和信号完整性有关连接器虚焊、走线阻抗偏差、参考时钟抖动都可能导致训练协商降速。遇到这种情况先用这条命令确认实际协商值再去查物理层信号质量顺序不要反。6.5 跑测速频繁断流的隐藏元凶如果你手里有 PCIe 接口的无线网卡、SSD跑吞吐测试时频繁中断或者延迟异常先别急着怀疑驱动。重点查一个东西ASPMActive State Power Management。PCIe 的低功耗状态 L0s/L1 默认在很多平台是开启的设备频繁进出低功耗状态时唤醒延迟和链路重训会让网络或存储流量出现明显毛刺。Linux 下可以先禁用 ASPM 测试sudo sh -c echo performance /sys/module/pcie_aspm/parameters/policy或者在内核引导参数里加 pcie_aspmoff 重启。如果你的卡在关闭 ASPM 之后测速就正常了那基本实锤是电源管理切换引起的链路不稳定。很多时候这不是卡质量差而是以太网/无线驱动正好对延迟敏感ASPM 的数学模型吼不住。我个人在实际调试中的体会是三层架构看着像纸面理论但每一次疑难问题最终都会落到到底哪一层断了这个问题上。链路上不了去物理层看 LTSSM性能骤降去数据链路层看 REPLAY 和 FC寄存器莫名超时去事务层看 TLP 和 Tag。把三层边界划清楚调试方向就错不了。下一篇我打算把 LTSSM 的 Configuration 和 Recovery 状态机详细过一遍尤其是各子阶段的报文流转和 Recovery 子状态超时的问题——这是最近被问得最多的方向也是抓包分析里最考验功力的地方。如果你调板卡也卡在类似的位置欢迎到时候一起对一下思路。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑