搞PCIe调试这么多年,我最大的感受是:很多工程师对PCIe协议本身背得滚瓜烂熟,但真到了现场设备插上去不识别、链路乱降速、或者某块网卡在系统里时有时无,就只会盯着dmesg刷屏,一边刷一边挠头。其实大部分时候,一条lspci命令就足以从拓扑层面看出关键线索。今天这篇,就围绕“利用lspci解析PCIe设备拓扑结构”这个主题,把BDF号怎么读、树形结构怎么看、bridge和endpoint怎么区分、以及热插拔与掉卡时lspci能暴露哪些问题,一次性讲透。无论你是做驱动开发的、搞嵌入式BSP的,还是常年在机房处理服务器故障的,这套方法都能直接落地。
我见过不少人用lspci只会敲一个裸命令,然后看个“设备名字”就完事。这么用,真有点浪费。lspci是Linux下理解PCIe拓扑最直接的工具,它会把PCIe总线上的层级关系、设备功能、甚至链路能力都摊开给你看。但前提是,你得知道怎么去“读”它。这期是系列的第19篇,我们进入实战环节——不聊虚的,全程跟着命令走,我会把我在真实服务器上解析拓扑结构时的一些操作习惯和踩坑经验一起分享出来。
1. 先搞清楚一件事:PCIe设备是怎么“排队”的
1.1 从枚举过程看PCIe的树形接力
PCIe和PCI一样,不是像USB那样靠设备ID广播去识别的,而是主机系统在上电后,从根节点(Root Complex,根复合体)开始,一级一级往下“点名”扫描,这就是常说的PCIe枚举过程。根节点会分配一段总线号给下游设备,如果下游设备是PCIe桥(通常是PCIe Switch或PCIe to PCI桥),这个桥就会声称自己还需要一个次级总线号,于是系统再往下分一段。
这个过程最终形成的结构,是一棵严格的树。树的根是Host Bridge(主机桥),比如CPU内部的PCIe Root Port,往下层层扩展。每个设备在树上的位置,就由“域(Domain)+总线号(Bus)+设备号(Device)+功能号(Function)”唯一确定,简写就是BDF。日常最常见的写法是0000:03:00.0,其中0000是域号(x86上一般固定为0),03是总线号,00是设备号,最后的.0是功能号。
理解BDF之后,lspci的输出就不再是孤立的设备列表,而是一张有树状结构的地图。比如你看到02:00.0和03:00.0都在lspci里出现,但实际可能03:00.0是挂在02:00.0底下的下游设备——这只有通过查看桥设备的“次级总线号(secondary bus number)”才能判断。这就是拓扑解析的核心逻辑。
1.2 为什么拓扑结构决定你能用什么排查思路
很多人平时根本不关心自己主板上有几个PCIe Root Port,直到有一天出现这样的情况:新插一张NVMe转接卡,系统死活认不出盘;或者双口网卡只有一口Link up,另一口消失。这时候你不看拓扑,直接怀疑卡坏了或者驱动不对,很容易走弯路。
正确做法是先确认这个设备有没有出现在lspci里。如果在,说明PCIe枚举阶段是成功的,问题很可能出在驱动或上层;如果不在,那说明卡根本没被系统枚举到,要么是物理链路没建立,要么是插槽供电/复位有问题,要么是Bridge配置出了差错。而要区分这些可能,你就需要知道这个设备究竟应该挂在哪个桥的下游,再沿着桥的BDF去查它的link状态。
再往深处说,多PCIe Switch的设备环境里,拓扑结构直接影响了资源分配逻辑。比如PCIe Switch的每个上行口和下行口在软件眼里就是一个“PCIe-PCI桥”,这些桥各自会占用一组总线号和地址资源。如果你看到一个Switch有8个下行口,那么系统里就会多出8个虚拟的bridge设备。lspci输出中这些bridge的分布,就是你解析整个硬件拓扑的骨架。掌握这套解码方式,进入机房拿到一台陌生服务器,五分钟内我就能大致画出它的全部PCIe扩展结构。
2. lspci工具的基础用法,不止是“列个清单”
2.1 常用参数速查:每个参数解决什么场景
lspci的核心功能是读取PCI配置空间,但默认输出只显示设备名称,信息非常有限。实际排查时必须配合参数使用。我把最常见的参数整理成一个速查表,都是实操中真正会高频用到的:
| 参数 | 作用 | 典型使用场景 |
|---|---|---|
-v | 显示详细设备信息,包括驱动、资源 | 判断设备有没有绑定驱动、占用哪些MMIO |
-vv | 显示PCIe链路状态、Capability、MSI等 | 查看LnkCap/LnkSta、链路速率和宽度 |
-vvv | 输出扩展配置空间全部信息 | 勒索AER、扩展能力、各种Decode偏移 |
-t | 以树状图显示拓扑 | 快速扫描整体层级关系 |
-s [<bus>:]<dev>.<func> | 指定某个BDF设备 | 只仔细看某个具体设备 |
-n | 显示数字形式的Vendor ID和Device ID | 对照驱动数据库,防止名称误判 |
-d <vendor>:<device> | 按厂商和设备ID过滤 | 高效查找某个型号的网卡、GPU列表 |
-vn | 显示详细信息的数字格式 | 能直观看到每个设备的能力位 |
-D | 显示域号,即使域号为0也显示 | 多域系统(如带多个Host)区分设备 |
这里特别说一下-t,它真的是拓扑分析利器。不带-vv时,lspci -t给出的是纯字符树,能一眼看到桥和桥挂设备的关系;带上-tvv则会输出超过两层的详细树,但是因为节点太多容易花眼,我倒建议先用-t看个大概,再针对性的用-s去深入。实际调试时我经常轮流切换三种视图:-t看骨架,-v看驱动,-vvv看链路能力。
2.2 看懂默认输出里每一列在说什么
lspci默认输出每行很短,格式大致是:
00:1f.6 Ethernet controller: Intel Corporation Ethernet Connection (7) I219-V
这里面00:1f.6就是BDF,Ethernet controller是设备所属Class Code,冒号后面是厂商和设备的名称。如果你在某台机器上看到设备名称后面带[8086:15bc],那是-n参数加进去的Vendor ID和Device ID,比如Intel的8086、NVIDIA的10de。
Class Code为什么不直接看设备名?因为lspci默认读取配置空间里的Class字段,它反应的是这个设备“是干嘛用的”:VGA compatible controller、Non-Volatile memory controller、Ethernet controller等等。驱动加载时也是根据Vendor/Device ID去匹配,而不是根据Class。所以当你看到一个硬件名称写得过于“朴素”(比如“PCI bridge”),别急着觉得没用——在多级Switch拓扑下,这类PCI bridge恰恰是最关键的解析节点,它们是树的枝干。
默认输出里还有一个容易忽视的信息:设备名称前面有几位缩进。千万别觉得这是对齐用的,其实这是lspci为了表现拓扑层级故意设计的——缩进一级,就表示该设备在拓扑树中比上一行设备深一级。虽然这个功能没有-t直观,但如果你不习惯看树状图,单看缩进也能大致分辨层级。
3. 实战:从lspci输出还原完整设备拓扑
3.1 在一台带GPU的服务器上实际走一遍
我最近在检修一台双路服务器,上面有一张NVIDIA V100 32G PCIe显卡,又插了两块企业级NVMe SSD,还有一张双口万兆网卡。直接用lspci -t看一眼整体拓扑:
-[0000:00]-+-00.0 Intel Corporation Sky Lake-E DMI3 Registers +-01.0 Intel Corporation Sky Lake-E PCI Express Root Port A +-01.1 Intel Corporation Sky Lake-E PCI Express Root Port B +-04.0 Intel Corporation Sky Lake-E PCI Express Root Port D +-05.0 Intel Corporation Sky Lake-E PCI Express Root Port E ... +-1c.0-[01-02]----00.0-[02]----00.0 NVIDIA Corporation GV100 [TITAN V] [10de:1d81] ...这个输出信息量非常大。先看最外层的00:总线上的那一串Root Port:00:01.0、00:01.1、00:04.0……这些都是CPU内部的PCIe Root Port,每个Root Port主管一条链路。重点看这个分支:00:1c.0-[01-02],意思是00:1c.0这个root port把原本的总线扩展成了两条总线号01和02,其中下面还出现了一层01:00.0的PCIe bridge。这不是什么奇怪的事,通常表示这里接了一个PCIe Switch,或者是转接卡上嵌入了Switch芯片。
这种情况下,真正挂载在末梢的是02:00.0——一张NVIDIA显卡。我得到这个输出后,就能明确告诉同事:显卡不是直接插在主板插槽上,中间隔了一级Switch。如果后面出现显存报错、驱动加载失败,排查范围必须包含这个Switch,因为链路中间多一个桥,就多一个电力、热插拔和信号累计的环节。同时,我也从-t里看到了另一个重要信息:这条分支上没有任何NVMe SSD或网卡被挂进来,说明它们各自分散在另外的Root Port下。
3.2 如何快速定位网卡、GPU、NVMe在树里的确切位置
平时找某块特定设备时,我不会在满屏输出里肉眼搜名字,而是用-d参数直接按设备ID过滤。比如我手头有一块Realtek的千兆网卡,ID是10ec:8168,我就直接敲:
lspci -d 10ec:8168 -vvv这样我能瞬间得到它的BDF号、链路宽度和当前协商速率,还能看到ASPM(Active State Power Management)状态和LnkCap/LnkSta两组关键参数。如果这个设备同时存在多块(比如同型号多端口网卡),加-s能进一步锁定某一个。
再比如批量查GPU,如果是NVIDIA的V100系列,Vendor ID是10de,Device ID可能有好几个,这时候用Vendor ID过滤就能把所有NVIDIA设备列出来:
lspci -d 10de: -nn输出里可能看到几排10de:1db5(V100 smx2等不同版本编号),这样你就知道当前机器到底插了几张卡、在什么BDF位置。要知道BDF不仅是给人看的,上层驱动、虚拟化设备直通、甚至FIO测试绑定CPU Node,全都依赖这个编号。找到BDF后,再用lspci -s <BDF> -vvv看那一张卡有没有正确地Link up、带宽是否是满的。如果V100的LnkSta显示Speed 8GT/s(实际应为16GT/s),那别怀疑卡的问题了,大概率是插槽或者转接线老化了。
4. 动态环节:热插拔、掉卡、降速与AER在lspci中的表现
4.1 设备热插拔后lspci视角的变化
PCIe支持热插拔(Hot-Plug),但服务器里真正玩得转的人都知道,这不是“拔了就完事”。热插拔后系统里该设备对应的PCI配置空间会被移除,lspci输出里这个BDF直接消失。如果你执行lspci时发现某个插槽对应bridge下面空空如也,但机械上明明插着卡,那大概率是热插拔流程被异常打断了。
此时正确复位流程不是重启机器,而是先找到这个slot对应的PCIe bridge,然后操作/sys/bus/pci/slots/底下的控制文件。最典型的操作是:
# 从系统中移除整个总线树下的设备 echo 1 > /sys/bus/pci/slots/2/power # 重新上电并触发重新扫描 echo 0 > /sys/bus/pci/slots/2/power操作过程中,我个人的习惯是开两个终端:一个终端持续执行watch -n 1 "lspci -t",另一个操作slot电源。这样能很直观地看到设备从树里“消失”再“重生”。但要注意:执行hotplug reset时,如果有驱动已经绑定设备,最好先卸载驱动,否则系统可能在内核里留下悬空的PCI device对象,后续再插同一个设备时,会出现新设备号和旧驱动互相错乱的现象。这就算不是lspci的直接责任,它也能帮你判断驱动卸载是否彻底——当你看lspci里设备已经消失,但dmesg还在刷驱动报错,这就说明驱动生命周期没处理好。
4.2 链路降速和掉卡问题在LnkSta和AER里的蛛丝马迹
我们常说的“掉卡、降speed/lane”问题,其实分两类。一类是物理环境导致的链路退化,例如金手指氧化或插槽接触不良,PCIe链路协商速率会从16GT/s掉到8GT/s,甚至只留下x1链路。这类情况光看lspci名称完全看不出来,必须看-vvv输出里LnkSta那些行。
LnkCap是设备支持的最大能力,LnkSta是当前实际协商的结果。我举个例子,一块正常PCIe x8网卡应该是:
LnkCap: Port #0, Speed 16GT/s, Width x8, ASPM not supported LnkSta: Speed 16GT/s, Width x8但如果实际运行时出现这种情况:
LnkSta: Speed 8GT/s, Width x4那基本可以肯定是链路中间出了问题。遇到这种问题,我会先看是哪个桥下游掉下来的,再用lspci -s <桥设备>-vvv去看该桥的LnkSta和LnkCap是否一致,如果桥本身没问题,则重点怀疑显卡或卡槽。很多有关“PCIe稳定性/兼容性”的案例,最终都能从LnkSta的衰减里找到直接证据。这个过程中不需要抓大量波形,一条命令就能完成初步定位,确实是机房排查的第一台阶。
另一类是AER(Advanced Error Reporting)里记录的错误。AER在PCIe的Capability里专门用一部分配置空间存放错误状态。lspci -vvv如果看到该设备有AERCap支持,还可以用lspci -vvv去检查ECRC、Uncorrectable Error Mask等字段,但更实际的做法是直接从dmesg里捞aer相关的错误记录。不过,AER的错误往往会有“报错设备”的BDF,这个BDF怎么对到实际硬件,还得回到lspci的拓扑输出里来定位。有一次我看到一块NVMe上报PCIe Bus Error: severity=Corrected的日志,dmesg里指向0000:03:00.0,我通过lspci -s 03:00.0一查,确认它是在一块PCIe Switch下面,而不是直连主板。顺着树形结构逐级排查后,发现是Switch的某一根链路down导致路径上错误累计。没有树形拓扑的视角,这类问题排查至少要慢一倍。
5. 常见问题与排查技巧:lspci使用中的坑
5.1 设备显示为Class但驱动不认,先别急着叹气
有相当多的时候,你会在lspci里看到某个设备清清楚楚写着Ethernet controller,但ip link命令里就是没有对应网口。这类问题十有八九不是枚举失败,而是驱动没加载或者加载顺序不对。这时候我会先看lspci -v里有没有Kernel driver in use: xxx字眼。如果显示Unknown,说明驱动没绑定,那就去查一下该设备的Vendor/Device ID是否在驱动支持列表里。
我踩过的一个经典坑:某国产平台的PCIe转SATA控制器,lspci显示SATA controller: ASMedia Technology Inc. ASM1062,但偏偏系统里就是没有sata盘。后来发现是BIOS把控制器ROM设成disabled,导致配置空间里没有扩展ROM而系统没有分配BAR资源。判断方法是跑lspci -v,看有没有Region 0: Memory at ...这类资源条目。没有Region输出,驱动自然无法访问设备寄存器,这跟驱动本身半毛钱关系都没有。
5.2 lspci树只有根桥,下游设备一个不见
这种情况常见于插槽供电异常、PCIe插槽被BIOS禁用,或者整个PCIe链路没能完成link training。看到这种树别有挫败感,先检查主板PCIe针对该slot的BIOS设置,确认没有关闭。其次看有没有ACPI相关错误,有些平台为了省电默认启用ASPM,在低功耗状态下链路可以协商成功,但如果下电之后无法重新唤醒,设备会彻底消失。这时候除了lspci,还可以看看/sys/bus/pci/slots/下面有没有slot节点,有的话尝试操作slot power重置。
务必记住:lspci本身无法告诉你链路为什么没有train up,它只能告诉你“有没有这个设备”。所以定位链路问题还需要辅助看dmesg里有没有PCIe protocol层的报错,比如link is down或者link training failed。但反过来,lspci能帮你排除“是不是软硬件兼容性问题”——如果根桥和Switch都能被枚举出来,只有末端的disk或网卡无法枚举,问题方向就完全变了。
5.3 信息太多看不全?试试用管道和脚本组合
lspci输出每次动辄几十行,手动翻看效率感人。我通常会用组合命令来做初步筛选:
# 只看所有PCI桥设备 lspci | grep -i "PCI bridge" # 只看所有带设备号的NVMe/SSD控制器 lspci | grep -i "Non-Volatile memory" # 如果想同时看厂商ID和名称,避免型号混淆 lspci -nn | grep -i intel | head -20更进阶一点的,用awk按bus处理,把同一个bus下的设备归组:
lspci | awk '{print $1}' | awk -F: '{print $1}' | sort -u这能快速列出当前机器用到了哪些bus号。如果出现了很多不连续的bus号,比如0、1、3、5、7,说明中间有很多Switch或虚拟桥占用了bus资源,这时候再看lspci -t,基本就能还原整体结构。如果bus号全部连续,大概率是普通直连拓扑。
还有一个实用技巧:用lspci -D强显域,特别是在服务器上配置了多个PCIe域的时候,不同域之间设备可能完全独立,若不带-D,你会误以为它们在同一条总线上,导致后续查看sysfs路径时找不到设备。
6. 最后分享几个lspci实操中的个人习惯
我每次拿到新服务器或者现场出问题,第一件事就是跑三连:lspci -t看拓扑草图,lspci -nn | sort看有哪些设备数字ID,再针对关键设备(网卡、GPU、存储控制器)执行lspci -s BDF -vvv看链路状态和Capability。这套流程几乎已经刻进肌肉记忆了。千万别一上来就用-vvv刷全量输出,信息过载还不如不看。
另一个习惯是,在排查涉及热插拔、掉速这类动态问题时,我会结合watch命令持续观察:
watch -n 3 "lspci -s 03:00.0 -vvv | grep -E 'LnkCap|LnkSta'"这样一旦链路抖动,协商速率和宽度变化就能立刻被记录到。配合dmesg里AER的时间戳,基本能还原出“链路掉速导致设备降级”的完整时间线。很多看似瞬息万变的怪问题,其实只要这样持续盯一阵,规律就会自己浮出水面。
lspci并不是一个能“修复”问题的工具,它更像一张透视仪:帮你把PCIe树里的每个节点、每条链路、每个Endpoint的真实状态看穿。做排查时先看树、再找节点、最后看链路,这三步走稳了,很多疑难杂症就输在第一步乱猜测上。这期的实战先聊到这里,下一篇我准备继续拿真实案例,说说怎么结合lspci输出去分析设备资源冲突和MMIO地址分配的问题,希望能帮你把这棵PCIe树彻底吃透。