十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

lspci背后的内核机制:AI Infra工程师必备的PCI/PCIe排查指南

lspci背后的内核机制:AI Infra工程师必备的PCI/PCIe排查指南 1. 为什么 AI Infra 工程师离不开 lspci做 AI Infra 的兄弟应该都有过这种经历新到一批 8 卡 GPU 服务器还没装驱动第一件事就是敲lspci | grep -i nvidia。看到 8 行 NVIDIA 设备列出来心里那块石头才落地。要是只出来 6 行或者 4 行后面就是一连串的硬件排查、BMC 日志、换槽位、甚至退货流程。这个场景基本每天都在各种机房和实验室里上演lspci 就是 AI Infra 工程师的听诊器第一条命令永远是它。但这个天天都在用的命令绝大多数人其实只停留在“能用”的层面知道它能列出 PCI 设备知道加-v能看更多信息知道用-s 01:00.0能过滤指定设备。至于它背后的机制——当你敲下回车的那一瞬间Linux 内核里到底发生了什么为什么有些设备 lspci 能看到但系统里不认为什么 GPU 在 lspci 里显示为 VGA compatible controller 而不是 NVIDIA A100这些问题很少有人能讲清楚。我大概拆解一下lspci 的工作可以分成用户态和内核态两段。用户态负责调起进程、读取 sysfs、解析 PCI ID 数据库、排版输出内核态负责打通文件系统到 PCI 配置空间的通路维护设备枚举结果响应一个个 read 请求。今天这篇就沿着这两条线把 lspci 从敲下去到输出结果的完整链路过一遍重点讲内核态这一半——因为这才是判断 AI 服务器硬件健康度的基石也是排查 GPU 识别问题的底层能力。这篇内容适合所有跟 GPU 服务器、内核、驱动打过交道的工程师。不管你是搞训练集群的、做推理优化的还是维护裸金属平台和虚拟化环境的理解了这条链路之后再看lspci输出、/sys/bus/pci目录结构、dmesg里的 PCIe 报错都会有一种“原来如此”的通透感。新入门的朋友也不用担心我会从最基本的概念讲起保证你看完能直接上手用也能在下次排查问题的时候多一层判断维度。2. 敲下回车之后一条命令的用户态前半程2.1 从 shell 到进程lspci 是怎么被拉起来的先看用户态这条线。你在终端敲下lspcishell 会先做命令查找——依次搜索 PATH 环境变量里的目录最终找到/usr/bin/lspci这个可执行文件。shell 调用fork()创建子进程然后子进程调用execve()把 lspci 的二进制镜像加载进内存完成进程替换。这一步会触发内核的 ELF 加载器把可执行文件的代码段、数据段映射到进程地址空间同时加载动态链接器/lib64/ld-linux-x86-64.so.2由它负责把 lspci 依赖的共享库比如 libc、libpci拉进来完成符号重定位。这个过程本身没什么特殊的任何一个命令都是这么跑起来的。但有个细节值得注意lspci 虽然看起来是个“查询命令”它的实现其实不是一个简单的 sysfs 遍历程序而是链接了 libpci 库。libpci 是 PCI 实用工具的核心库它封装了所有访问 PCI 配置空间的逻辑lspci 只是个命令行前端。这个分层很重要因为 libpci 面对的不只是 Linux——它还有 FreeBSD、Windows、Solaris 等平台的实现每个平台访问 PCI 配置空间的方式都不一样。在 Linux 上libpci 主要走 sysfs这是最直观、最安全的方式。2.2 核心数据结构libpci 的初始化流程libpci 初始化时会先创建一个struct pci_access对象。这个对象是整个 PCI 访问的上下文里面保存了访问方法access method、错误处理回调、设备链表、ID 数据库的加载状态等信息。在 Linux 平台上pci_init()会调用linux_init()设置好访问 sysfs 的路径、确定可用的访问方法sysfs、proc、config。然后pci_scan_bus()开始扫描总线。这一步是关键——它并不是直接去访问硬件而是去 sysfs 下枚举已经存在的 PCI 设备也就是“用户态扫描内核态已经做好的枚举结果”。每发现一个设备目录比如/sys/bus/pci/devices/0000:3b:00.0就创建一个struct pci_dev对象填充总线号、设备号、功能号BDF并继续读取 vendor ID、device ID、class code 等基本信息。这些信息全部来自 sysfs 文件不直接碰硬件。有个容易忽视的点lspci 默认显示的是/sys/bus/pci/devices/下面的内容这个目录里的每个子目录都是一个 PCI 设备。目录名就是设备的 BDF 号格式是domain:bus:slot.function。比如0000:3b:00.0表示 domain 0bus 0x3bslot 0function 0。这个编号在后面的排查中会反复用到尤其是在定位 GPU 在 PCIe 拓扑中的位置时。3. 内核态的主战场sysfs 与 PCI 子系统的协作3.1 sysfs 是“窗口”PCI 子系统才是“仓库”lspci 在用户态读到的所有东西源头都在内核的 sysfs。sysfs 是一种基于内存的文件系统挂载在/sys目录它的作用是把内核里的对象设备、驱动、总线等以文件和目录的形式暴露给用户态。对 PCI 设备来说关键路径有这么几个/sys/bus/pci/devices/设备符号链接目录按 BDF 命名/sys/bus/pci/slots/物理槽位信息/sys/class/pci_bus/PCI 总线信息/sys/kernel/ioapic/、/sys/firmware/等辅助路径这些目录不是凭空生成的而是 PCI 子系统在设备枚举阶段构建的。内核启动时PCI 子系统会扫描 PCI 总线发现设备、分配资源、建立设备模型然后把结果注册到 sysfs 中。lspci 做的事本质上就是在读内核已经整理好的“设备档案”。所以现在的问题就变成这些“档案”是怎么生成的这就是 PCI 子系统设备枚举的完整故事——整个枚举过程从内核启动开始分为两个阶段。第一个阶段是早期 PCI 探测。内核在初始化阶段会调用acpi_pci_root_add()之类的钩子遍历 ACPI 表DSDT/SBDF里描述的所有 PCI 根桥host bridge为每个根桥创建struct pci_host_bridge。然后调用pci_scan_root_bus()进入实际的扫描流程。第二个阶段是递归扫描。pci_scan_bus()会读取总线上的每个设备对每个设备再检查它是否是 PCI-to-PCI 桥Type 1 配置头如果是就继续往下扫描次级总线。这就是为什么 PCIe 拓扑能以树形结构呈现的原因——每个 bridge 就是树的一个分叉。3.2 配置空间访问从 sysfs 到硬件寄存器前面说过lspci 读 sysfs 就相当于读内核的枚举结果。但有一个例外lspci -xxx这类命令会直接读取设备的配置空间扩展区域。这种情况下内核会真正访问 PCI 配置寄存器。怎么访问呢PC 体系结构提供了两种配置空间访问机制Config I/O 端口通过0xCF8/0xCFC端口对。往0xCF8写入要访问的地址包括总线号、设备号、功能号、偏移量然后从0xCFC读数据。这是 PCI 时代的标准做法。MMIO 配置空间通过内存映射方式访问。每个 PCIe 设备的配置空间被映射到物理内存地址空间内核直接对映射地址做 readl/writel。这就是pci_mmcfg机制x86 上的 MMCONFIG 表提供了基础地址。内核的 PCI 配置空间访问最终都会走到pci_ops的read回调函数。x86 平台上这个回调可能是pci_conf1_read走 CF8/CFC或者pci_mmcfg_read走 MMCONFIG取决于系统使用的是哪种访问方式。现代系统基本都是 MMCONFIG因为速度快、支持 PCIe 扩展配置空间而且 ACPI 表已经提前把 MMCONFIG 的物理地址范围告诉内核了。在pci_ops-read之下还有一层抽象raw_pci_ops和raw_pci_ext_ops。前者处理 256 字节的标准配置空间后者处理 4096 字节的 PCIe 扩展配置空间。lspci 的-xxx参数会触发读取到 256 字节之外如果只看到前 64 字节PCI 配置头通常就是配置空间读取被某种机制截断了——这个问题在虚拟化环境中特别常见。3.3 设备模型的注册与 file_operations 挂钩内核里每次扫描发现一个新设备后会调用pci_device_add()把设备加入 PCI 总线的设备链表然后device_add()把它注册到 Linux 设备模型核心。这个过程中会创建对应的 sysfs 目录和属性文件。PCI 设备在 sysfs 下有几个重要的属性文件它们的打开和读取操作分别对应不同的内核函数。举几个例子读取/sys/bus/pci/devices/0000:3b:00.0/vendor或/device对应的是show_pci_vendor()、show_pci_device()这类属性回调直接从struct pci_dev-vendor和-device字段取数据这两个字段在扫描时就已经从配置空间读出来存放在内存里了。读取config属性走的是pci_read_config()路径会实际调用底层pci_ops去访问硬件配置空间。读取resource属性对应的是资源报告逻辑把设备的 BARBase Address Register信息转换成文本格式输出。这些就是打开/sys/bus/pci/devices/0000:3b:00.0/resource后走的函数路径。每次打开文件时VFS 层会根据 inode 里记录的file_operations调用对应的 open 方法随后每次 read 调用也会对应到file_operations-read。PCI 设备 sysfs 属性标准实现里struct file_operations通常是这样挂钩的static const struct file_operations sysfs_pci_device_fops { .read sysfs_pci_device_read, .open sysfs_pci_device_open, .release sysfs_pci_device_release, };用 VFS 视角来看open(/sys/bus/pci/devices/0000:3b:00.0/config, O_RDONLY)首先走到内核 VFS 层通过路径查找定位 inode然后调用该 inode 上注册的file_operations。inode 是在文件系统填充时fill_super/sysfs_readdir阶段就绑定好回调的。这里有一个经常被忽略的机制sysfs 目录项和属性文件是在设备注册时由device_add()统一创建的属性文件里的file_operations指针则来自struct attribute-attr.ops。PCI 子系统的属性文件用的是统一的 PCI 属性操作集合所以你在用户态看到的每个文件背后都挂着一个内核函数。到这里用户态和内核态已经接上了lspci 打开文件读东西 → VFS 定位到对应的 inode → 通过file_operations进入 PCI 子系统 → 从struct pci_dev拿内存中的缓存的设备信息或者直接访问硬件配置空间。整条链路清晰、完整。3.4 资源分配与 BAR 的来龙去脉除了基础 ID 信息和配置空间lspci 输出里还有一类关键数据设备资源BAR 地址、范围、类型。比如lspci -v会显示Memory at 3b000000 (64-bit, prefetchable) [size16M]这些信息代表设备在 PCIe 地址空间里的映射位置。BAR 的分配是 PCI 枚举过程中最复杂的部分之一。设备在配置空间的 0x10~0x24 偏移处有一组 BAR 寄存器每个 BAR 定义了一段地址范围。硬件上电时BAR 里的值可能是默认值或者无效值由固件BIOS/ACPI或操作系统负责分配。分配过程是这样的内核向 BAR 写入全 1 值然后读回来根据返回值的低比特位判断 BAR 的类型和大小再根据系统中的可用地址空间分配一个合适的起始地址写回去。这个过程在pci_assign_unassigned_resources()/pci_bus_assign_resources()中实现。对 AI 服务器来说这一块的调试价值极大——GPU 的 BAR 空间动辄 16GB 以上如果系统没有开启大 BAR 支持Above 4G Decoding或者 IOMMU 地址空间不足GPU 的 BAR 就可能分配失败导致设备无法正常使用。这时候 lspci 的输出里会看到资源的 size 异常或者 resource 字段消失配合dmesg | grep -i BAR基本就能定位问题。3.5 PCIe 拓扑与链路状态lspci -t 的数据来源除了设备本身的配置lspci -t可以显示树状的 PCIe 拓扑结构。这个树的构建依据不仅仅是设备枚举还依赖桥设备的扫描关系。每个 PCI-PCI 桥在 PCIe 时代也叫 Root Port 或 Switch Port都有一个subordinate bus number字段标识它管理的次级总线范围。lspci 递归地读取这些信息就能恢复出一棵完整的总线树。拓扑结构对 AI Infra 来说不是摆设。GPU 服务器通常采用 CPU 直连 PCIe 或通过 PCIe Switch 扩展的拓扑GPU 的通信带宽和 NUMA 亲和性直接取决于它在树中的位置。我之前排查过一个案例某个推理服务性能始终不达标最终定位原因就是两张 GPU 挂在同一个 PCIe Switch 下的不同端口上跨 Switch 通信反而更慢把两张卡移到同一个 Root Port 下的两个 Switch 端口之后带宽问题才解决。这些诊断的第一步就是lspci -tv看清楚拓扑。4. 从内核回到终端lspci 的翻译与排版4.1 PCI ID 数据库数字到名字的映射内核把设备信息交给用户态后lspci 还差最后一步把冰冷的十六进制数字翻译成人能看懂的名字。比如vendor0x10de, device0x20b5翻译过来就是 NVIDIA Corporation GA102 [GeForce RTX 3090]。这个翻译依赖一个 ID 数据库通常位于/usr/share/misc/pci.ids或/usr/share/hwdata/pci.ids。pci.ids 数据库由系统维护内容涵盖所有已知的 PCI 厂商、设备、子系统、类别编码。lspci 启动时用pci_lookup_name()逐个查询这个函数内部会加载 ID 数据库建立哈希索引然后做快速查找。如果数据库过旧或者文件缺失lspci 就会退化为只显示十六进制 ID——比如10de:20b5虽然也能看但体验就差远了。这里有个实用技巧服务器上如果发现 lspci 显示不出来设备名先检查/usr/share/misc/pci.ids是否存在以及版本是否够新。AI 服务器上经常会遇到新发布的 GPU 型号老版本的 pci.ids 查不到 ID就会显示成 “Unknown device”。这种情况不代表硬件有问题更新hwdata/pciutils包即可。4.2 -v / -vv / -xxx 的区别读取深度逐级加深lspci 的详细度参数不是简单的显示美化而是对应不同深度的数据读取lspci只读基本的配置头vendor、device、class、prog-if数据全部来自 sysfs 的 vendor、device、class 属性文件不额外访问硬件。lspci -v增加读取 BAR、中断线、能力链表capabilities、如 MSI-X、Power Management、Express Capability等信息。这些来自系统启动时的枚举结果已缓存在struct pci_dev中一般不需要重新访问硬件。lspci -vv进一步读取更详细的能力信息比如链路状态LinkCap、LinkSta、错误状态AER 寄存器。部分信息会触发对配置空间的即时读取。lspci -xxx/-xxxx逐字节 dump 配置空间。这是真正的硬件级访问每次都会通过内核pci_ops去读硬件寄存器开销最大但对调试最有用。实际操作中我排查问题最常用的组合是lspci -vvv -s 3b:00.0查看指定设备的完整配置空间详细信息。GPU 驱动加载失败的时候重点看这几项Kernel driver in use 字段是否为空、Kernel modules 是否列出了 nvidia 或 amdgpu、LinkCap 里的 Max Link Width 和速度是否符合预期比如 16 GT/s x16、Current Link Speed/Width 是否降级了。4.3 输出格式的“幕后开关”为什么有时候设备名后面带 [hex:hex]lspci 输出里经常看到设备后面挂着方括号比如NVIDIA Corporation GA102 [GeForce RTX 3090]。方括号里的内容是从子系统 IDsubvendor/subdevice反查得到的板卡具体型号。原理是PCI 规范允许设备上报子系统厂商和子系统设备号这个信息能精确到具体某块显卡是哪个 OEM 生产的。主板上的网卡、显卡经常用这个字段区分不同版本对排查兼容性问题很有用。更隐蔽的细节是lspci输出的设备类别也和内核密切相关。设备类别class code由配置空间偏移 0x09 处的三个字节决定base class、sub class、prog-if。内核在扫描时会把这三个值组合换算成标准的 PCI class 编号lspci 再去数据库里查对应的名字。GPU 显示成 “VGA compatible controller” 还是 “3D controller”取决于设备的 class code 怎么写的。大多数 NVIDIA GPU 会写 VGA class所以 lspci 显示成 VGA compatible controller——这不是错误反而是正常现象。5. 实战排查lspci 相关的典型问题和经验5.1 lspci 卡住、无输出或者输出不完整先列几个我实际遇到过的 lspci 故障场景和解决思路现象可能原因排查手段lspci 命令卡住不动CtrlC 都没响应sysfs 访问阻塞可能和驱动 bug 或 PCIe 链路异常有关另开终端看 dmesg检查是否有 pcieport 报错用 strace 跟踪 lspci 停在哪个系统调用上lspci 有输出但设备数量不足部分设备未被内核扫描到检查 dmesg 是否有 Skipping 相关日志看 BIOS 是否禁用了某些槽位确认是否是热插拔设备没触发 scan设备在 lspci 里能看到但全显示 Unknown devicepci.ids 数据库缺失或过旧更新 pciutils/hwdata 包可以临时用lspci -n先拿 IDlspci 显示设备但在 /dev 下找不到对应节点设备驱动未加载或驱动加载失败检查 Kernel driver in use 字段、dmesg 驱动加载日志、模块依赖设备显示 Memory at [disabled]BAR 资源分配失败dmesg 查 BAR 相关告警检查 BIOS 的 Above 4G Decoding 是否开启看 IOMMU 配置有个很典型的踩坑案例某次在 K8s 节点上排查 GPU 调度失败节点上nvidia-smi完全看不到 GPU但lspci能看到设备。继续用lspci -v看发现 Kernel driver in use 字段是空的说明 NVIDIA 驱动没有绑定到设备。再查 dmesg发现是 GSP 固件加载失败。定位手段就是从 lspci 开始一级一级往下钻——先确认设备存在再确认驱动绑定再排查固件和模块问题。另一个典型案例是 GPU 的 BAR 分配失败。某台服务器插了两张 A100lspci 显示正常但驱动加载报 insufficient resources 错误。查 lspci -v 发现 GPU 的 prefetchable BAR 大小是 16GB而系统的 64-bit BAR 空间因为 BIOS 没开 Above 4G Decoding也叫 Resizable BAR / 4G above decoding只剩 8GB 可用。进 BIOS 开启后再启动问题解决。这个坑在老的 BIOS 版本上非常常见特别是任何涉及大容量显存的 GPU 服务器都建议默认检查 BIOS 里这个选项。5.2 GPU 透传和虚拟化场景的 lspci 异常搞虚拟化和容器场景时lspci 的行为会有一些微妙差异。最典型的是 SR-IOV。启用 SR-IOV 的物理功能PF设备在宿主机上 lspci 能看到但它创建的虚拟功能VF设备默认不一定显示。比如 Intel 的网卡创建 VF 后需要lspci -d 8086:配合过滤才能看到带[Virtual Function]后缀的设备条目。GPU 透传VFIO passthrough场景下lspci 输出通常会显示Kernel driver in use: vfio-pci。如果看到的是nvidia而不是vfio-pci说明设备还绑在原驱动上透传配置有问题。另外当设备被 VFIO 接管后lspci -v 里可能看不到完整的 BAR 信息因为内核已经把这些资源归属到 vfio 组的管理范围里了。这是正常现象不是设备坏了。从内核实现角度看VFIO 会通过vfio-pci驱动接管设备并注册自己的 sysfs 属性。/sys/bus/pci/devices/0000:xx:00.0/vfio_pci目录下的配置信息会覆盖默认的设备属性展示。所以如果你在虚拟机里跑 lspci看到的输出和宿主机上的有所不同不用大惊小怪那是虚拟化层对设备模型的重新呈现。5.3 我踩过的一些坑和个人的经验心得写到这里分享几个我个人的习惯和教训。第一个习惯任何 AI 服务器上机后跑 GPU 之前一定会做一次完整巡检命令就是lspci -tv加lspci -vvv | grep -E LnkCap|LnkSta。前者看拓扑是否正确后者看每张卡的链路宽度和速度有没有降级。PCIe 链路降级是 GPU 服务器常见的隐性故障接口金手指氧化、插槽灰尘、固定螺丝没拧紧、转接线质量差都可能导致链路从 x16 降到 x8 或者从 Gen4 降到 Gen3。表面上看设备都在实际训练吞吐打个七八折不仔细查根本发现不了。第二个习惯排查问题前先执行lspci -nn拿设备的供应商和设备 ID 原文。截图给别人看时这个名字会更有参考价值也方便直接查驱动支持和厂商文档。格式像10de:20b5这种一眼就能对应到具体的芯片型号。第三个教训不要完全依赖 lspci 判断设备健康状态。lspci 能显示设备只说明 PCI 枚举成功了、设备响应了配置空间读取。这距离“设备完全健康”还有相当距离。我曾经碰到一张 GPUlspci -v 一切正常但跑 CUDA 程序直接报 no kernel image is available for execution on the device最后定位是 GPU 显存颗粒损坏触发了 ECC 错误和 PCI 枚举一点关系都没有。所以 lspci 是排查的第一步但绝不应该是最后一步。第四个技巧学会用lspci -k快速查看设备绑定的驱动。这个参数会列出每个设备当前使用的内核驱动以及内核支持的驱动模块列表。排查驱动绑定问题的时候这个命令比lsmod直观得多因为它直接对应到了具体的 PCI 设备。如果再往深了说PCI 子系统的调试其实还有不少高级玩法注册pci_driver时的匹配逻辑、ACPI 表对 PCI 资源的覆盖、P2PPeer-to-Peer通信的 DMA 映射、ACS 隔离对虚拟化的影响、IOMMU 地址转换对 BAR 重映射的影响……这些主题每一个展开都能写一篇单独的文章而且都直接关系到 AI Infra 的稳定性。lspci 只是这一切的入口真正值钱的是你对这条链路的理解和排查思路的建立。回到开头那个问题敲下 lspci 的那一瞬间内核里发生了什么答案是内核本来就把一切都准备好了。设备枚举、配置空间读取、资源分配、驱动绑定、sysfs 导出全都在系统启动阶段就完成了。lspci 做的只是把这些成果翻译成人类可读的语言。理解了这一点你就知道——排查问题的关键不在于 lspci 这个命令本身而在于你知道它背后那套完整的 PCI 子系统机制。每次敲下回车你其实是在向内核要一份已经被精心整理过的硬件清单而这份清单的质量取决于内核枚举阶段是否一切顺利。所以下次你在机房敲下lspci | grep -i nvidia的时候不妨多看一眼输出里的每一列多想想那些数字和状态位的含义。很多时候问题就藏在你每天都看却从未细看的那几行文本里。
返回列表