尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

PCIe带宽争抢排查指南:从拓扑到实测,定位共享链路瓶颈

PCIe带宽争抢排查指南:从拓扑到实测,定位共享链路瓶颈 在实际服务器、工作站和嵌入式平台的日常运维里“谁在跟我抢 PCIe”不是一个夸张的比喻而是一个很具体的排查问题。系统里同时挂着 GPU 加速卡、NVMe 固态盘、高速网卡、FPGA 加速卡甚至 USB4 扩展坞任何一块外设性能不达标很多人的第一反应是“驱动坏了”或者“设备坏了”但反复重装驱动、换固件之后问题还在。真正的原因往往藏在链路里设备插在哪个 Root Complex 下和谁共用同一根上游链路协商出来的 lane 数和速率是多少并发负载下有没有撞到共享带宽的天花板。MiniMax-H3 这类多 PCIe 设备平台的排查走的也是同一套思路。这篇文章会把 PCIe 资源争抢的拓扑原理、查看方法、实测手段和常见报错排查路径完整梳理一遍。1. 先想清楚PCIe 系统里到底是谁在共享带宽1.1 PCIe 不是一条总线而是一张由 RC、Switch、Endpoint 组成的交换网络PCIePeripheral Component Interconnect Express和传统 PCI 总线最大的区别在于PCI 是共享并行总线所有设备抢同一条总线的带宽PCIe 是点对点串行链路每个设备理论上拥有独立链路。但“点对点”不意味着“不共享”因为所有设备的数据最终都要汇入 Root Complex根复合体简称 RCRC 再通过内存控制器访问系统内存。换句话说RC 是所有 PCIe 流量的必经之地。下面用一棵典型的 x86 平台设备树来说明挂载关系-[0000:00]--00.0 Intel Corporation Host Bridge -01.0-[01]----00.0 NVIDIA Corporation Graphics Adapter -1c.0-[02]----00.0 Samsung Electronics NVMe SSD -1c.4-[03]---00.0 Intel Corporation Ethernet Controller | \-00.1 Intel Corporation Ethernet Controller -1d.0-[04]----00.0 FPGA Accelerator Card -1f.0 Intel Corporation ISA Bridge在这棵树里RC 是根节点负责枚举总线和分配资源PCIe Switch 用于扩展下游端口内部通过 BDFBus:Device.Function信息做转发Endpoint 是实际设备比如 GPU、NVMe、网卡和 FPGABridge 则负责承接不同类型的下游总线。判断“谁在抢 PCIe”时必须记住一个原则一个设备能拿到多少带宽取决于从它到 RC 之间所有环节中“最窄的那一段”。显卡直连 RC 的 x16 端口独享带宽而挂在同一个 Switch 下游的网卡和 NVMe则要共享 Switch 上游端口到 RC 之间的那根链路。1.2 链路协商Lane 数和速率按最弱环节定一条 PCIe 链路可以由 1、2、4、8、16 条 lane 组成每条 lane 是一对独立的差分收发线。设备上电或复位后链路两端会通过 LTSSM 状态机进行链路训练协商出当前可用的 lane 数和速率。如果某条 lane 的物理信号有问题链路会自动降级到更小的宽度。这里最容易踩的坑是“配置是对的但实际协商结果降级了”。例如主板是 x16 插槽显卡也是 x16但触点氧化、机箱受力、转接线质量差导致训练出来的实际宽度变成 x8 甚至 x4带宽直接减半甚至减到四分之一。此时系统并不会报错只会悄悄降级。查看设备协商结果Linux 下最常用的是lspci -vvsudo lspci -s 03:00.0 -vv | grep -E LnkCap|LnkCtl|LnkSta输出大致如下LnkCap: Port #0, Speed 8GT/s, Width x8, ASPM L0s L1 LnkCtl: ASPM Disabled; RCB 64 bytes, Disabled- CommClk LnkSta: Speed 8GT/s, Width x8, TrErr- Train- SlotClkLnkCap 是这条链路的设计能力LnkSta 是当前实际协商状态。如果 LnkCap 是 Width x16LnkSta 变成 Width x8说明链路发生了降级这是带宽损耗最常见的原因之一。1.3 共享带宽要分三个层级看PCIe 的“共享”不是笼统的可以分成三个层级层级共享点典型场景判断方式第一层同一个 Endpoint 内部的多 Function双口网卡、多功能 NVMe 控制器看 BDF 中 function 编号第二层同一个 Switch 下游的所有设备FPGA 和网卡挂在同一 Switch 后看lspci -tv的层级关系第三层同一 RC 下跨端口的公共瓶颈CPU 到 PCH 的 DMI、内存控制器总吞吐看平台文档和性能计数器第三层最容易被忽略。比如 CPU 提供若干条直连 PCIe 通道给显卡和 NVMePCH 也提供一部分 PCIe 通道但 PCH 与 CPU 之间通常只有一条 DMI 总线。所有挂在 PCH 下的设备无论各自链路多宽最终都要挤过 DMI 这一条通道这和“直接挂在 CPU 根端口下”的带宽是完全不同的。注意看到设备在lspci里出现只能说明枚举成功不能说明它跑在满速。确认带宽之前一定先看 LnkSta再看它属于哪个上游端口。2. 用 lspci 和 sysfs 把设备树“画”出来2.1 BDF 编号先读懂设备地址每个 PCIe 设备都有一个唯一地址格式是domain:bus:device.function实际显示通常省略 domain例如0000:03:00.0。其中 bus 是总线号device 是设备号function 是功能号。枚举顺序决定了 BDF 的分配RC 先扫描总线 0每发现一个 PCIe Bridge 或 Switch 端口就会为其分配新的次级总线号然后继续向下扫描。因此 BDF 本身就隐含了拓扑关系挂在同一个 Bridge 后面的设备总线号会比较接近或者位于同一个总线号之下。反过来总线号跨度很大、直接挂在 RC 下的设备通常独享一个根端口。查看当前所有设备最直接的是执行lspci lspci -nnk第一行输出能看到设备类型和厂商-nnk能额外显示内核驱动信息和 PCI ID。例如一块 FPGA 卡可能显示成04:00.0 Memory controller: Xilinx Corporation Device 7024 (rev 01) 04:00.0 Kernel driver in use: my_fpga_driver2.2 一张拓扑图看穿挂载关系lspci -tv会把整棵设备树打印出来这是判断“谁和谁共享链路”最常用的工具lspci -tv输出示例精简过-[0000:00]--00.0 Intel Corporation Host Bridge -01.0-[01]----00.0 NVIDIA Corporation Graphics Adapter -1c.0-[02]----00.0 Samsung Electronics NVMe SSD -1c.4-[03]---00.0 Intel Corporation Ethernet Controller | \-00.1 Intel Corporation Ethernet Controller -1d.0-[04]----00.0 Xilinx Corporation FPGA Accelerator Card从这棵树可以直观得到结论GPU 挂在00:01.0后面独占一条路径NVMe 挂在00:1c.0后面网卡和它的第二个 function 挂在00:1c.4后面两者共用从00:1c.4到 RC 的链路FPGA 卡挂在00:1d.0后面。如果两块设备在输出里处于同一个 Bridge 子节点下面它们就很可能共享上游带宽。这里要注意PCIe 网卡的物理长度不代表 lane 数x16 的插槽完全可以插一块 x1 的小网卡物理上兼容但链路宽度按短的算。2.3 用 sysfs 核对设备真实属性除了lspciLinux 的 sysfs 也能直接查看设备属性适合写脚本采集cat /sys/bus/pci/devices/0000:04:00.0/vendor cat /sys/bus/pci/devices/0000:04:00.0/device cat /sys/bus/pci/devices/0000:04:00.0/classvendor 是厂商 IDdevice 是设备 IDclass 是设备类别。比如常见的以太网控制器 class 是0x020000非易失性存储器控制器是0x010802。实际项目中如果驱动无法自动绑定通常要先核对这三个值再决定是用new_id注册新 ID还是手动绑定驱动。需要强调一个技术点lspci看到的是枚举完成后的“现状”不是硬件设计时的“意图”。线上系统里设备没有出现在lspci中不一定代表硬件没接可能是枚举失败、链路没训练成功也可能是 BIOS 关闭了某个 Root Port。后面第 5 节会专门展开枚举失败的排查。3. 真实场景哪些设备会互相抢带宽3.1 多块 NVMe 同时读写总和被上游端口卡住最常见的“被抢”场景是服务器里插了多块 NVMe 固态盘。假设四块 NVMe 都挂在一个 PCIe Switch 下游每块盘自己的链路是 x4但 Switch 到 RC 的上游端口只有 x8。单独测试任何一块盘时速度都能跑满四块盘同时跑随机读或顺序读总吞吐就会被上游 x8 的带宽限制住。验证方式是用 fio 同时对两块或四块盘发起压测sudo fio \ --nameseqread_a \ --filename/dev/nvme0n1 \ --rwread \ --bs1m \ --size8g \ --iodepth64 \ --numjobs4 \ --direct1 \ --ioenginelibaio \ --group_reporting观察点不是单块盘能跑多快而是“几块盘同时跑时总吞吐等于多少”。当总吞吐明显低于各盘理论带宽之和并且稳定在某个上游链路上限附近时就说明共享带宽撞墙了。3.2 GPU 和高速网卡争用同一条上游链路在大模型训练、视频渲染和边缘推理平台上GPU 需要持续从存储读取样本网卡同时承担梯度同步或视频流接收任务。如果 GPU 和网卡插在同一个 Switch 下游或者网卡挂在 PCH 下面而 GPU 也依赖 DMI 访问某些数据并发负载下就会出现明显的延迟抖动。如果是 NVIDIA GPU可以先用nvidia-smi topo -m查看 GPU 之间的拓扑关系再用lspci -tv确认 GPU、网卡、存储设备之间的挂载关系。真正要判断的是两个高带宽设备的数据路径在到达内存控制器之前有没有经过同一个 Switch 上游端口。这类争抢的典型表现是单独跑 GPU 训练或单独跑网卡收包都正常两个任务同时运行后网卡延迟飙升或者训练吞吐下降。解决方向优先是调整物理插槽位置把 GPU 和网卡分别放到不同 RC 根端口下如果物理布局无法调整就只能通过流控、限速和分时调度来降低互相影响。3.3 USB4 / 雷电扩展坞也是 PCIe 流量很多开发者忽略 USB4 和雷电设备它们本质上会把 PCIe 流量隧道化后封装在 USB4 链路上。也就是说USB4 并没有“等于多少条 PCIe lane”的说法它取决于 USB4 链路总带宽、隧道策略和平台实现。常见实现中PCIe 隧道能拿到的有效带宽大约相当于 PCIe 3.0 x4 的水平实际还要扣除协议开销。因此外接 NVMe 硬盘的速度低于内置 NVMe 盘是很正常的现象因为外接盘不仅要经过 USB4 控制器的 PCIe 隧道还要和同一条 USB4 链路上的显示、USB 数据共用带宽。排查时可以在lspci -tv里找到 USB4/雷电控制器再确认外接 NVMe 是否挂在这个控制器下游。没有看到这条链路关系就盲目怀疑硬盘或转接线往往会走弯路。3.4 FPGA 加速卡与下游设备互相挤占FPGA 加速卡做 DMA 搬运大块数据时会持续占用它到 RC 之间的链路。如果同一 Switch 下游还挂着高速网卡或 NVMeFPGA 的 DMA 流量与这些设备的流量会互相挤占。这个场景在 Zynq 和其他嵌入式平台上尤其常见PL 侧通过 AXI PCIe IP 接成一个 EndpointPS 侧同时还要跑网络和存储四条数据流最终都汇聚到同一条 PCIe 上游链路上。这类问题不能靠“提高 FPGA 时钟”解决因为瓶颈通常不在 FPGA 内部而在链路聚合点。正确做法是在设计阶段就规划好FPGA 的高带宽 DMA 通道尽量独占一个 RC 端口或者使用支持多上游端口的 PCIe Switch 做流量分流。4. 测量把“被抢占”变成可量化数据4.1 单项压测先拿到单设备基准判断是否被抢带宽之前必须先知道每台设备在“独占链路”时的基准值。基准值分成两层硬件理论值和实测值。硬件理论值由 LnkSta 决定比如 PCIe 4.0 x4单向理论约 7.88 GB/s实测值则取决于设备固件、驱动和实际负载。存储设备用 fio 测网络设备用 iperf3 测iperf3 -c 192.168.1.10 -p 5201 -t 30 -P 8单项测试时如果实测值远低于 LnkSta 对应的理论值优先排查设备本身如果实测值接近理论值说明单设备没问题接下来才做并发压测。4.2 并发压测把争抢“打”出来PCIe 争抢只在并发负载下才会暴露。推荐的做法是同时启动两个压测任务再观察总吞吐# 终端 1NVMe 连续读 sudo fio --nameread --filename/dev/nvme0n1 --rwread --bs1m --size8g --iodepth64 --direct1 --ioenginelibaio # 终端 2网卡收包 iperf3 -c 192.168.1.10 -p 5201 -t 60 -P 16 # 终端 3观察设备吞吐 iostat -x 1记录两件事一是两块设备各自的吞吐下降了多少二是两者的合计吞吐是否被稳定压在一个值附近。如果合计值明显小于“单独跑 A 单独跑 B”之和并且稳定在某个链路宽度对应的带宽上限基本可以确认共享瓶颈存在。这里的关键判断是不要在测试时只看某一个设备自己的工具输出因为设备驱动通常会认为自己已经跑满必须把多路流量的和放在一起看才能看到链路聚合点的物理上限。4.3 用平台性能计数器拿到直接证据如果用的是 Intel 平台可以尝试 Intel PCM 工具集中的pcm-pciesudo pcm-pcie它能统计各 PCIe 端口的实际吞吐和利用率直接告诉你某根链路到底跑到了百分之多少。其他平台则需要查阅对应 CPU、芯片组或 SoC 的性能计数器文档有些平台没有开放 PCIe 带宽计数器那就只能用“多路压测 汇总吞吐”的间接方法判断。注意性能计数器只能说明链路利用率不能说明“谁占了多少”。定位到具体设备仍然需要回到设备树和并发测试的结果。5. 枚举与“设备不识别”排查从原理到日志5.1 枚举过程到底做了什么PCIe 枚举是 RC 在上电和复位后做的一系列动作先完成链路训练然后为每个设备分配总线号读取 Vendor ID、Device ID、Class Code 等配置信息再通过向 BAR 寄存器写全 1 来测量地址空间大小最后分配内存和 IO 窗口并打开设备的内存/IO 访问能力。这一套流程决定了几个常见现象如果链路训练失败RC 读到的是0xFFFFFFFF设备就会完全消失。如果配置空间读取有问题设备可能显示出来但 BAR 窗口分配失败。如果 Class Code 缺失或不规范驱动可能无法自动绑定。所以在 Zynq、FPGA 或者自制 PCIe 板卡上调试时不要一上来就怀疑驱动而要先确认枚举阶段是否把设备“看到”了。5.2 Zynq/FPGA 板卡插上不识别按这个顺序查以 Zynq 平台上调试 FPGA PCIe 设备为例常见排查顺序是确认 bitstream 或固件已经加载。很多 FPGA 板卡要先用 JTAG 或 QSPI 加载配置PCIe 逻辑才会出现。确认参考时钟。PCIe 需要 100 MHz 参考时钟方向通常由主板或 RC 提供FPGA 端要做参考时钟引脚绑定和约束检查。确认复位时序。PERST# 释放必须在电源稳定后RC 侧才能开始链路训练。在内核里看设备是否存在lspci -d 10ee: dmesg | grep -i pci其中10ee是 Xilinx 的厂商 ID。如果你的 FPGA 设计里填了自定义 VID/DID就要换成实际值。用lspci -vv看 BAR 是否分配成功Class Code 是否正常。常见现象与可能原因可以整理成下面这个速查表现象可能原因优先检查lspci完全看不到设备链路训练失败、时钟或复位异常REFCLK、PERST#、bitstreamVID/DID 读到0xFFFF链路未建立或配置空间读取失败链路训练状态、参考时钟设备出现但 BAR 全部为 0RC 没有分配资源窗口内核报告、BIOS 窗口大小设备出现但驱动无法绑定Class Code 不标准、驱动 ID 不匹配vendor/device/class 三个值内存访问触发总线错误BAR 地址翻译错误、inbound/outbound 配反AXI PCIe 地址映射如果做的是 PCIe Endpoint 功能验证可以编译内核源码树里的tools/pci/pcitest工具配合 Linux PCIe Endpoint Framework 对 BAR 读写、中断和 DMA 做基础测试。它比直接开发驱动更早地暴露链路和地址翻译问题。5.3 Inbound 和 Outbound 的地址翻译要分清方向在 FPGA 和 Zynq 的 PCIe 设计中inbound 和 outbound 是绕不开的概念。从 Endpoint 的角度看inbound 是主机侧发往 FPGA 的访问也就是主机访问 Endpoint 的 BAR把请求翻译成 FPGA 内部的 AXI 地址。outbound 是 FPGA 主动发往主机的访问典型就是 DMA把 AXI 地址翻译成 PCIe 地址去读写主机内存。不同厂商手册使用的命名角度可能相反读文档时先确认它是站在 RC 还是 EP 角度描述。实际调试中inbound 和 outbound 方向配反或地址窗口重叠最常见的表现就是“驱动加载成功但读返回值全 F、写没反应、或者 DMA 完成中断来了数据却是错的。”在 Xilinx AXI PCIe / 7 Series PCIe IP 的例程里BAR 窗口大小、AXI 地址偏移、DMA 基地址都需要与主机侧分配的资源对齐。改任何一边的地址另一边必须同步核对否则就会变成“枚举没问题一访问就总线错误”。6. ACS、总线错误和 WHEA 事件怎么查6.1 ACS 与 IOMMU 分组为什么“打不开”很麻烦ACSAccess Control Services是 PCIe 提供的一组访问控制能力由 Switch 端口和 Root Port 实现核心用途是阻止下游设备随意发起 Peer-to-Peer 访问从而为 IOMMU 分组提供硬件隔离基础。在 VFIO 设备直通、SR-IOV 热迁移等场景里ACS 是否使能直接决定一个 IOMMU group 是否“干净”。当网络上有人说“pcie acs 无法打开”时通常遇到的是两种情况硬件本身不支持 ACS比如廉价 PCIe Switch 芯片没有实现 ACS 能力。硬件支持 ACS但固件或驱动没有使能或者平台策略把它关掉了。Linux 下可以这样看 ACS 能力sudo lspci -vvv | grep -i -A4 Access Control Services如果输出里有ACSCap但没有ACSctrl说明硬件报告了能力但控制寄存器没有被正确配置。更常见的是设备完全没有任何 ACS 相关字段说明这颗 Switch 或 Root Port 根本没实现 ACS。还有一个经常被提到的内核启动参数是pcie_acs_override它可以把不规范的硬件强制视为支持 ACS常见用法面向特定设备 ID 出现时一般写成pcie_acs_overridedownstream,multifunction但这个参数本质上是在“绕过硬件能力上报”会让 IOMMU 分组认为隔离成立而实际上硬件并没有提供对应保护。只适合在测试台验证直通方案生产环境使用前必须评估 DMA 隔离风险并且要走主机侧的安全评审。默认情况下不建议在生产机器上开启。6.2 Linux 下的 AER 总线错误看日志、找设备、查链路PCIe 总线错误在 Linux 里通常通过 AERAdvanced Error Reporting上报。先看内核日志dmesg | grep -i aer\|pcieport常见的报错格式类似pcieport 0000:00:1c.4: AER: Corrected error received: 0000:03:00.0 pcieport 0000:00:1c.4: AER: Multiple Corrected error received: 0000:03:00.0看到这类日志时排查顺序是确认错误源设备 ID也就是日志最后那串0000:03:00.0。用lspci -s 03:00.0 -vv检查该设备的 AERCap 和当前错误状态。检查 ASPM 电源管理状态。链路在 L0s/L1 之间频繁切换时一些劣质转接线或信号质量差的板卡会产生大量 Corrected Error。如果错误持续增加优先尝试在 BIOS 里关闭 ASPM或者使用内核参数pcie_aspmoff验证。AER 日志按严重度分为已纠正和未纠正两类。已纠正错误不丢数据但反复出现说明链路信号质量有问题未纠正错误通常伴随数据传输失败需要更紧急地处理。不要直接加pcinoaer把 AER 关掉那只是掩盖现象不是解决问题。6.3 Windows 下 WHEA-Logger 事件怎么读Windows 平台对应的事件来源是 WHEA-Logger在“事件查看器 - Windows 日志 - System”里能看到。常见的事件 ID 是 17已纠正的 PCIe 错误和 18未纠正的 PCIe 错误描述通常类似A corrected PCIe error has occurred.处理思路和 Linux AER 基本一致先记录事件来源中的设备信息再确认是不是特定插槽、特定转接线引起的链路错误最后检查 BIOS 中的电源管理和链路速率设置。如果事件集中在某一块 FPGA 卡或 RAID 卡上还要确认卡体散热和供电是否正常高负载下温度过高会让信号质量变差从而产生大量已纠正错误。7. 最佳实践与排查清单7.1 在设计和部署阶段就避免争抢任何线上优化都不如一开始的拓扑规划。实际项目里可以遵循这几条规则高带宽设备优先直连 CPU 提供的根端口。GPU、高性能 NVMe、FPGA DMA 这类长期跑大流量的设备尽量不要全部堆到 PCH 或同一个 Switch 下。需要 P2P 通信的设备尽量放到同一个 RC 下同时确认路径上有没有经过 Switch。GPU 与网卡做 GPUDirect RDMA 时路径上的每一级都会影响延迟。高带宽设备之间保留“可以分流”的空间。同一个 Switch 下游最多只放整机带宽上限能覆盖的设备组合。学习环境可以为了验证功能临时使用pcie_acs_override、关闭 AER、固定链路速率等实验性手段但生产环境任何绕过机制都必须先评估安全影响并保留变更记录和回滚方案。把 LnkSta、AER 计数、PCIe 端口利用率接入监控。PCIe 链路降级和错误率上升往往是硬件故障的前置信号。7.2 “谁抢了我的 PCIe”排查清单把前面几节的步骤收敛成一份可以直接照着做的清单执行lspci -tv确认目标设备和它邻居设备的挂载关系。执行lspci -s BDF -vv确认 LnkSta 里的实际 Speed 和 Width 是否与设计一致。核对目标设备是否与其他高带宽设备挂在同一个 Bridge 或 Switch 下游。单独对目标设备做压测记录单设备峰值。对共享链路的所有高带宽设备同时压测记录合计吞吐和各自下降幅度。如果合计吞吐被压在上游链路上限附近确认共享瓶颈存在。检查dmesg和 Windows 事件日志中是否有 AER / WHEA 报错逐条定位错误源设备。如果设备根本没有被枚举到回到第 5 节的顺序检查时钟、复位、bitstream 和 BAR。7.3 高频问题速查表问题现象常见原因检查方式处理建议x16 变成 x8 或 x4金手指接触不良、转接线质量差、插槽受力看 LnkSta 的 Width重新插拔、清理触点、换转接线验证多块 NVMe 并行总吞吐封顶共享同一个 Switch 上游链路看lspci -tv和并发 fio调整插槽位置或接受上游带宽上限设备完全不被枚举链路训练失败、固件未加载、时钟或复位异常看lspci、REFCLK、PERST#按枚举排查顺序逐步检查Linux 大量 AER 已纠正错误ASPM 链路电源管理、信号质量差dmesg -kgrep AER关闭 ASPM检查插槽和转接线Windows 反复出现 WHEA 事件 17/18链路不稳定、供电或散热问题事件查看器定位设备固定链路速率检查卡体供电散热ACS 无法打开硬件不支持或固件未使能lspci -vvvgrep ACS更换支持 ACS 的 Switch或仅在测试环境用 override回到最初的问题谁会跟你抢 PCIe答案从来不在设备标签上而在设备树的实际挂载关系里。排查争抢问题的核心是三个动作先画出拓扑再确认链路协商状态最后用并发负载验证聚合带宽。掌握lspci、sysfs、AER/WHEA 日志以及并发压测这套链路无论是 x86 服务器、Zynq 嵌入式平台还是 FPGA 加速卡场景都能快速定位到真正的瓶颈。如果要继续深入下一步值得学习的是 LTSSM 链路训练状态机、PCIe 配置空间的完整字段解析以及 Linux PCIe Endpoint Framework 的使用方法。新手阶段最有价值的练习就是反复在真实或虚拟环境里读lspci -tv和lspci -vv直到能一眼看出“这台机器上哪条链路会被谁抢”。
返回列表