标题里的“谁在跟你抢 PCIe?”不是一句文案,而是一条 MiniMax-H3 竖屏短片想讲清楚的问题:一块主板上同时挂着 GPU、NVMe、网卡、FPGA 加速卡,它们都在通过 PCIe 申请链路、带宽和中断资源。短片用竖屏动画把“抢”这个动作做得很直观,但看完之后真正需要搞明白的是:抢的到底是什么、怎么查谁在抢、设备认不出来或链路降速时应该从哪一步开始排查。
这篇不讨论 AI 视频生成的提示词,只讲 PCIe 本身。内容会覆盖链路协商、设备枚举、ACS 冲突、带宽争抢验证、批量巡检脚本,以及 Linux 和 Zynq/FPGA 场景下最常用的排查命令。适合刚接触 PCIe 驱动开发、遇到过 PCIe 设备不识别、或者发现多卡机器带宽跑不满的读者。如果你打算做一条同主题竖屏短视频,这篇的技术清单也能直接当脚本素材。
1. 核心能力速览
| 能力项 | 说明 |
|---|---|
| 主题范围 | PCIe 资源竞争、链路协商、设备枚举、ACS 冲突、带宽排查 |
| 涉及协议 | PCIe 链路训练、配置空间、ACS、AER、MSI/MSI-X、IOMMU |
| 支持平台 | Linux、Windows、Zynq/FPGA 嵌入式平台 |
| 核心工具 | lspci、setpci、dmesg、sysfs、Vivado、PCIe test suite |
| 批量任务 | 支持通过脚本对整机所有 PCIe 设备批量采集链路状态 |
| 接口能力 | PCIe 本身不是 HTTP API,但 Linux sysfs 提供可编程采集接口 |
| 硬件门槛 | 需要有 PCIe 接口的台式机、服务器或 Zynq 开发板 |
| 适用人群 | 驱动开发、FPGA 调试、运维排障、多卡工作站使用者 |
| 风险评估 | 涉及硬件配置操作,部分调试命令和 BIOS 参数调整需谨慎执行 |
这套内容不需要特殊型号的显卡,也不需要固定显存规模,核心是一个“能跑 lspci 的操作系统”加一份“能插 PCIe 设备的硬件”。
2. 适用场景与使用边界
什么情况下你会遇到“有人在抢 PCIe”?
第一种是多卡工作站。一张 GPU 在做训练,另一张 GPU 在渲染,中间 NVMe 还在写数据集,所有数据都要经过 CPU 的 Root Port 或者 PCIe Switch,共享链路一拥堵,训练速度就掉。第二种是服务器扩容,插了多张网卡和 RAID 卡,结果 BIOS 枚举后某张卡带宽变成 x4 而不是 x16。第三种是 FPGA 开发板调试,自己在 Zynq 上例化了一个 PCIe Root Complex,接上端点设备后发现 lspci 里根本看不到。
这些场景都能用同一套方法排查:先看拓扑,再看链路协商结果,最后定位瓶颈和错误。
使用边界也要说清楚。PCIe 排查能解决的是链路协商、枚举顺序、带宽不足、设备识别异常、ACS 隔离问题。它不能替代逻辑分析仪和协议分析仪,也不能处理 PCB 设计阶段的高速信号完整性问题。另外,对生产环境执行 BIOS 参数修改、驱动参数注入、设备复位或热插拔前,需要确认设备归属和授权范围,避免影响正在运行的服务。
3. PCIe 资源竞争的本质:链路、端口、带宽都要看
3.1 PCIe 链路与 Lane 协商
PCIe 是点对点串行总线,每个设备通过一条或多条 Lane 与对端连接。Lane 是收发差分对,一条链路可以包含 1、2、4、8、16 条 Lane,体现在设备命名上就是 x1、x4、x8、x16。
链路两端在上电后要完成一个叫 LTSSM 的链路训练流程,状态机依次经过 Detect、Polling、Configuration、L0 等状态,最终协商出双方都能接受的速率和宽度。协商结果不代表设备能力,只代表当前物理连接能达到的结果。
| 代际 | 单 Lane 速率 | 编码方式 | x16 单向理论带宽 |
|---|---|---|---|
| PCIe 1.0 | 2.5 GT/s | 8b/10b | 约 3.93 GB/s |
| PCIe 2.0 | 5 GT/s | 8b/10b | 约 7.86 GB/s |
| PCIe 3.0 | 8 GT/s | 128b/130b | 约 15.75 GB/s |
| PCIe 4.0 | 16 GT/s | 128b/130b | 约 31.5 GB/s |
| PCIe 5.0 | 32 GT/s | 128b/130b | 约 63 GB/s |
这里的 GT/s 是每秒传输的 Giga Transfers,包含编码开销,实际有效带宽要按编码效率折算。所以同样写着 PCIe 4.0 x16,跑数据处理时看到的内存拷贝吞吐不会等于 32 GB/s,而是接近 31.5 GB/s 甚至更低。
“抢带宽”最常见的表现就是协商宽度低于预期,例如显卡槽位物理是 x16,但插上后 lspci 显示 negotiated width 只有 x8。这可能是插槽被机械阻挡、后端缺少 Lane、BIOS 分流配置或金手指接触问题。
3.2 被忽略的共享瓶颈:PCIe Switch 与上行链路
很多人以为设备数量越多总带宽越大,这是误解。PCIe Switch 的作用是扩展端口数量,但它内部有一个上行端口连接 Root Complex,所有下行端口的数据都要通过上行端口转发。
一个典型拓扑是这样的:
- CPU Root Port 0 直连 GPU
- CPU Root Port 1 直连 NVMe
- CPU Root Port 2 连接 PCIe Switch
- PCIe Switch 下行挂着网卡 A、网卡 B、RAID 卡
前两条链路是 CPU 直连,独占带宽;第三条链路通过 Switch 扩展出来的所有设备共享 Switch 的上行带宽。如果上行只有 PCIe 4.0 x8,上面挂了三张网卡,三张网卡同时跑满流量时,必然在上行端口拥堵。
排查这类问题要看完整拓扑,不能只看单个设备的协商状态。设备自身的 Link Status 正常,不代表它到 CPU 之间的通路没有共享瓶颈。
3.3 不止带宽:中断与地址空间也在抢
资源竞争不只是带宽。MSI/MSI-X 中断向量、BAR 地址空间、IOMMU 页表同样会冲突。
网卡启用多队列时依赖 MSI-X,每个队列需要一个中断向量,中断分配如果集中到同一个 CPU 核心,高流量下会出现 CPU 软中断占用过高,直观表现是“网卡没跑满但业务延迟很高”。GPU 和 FPGA 这类设备会申请较大的 BAR 空间,某些老主板的地址窗口分配不合理,会导致第二张卡 BAR 申请失败,系统直接不识别。
所以在回答“谁在跟你抢 PCIe”时,第一反应不该是去改设备,而是先获取整机的拓扑和资源分配现状。
4. 环境准备与前置条件
4.1 Linux 环境
Linux 下排查 PCIe 主要依赖 pciutils 和内核提供的 sysfs 接口。
安装工具:
# Debian/Ubuntu sudo apt install pciutils # RHEL/CentOS sudo yum install pciutils大部分服务器和桌面发行版默认自带 lspci,安装后用 root 权限执行能看到完整链路信息。权限不足时,部分 config space 读取会被内核拦截,所以能 sudo 尽量 sudo。
4.2 Windows 环境
Windows 没有 lspci 原生命令,但设备管理器已经能看到设备是否存在、是否有感叹号、是否报错。更详细的链路速率和宽度可以在 BIOS 里看,也可以用 HWiNFO、GPU-Z 等工具查看。
Windows 下 PCIe 错误会以 WHEA 事件形式记录。如果系统中反复出现 WHEA-PCIe 警告,说明有 PCIe 设备触发了 AER 错误或链路降速,事件查看器里的来源和设备 Instance ID 能帮你定位是哪张卡、哪个 BDF。
4.3 FPGA 与嵌入式环境
如果你在 Zynq 或纯 FPGA 上调试 PCIe,需要准备:
- Vivado 工程,用于例化 PCIe IP 核
- 串口终端,用于查看启动日志
- 逻辑分析仪或 VIO,用于观测 user_lnk_up、perst、时钟信号
- 一块符合规范的 PCIe 端点设备,例如 NVMe 转接卡、网卡或自研 EP 逻辑
Zynq 的调试路径和普通服务器不太一样。普通服务器是 CPU 作为 Root Complex,系统固件自动做枚举;Zynq 上你既可以用 PS 端 PCIe 控制器,也可以在 PL 里例化 AXI PCIe Root Complex,枚举逻辑可能由裸机程序或 Linux 内核完成,每一步都要自己确认。
5. 实操一:看拓扑和链路协商
5.1 查看 PCIe 拓扑
先获取整机 PCIe 树状拓扑:
sudo lspci -tv输出会显示总线号、设备号、功能号和设备类型。重点关注设备挂在哪个 Root Port 下面,是否有多设备共享同一个上游端口。
比如输出里看到:
-[0000:00]-+-00.0 Intel Host Bridge +-01.0-[01]----00.0 NVIDIA GPU +-1b.0-[03]----00.0 Realtek Ethernet就说明 GPU 占了一条 Root Port,网卡挂在另一条 Root Port 下,两者不共享上行。如果看到某个 Root Port 下面带了一个 Switch,Switch 下面又带了三四个设备,那这个上行端口就是潜在的争抢点。
5.2 查看单设备链路
要查看某个设备的链路协商情况,用 -vvv 参数:
sudo lspci -vvv -s 01:00.0重点看这几项:
LnkCap: Port #0, Speed 32GT/s, Width x16 LnkSta: Speed 32GT/s (ok), Width x16 (ok)LnkCap 表示该设备所在的端口物理能力上限,LnkSta 表示当前实际协商结果。Speed 和 Width 任一比预期低,都需要追查原因。
内核 sysfs 也提供同样的信息:
cat /sys/bus/pci/devices/0000:01:00.0/max_link_speed cat /sys/bus/pci/devices/0000:01:00.0/max_link_width cat /sys/bus/pci/devices/0000:01:00.0/current_link_speed cat /sys/bus/pci/devices/0000:01:00.0/current_link_width这套接口很适合写脚本,配合 for 循环就能批量输出整机每个设备的协商状态。
5.3 带宽协商的判断标准
判断标准不是“设备支持多少”,而是“当前协商了多少,是否满足业务需要”。
一张支持 PCIe 4.0 x16 的 GPU,如果插在物理 x8 的槽位上,协商宽度会是 x8。可以从 BIOS 的 PCIe Slot Configuration 中重新确认槽位 Lane 设置,也可以把卡换到 CPU 直连 x16 的槽位验证。
如果是通过 M.2 转接卡插 NVMe,转接卡只引出 x4 Lane,那么设备再快也只能用 x4 的带宽。要注意转接卡本身的质量,劣质转接卡可能导致高负载下链路直接降速到 x1。
6. 实操二:设备枚举与不识别问题排查
6.1 PCIe 枚举原理
PCIe 枚举由 Root Complex 发起。系统固件或内核首先扫描总线 0,通过配置读写事务发现 Root Port,然后为每个 Root Port 分配下一级总线号。如果下游存在桥或 Switch,则继续递归分配总线号,直到所有层级的设备都被遍历。
每次枚举都会读取设备的 Vendor ID 和 Device ID。如果读取结果是 0xFFFFFFFF,说明配置请求没有收到有效响应,系统会认为总线上不存在设备。
所以在 Zynq 上调试“PCIe 设备不识别”时,起点不是看驱动,而是看枚举阶段是否拿到了正确的 Vendor ID。
6.2 Zynq 调试 PCIe 设备的典型排查路径
在 Zynq 上,如果 PCIe 端点设备没有被枚举出来,按下面顺序排查:
第一步确认链路训练。查看 PL 中 PCIe IP 核的 user_lnk_up 信号,或者打印 PS-PCIe 的链路状态寄存器。链路没 up,后续所有枚举都没有意义。
第二步确认参考时钟。PCIe 需要 100 MHz 参考时钟,时钟幅度和 AC 耦合电容异常会导致训练一直重试。示波器或时钟芯片寄存器能确认输出。
第三步确认复位信号。PERST# 必须有效释放,设备在复位释放后才能进入 Detect 状态。常见问题是用 GPIO 拉复位后没有给足够延时。
第四步确认配置读取。在 CPU 侧通过 setpci 直接读端点的配置空间:
sudo setpci -s 01:00.0 VENDOR_ID.w sudo setpci -s 01:00.0 DEVICE_ID.w如果读回来不是 0xFFFFFFFF,但系统 lspci 不显示,可能是枚举时序问题,需要调整设备 ready 时间。如果读回来全是 F,重点回到链路训练。
第五步检查 Vivado 中 AXI PCIe 的地址映射。例化 AXI PCIe Root Complex 后,AXI 地址窗口与 PCIe 地址之间要做 inbound/outbound 转换。outbound 是 CPU/PS 发起、发往 PCIe 设备的访问,inbound 是 PCIe 设备发起、发往内存的 DMA 访问。两边映射关系写错,设备就算枚举出来,DMA 也无法工作。
6.3 ACS 打不开与总线错误
ACS 全称 Access Control Services,作用是限制同一 PCIe 拓扑内的设备绕过 Root Complex 直接通信。虚拟化直通场景下,需要 ACS 把设备隔离,否则一个恶意或异常设备可能直接访问另一个设备的内存。
部分设备对 ACS 支持不完整,lspci 里看不到完整能力位,或者系统日志报告 ACS 冲突。Linux 启动参数里有一个常用选项:
pcie_acs_override=downstream,multifunction这个参数会强制让内核为下游端口和多功能设备打开 ACS 行为。但要注意,它属于 workaround,不是设备原生支持,生产环境需要结合虚拟化和 IOMMU 需求评估后再使用。
与总线错误相关的是 AER 和 WHEA。Linux 下看:
sudo dmesg | grep -i "aer\|pcieport\|bus error"如果反复出现 AER 报错,说明链路在运行中出现错误或降速。常见原因包括金手指接触不良、PCIe 卡功耗过高导致供电不稳、转接卡信号质量差、链路跑在 Gen4 但主板信号完整性不足。可以先在 BIOS 中把链路速率手动降到 Gen3,对比错误是否消失,以此判断是否是物理信号问题。
USB4 场景中也要注意,USB4 是通过 PCIe 隧道把 PCIe 事务封装在 Type-C 链路里传输,主机枚举出的外部设备本质还是 PCIe 设备。如果 USB4 外设识别异常,除了看设备管理器,还要确认 USB4 控制器固件和线缆是否为完整 40Gbps 规格,隧道带宽分配不足也会造成设备随机掉线。
7. 性能观察与带宽争抢验证
7.1 怎么判断带宽被抢
打开系统监控后,CPU、内存、磁盘、GPU 使用率都正常,但业务吞吐不达标,这时候就要怀疑 PCIe 链路是否成为瓶颈。最直接的观察点有三个:协商宽度是否满足设备能力、是否存在大量 PCIe 错误、设备吞吐是否远低于理论值。
GPU 场景可以用 nvidia-smi 查看 GPU 的 PCIe 信息:
nvidia-smi --query-gpu=index,name,pcie.link.gen.current,pcie.link.width.current --format=csv输出类似:
0, NVIDIA GeForce RTX 4090, 4, 16这里显示的是当前协商的 PCIe 代际和链路宽度,不代表实时带宽占用,但能判断链路是否到达能力上限。如果 GPU 是 Gen4 x16,输出却只有 3 和 8,链路已经降速。
NVMe 场景可以用 dd 或 fio 做顺序读,同时观察 CPU 侧 PCIe 错误计数,判断高负载下链路是否稳定:
dd if=/dev/nvme0n1 of=/dev/null bs=1M count=8192如果高负载时 dmesg 出现 AER 错误,基本可以确定 PCIe 链路在高速率下不稳定。
7.2 实测方案设计
要验证“谁在抢带宽”,不建议直接用复杂业务压测,先做基线测试。
第一步,单独测每类设备的极限吞吐。例如单独跑 NVMe 顺序读,记录吞吐;单独跑 GPU 显存拷贝或渲染任务,记录帧率。第二步,让多个设备同时满负荷运行,再次记录吞吐。第三步,比较前后数据。如果单跑时 NVMe 能到 6500 MB/s,GPU 同时满载后降到 3000 MB/s,说明两者在上行链路或 CPU 侧资源上产生了竞争。
多网卡场景可以本机开 iperf 服务,让多张网卡同时收发:
iperf3 -s iperf3 -c 127.0.0.1 -P 4回环路径不一定走 PCIe,更可靠的是接两台机器,分别从不同网卡打流量,观察 Switch 上行端口是否成为瓶颈。
7.3 降低争抢的方向
- 优先把高带宽设备挂到 CPU 直连 Root Port 上。
- 减少 PCIe Switch 级联层级,避免多设备共享窄上行。
- 对不要求高可靠性的场景,可以关闭未使用板载设备释放 Lane。
- BIOS 中确认 PCIe Slot Configuration,避免 x16 槽被拆分为 x8/x4。
- 筛选链路速率,从 Gen4 降到 Gen3,如果性能损失可接受,能显著提高链路稳定性。
- 确认所有设备固件和驱动已更新,很多“掉链”问题由固件 bug 引起。
8. 接口与批量巡检脚本
8.1 Linux sysfs:PCIe 的“可编程接口”
PCIe 没有传统意义上的 REST API,但 Linux 把每个 PCIe 设备都暴露在 /sys/bus/pci/devices/ 目录下,可以通过读取文件和写文件的方式获取状态或触发重置。这相当于内核提供的可编程接口。
常用文件路径:
/sys/bus/pci/devices/0000:01:00.0/vendor /sys/bus/pci/devices/0000:01:00.0/device /sys/bus/pci/devices/0000:01:00.0/current_link_speed /sys/bus/pci/devices/0000:01:00.0/current_link_width /sys/bus/pci/devices/0000:01:00.0/irq /sys/bus/pci/devices/0000:01:00.0/resource写巡检脚本时优先读 sysfs,比解析 lspci 文本更稳定。
8.2 批量采集所有 PCIe 设备带宽状态
下面这个 Python 脚本可以批量扫描整机 PCIe 设备,输出当前链路速率和宽度,适合做巡检基线:
import os import glob base = "/sys/bus/pci/devices" for dev in sorted(glob.glob(f"{base}/*")): bdf = os.path.basename(dev) def read_file(name): try: with open(os.path.join(dev, name), "r") as f: return f.read().strip() except FileNotFoundError: return "N/A" vendor = read_file("vendor") device = read_file("device") max_speed = read_file("max_link_speed") cur_speed = read_file("current_link_speed") max_width = read_file("max_link_width") cur_width = read_file("current_link_width") irq = read_file("irq") print(f"{bdf} | vendor={vendor} device={device} | " f"link {cur_speed}/{max_speed} width {cur_width}/{max_width} | irq={irq}")脚本输出示例:
0000:01:00.0 | vendor=0x10de device=0x2684 | link 16GT/s/16GT/s width 16/16 | irq=81 0000:02:00.0 | vendor=0x144d device=0xa80a | link 16GT/s/16GT/s width 4/4 | irq=82实际 vendor ID 与设备对应关系以机器为准,脚本只负责采集。把输出保存成 CSV 后可以对比前后状态,快速发现降速设备。
8.3 批量检查 AER 错误
巡检不仅要看状态,还要看错误计数。Linux 下 AER 错误统计可以通过 aer_inject 等方式注入,日常巡检直接读 dmesg 更简单:
sudo dmesg -T | grep -i "aerr\|pcieport" | tail -50更自动化一点,可以定时执行一条命令,把错误信息追加到日志文件:
echo "=== $(date) ===" >> /var/log/pcie_check.log sudo dmesg -T | grep -i "aer" | tail -20 >> /var/log/pcie_check.log如果脚本发现日志里持续出现同一个 Root Port 的 AER 错误,例如:
pcieport 0000:00:1b.0: AER: Corrected error received: 0000:02:00.0说明对应设备链路存在稳定性问题,需要考虑降速、更换转接卡或清洁金手指。
9. 常见问题与排查方法
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| lspci 看不到设备 | 链路没有训练成功,或配置空间读回全 F | 检查 PERST#、参考时钟、电源,读 user_lnk_up | 修复硬件复位时序,确认时钟输出,重新训练链路 |
| 链路协商宽度只有 x4,预期 x16 | 槽位 Lane 不足、BIOS 拆分、金手指接触不良 | lspci -vvv 查看 LnkSta,在 BIOS 核对 Slot Configuration | 更换槽位,恢复 BIOS 默认 PCIe 配置,清洁金手指 |
| 协商速率降级到 Gen1/Gen2 | 信号质量差、转接卡劣质、链路不稳定 | dmesg 看 AER 报错,手动锁定 Gen3 对比 | 更换线缆/转接卡,BIOS 固定速率,降低链路工作频率 |
| 性能吞吐跑不满 | 上行链路共享带宽不足 | 多设备同时压测,观察 Switch 拓扑 | 把高带宽设备移到 CPU 直连端口,减少共享级联 |
| ACS 无法打开 | 设备未完整实现 ACS 能力 | lspci -vvv 查看 ACSCap | 按场景评估 pcie_acs_override 参数,优先换支持 ACS 的设备 |
| Windows 出现 WHEA-PCIe 错误 | PCIe 链路错误、驱动或固件异常 | 事件查看器读 Instance ID,配合 dmesg 类日志定位 | 更新 BIOS 和驱动,清理转接卡,必要时降速 |
| Linux dmesg 报 pcie bus error | 链路错误率上升,访问请求异常 | 查看 AER 报错设备,观察高负载是否复现 | 检查供电、接触、线缆,降低速率并重压测试 |
| Zynq 上端点设备不枚举 | PL 里 PCIe RC 未完成训练或 AXI 地址映射错误 | 检查 user_lnk_up,用 VIO 观测信号,核对 inbound/outbound 映射 | 修复映射关系,确认时钟复位,重新生成 bitstream |
| USB4 外设偶尔掉线 | USB4 隧道带宽不足或线缆规格不达标 | 更换 40Gbps 完整线缆,在系统日志确认隧道 PCIe 协商 | 保证线缆规格,减少同时占用 USB4 带宽的传输任务 |
| 设备插上后系统卡死或重启 | 设备 BAR 冲突、供电不足、固件兼容性问题 | 记录日志,确认新增设备 BDF 与 BAR 分配 | BIOS 更新,更换供电,按最小系统逐步添加设备 |
10. 最佳实践与合规边界
PCIe 调试最怕的是没有基线。建议第一次装机或接到新开发板时,先保存一份完整的 lspci -vvv 输出、dmesg PCIe 相关日志、BIOS PCIe 配置截图。这样后面出现不识别或带宽下降时,可以直接对比差异。
工程化部署建议:
- 把巡检脚本接入定时任务,每天记录链路状态、错误计数。
- 输出结果按日期归档,文件名带上主机名。
- 批量修改 BIOS 参数或驱动参数前,先在单台测试机验证。
- FPGA 工程做 PCIe 调试时,保留一个只包含最小 RC 的工程,排除用户逻辑干扰。
- 做带宽压测时先确认负载不会影响正在运行的业务。
合规和隐私方面要特别提醒。PCIe 调试会读取配置空间、BAR 地址、DMA 地址,这些信息在特定环境下可能涉及设备固件细节和企业内部数据。调试他人设备、服务器或 FPGA 板卡前,必须确认授权范围。不要用 PCIe 漏洞或 ACS workaround 去绕过安全机制,也不要把采集到的设备信息、日志数据向外传播。
如果需要从设备中读取存储数据,例如用 NVMe 直通做测试,务必遵守数据安全要求,测试结束后销毁临时数据。
11. 总结与下一步
“谁在跟你抢 PCIe”这个问题,本质是链路、带宽、中断、地址空间四个维度的竞争。最先要验证的不是设备性能,而是协商结果:打开 lspci -vvv,确认 Speed 和 Width 是否达到预期;再看拓扑,确认设备之间是否共享上行链路;最后用多设备并发压测,判断真正的瓶颈。
最容易踩的坑有三个:把端口数量误以为带宽翻倍、不保存基线就直接改 BIOS、在 Zynq 上忽略链路训练直接查驱动。这三个坑对应同一件事:先确认物理链路,再谈软件配置。
下一步如果还想深入,可以继续看 PCIe 6.0 的 PAM4 编码和 CXL 对资源池化的影响,也可以把巡检脚本扩展成一个小型 Web 服务,用图表展示每台机器的 PCIe 链路健康度。这块既适合做 FPGA 方向的技术积累,也适合服务器运维场景落地。建议先把今天的 lspci 基线命令保存下来,下次遇到“设备又慢了”的时候,你会感谢当初留了这份记录。