lspci排查指南:从AMD/ATI厂商名到Kernel modules驱动加载原理
前段时间在群里看到一条求助内容很典型有人在自己的服务器上敲了lspci | grep -i amd等了几秒屏幕干干净净零输出。他的第一反应是“lspci 是不是坏了”后来我把他的完整输出要过来扫了一眼发现机器上明明有显卡但那一行描述里只有ATI Technologies Inc整行根本没有AMD这三个字母。这个小小的乌龙倒让我认真把 lspci 里Kernel modules字段的信息来源翻了个透中间也借助 GLM-4.7 做了交叉验证。整理成这篇笔记希望对做驱动加载排查、PCI 设备管理和内核模块开发的朋友都有帮助。1. 先回答“grep amd 无反应”lspci 到底在输出什么1.1 不是命令坏了而是文本里确实没有 amd很多教程都教你用lspci | grep amd或类似命令去查显卡但这个做法有个隐藏前提设备名称里得真有AMD字样。以现代显卡为例常见输出是29:00.0 VGA compatible controller: Advanced Micro Devices, Inc. [AMD/ATI] Navi 23 [Radeon RX 6600] (rev c7)这一行里有AMDgrep -i amd能匹配。但如果你用的是老卡比如 Radeon X1300lspci 经过 pci.ids 厂商数据库翻译后的名字是01:00.0 VGA compatible controller: ATI Technologies Inc RV515 [Radeon X1300]整行只有一个ATI和一个Radeon唯独没有AMD。grep 是纯文本过滤工具它不知道“ATI 是 AMD 前身”这层历史知识自然就没有输出。所以遇到这种情况先别怀疑工具直接敲lspci | head -n 40看全量输出比瞎猜快得多。1.2 更稳的过滤方式按 vendor ID 而不是按厂商名厂商名的翻译依赖 pci.ids 数据库数据库里写什么你就看到什么老条目还停留在 ATI 时代。好在每张 PCI 卡还有一个稳定不变的 vendor ID。AMD/ATI 的 vendor ID 是0x1002从几十年前到现在都没变过。所以准确做法是lspci -nn | grep -i 1002-nn会让每个设备都带上[1002:xxxx]这样的编号前四位是 vendor后四位是 device。我常用的组合是lspci -nn | grep -iE 1002|amd|ati这样既不会漏掉老 ATI 卡也不会被新旧厂商名差异坑到。记住一个原则厂商名字是给人看的vendor ID 才是给机器和 grep 用的。1.3 顺手确认 lspci 的数据通道是否正常如果lspci本身就是空的那才需要查环境。现代 pciutils 默认从/sys/bus/pci/devices/读设备信息某些容器、虚拟机或受限环境没有暴露这个目录lspci 就会输出空或者只有极少数虚拟设备。可以做两个检查ls /sys/bus/pci/devices/ | head ls /proc/bus/pci/ 2/dev/null || echo no proc pcisysfs 里如果连设备目录都没有lspci 自然没数据grep 也就成了空转。还有一类情况是 pci.ids 文件缺失或版本过旧厂商名显示成Unknown device这时候 grep 厂商名必然失败。正确做法是update-pciids更新数据库或者干脆用 ID 过滤。2. lspci -k 的两个字段当前驱动和候选模块不是一回事2.1 一个典型的 -k 输出很多人知道 lspci 能显示设备列表但没细看-k参数加出来的信息。实际输出像这样# lspci -k -s 29:00.0 29:00.0 VGA compatible controller: Advanced Micro Devices, Inc. [AMD/ATI] Navi 23 [Radeon RX 6600] (rev c7) Subsystem: Gigabyte Technology Co., Ltd. Navi 23 [Radeon RX 6600] Kernel driver in use: amdgpu Kernel modules: amdgpu其中的Kernel driver in use和Kernel modules是最容易被搞混的两个字段。前者表示“现在正在为这个设备服务的内核驱动是谁”是一个当前状态后者表示“内核认为有哪些驱动模块可以驱动这个设备”是一张候选名单。很多机器上Kernel modules会同时列出radeon和amdgpu两个模块但Kernel driver in use最终只会是其中一个。拿现实打比方Kernel driver in use是“当前坐在这个工位上干活的人”Kernel modules是“hr 系统里有资格坐这个工位的所有候选人名单”。候选人没被录用之前名单上写着名字但不代表他已经坐在那里。2.2 driver in use 的真身sysfs 里的一个符号链接想挖Kernel driver in use的底其实非常简单用 readlink 就能看到readlink /sys/bus/pci/devices/0000:29:00.0/driver输出会指向.../bus/pci/drivers/amdgpu这样的路径也就是当前驱动。如果设备还没绑定任何驱动这个 driver 符号链接根本不存在。这里有个特别容易踩的坑driver_override机制。你可以往设备目录下的driver_override文件写入一个模块名比如vfio-pci强制设备不进入常规的驱动匹配逻辑。做 GPU 直通、PCIe passthrough 的人几乎天天用这招。一旦设置了 overridelspci 显示的Kernel driver in use就会是 override 指定的模块而不是按 ID 匹配出来的默认驱动。排查问题的时候如果发现驱动和预期不符先看一眼这个文件cat /sys/bus/pci/devices/0000:29:00.0/driver_override2.3 Kernel modules 候选名单的来源静态字典匹配真正容易让人想歪的是Kernel modules这个字段。它不是从/sys/module/里“当前已加载模块”中挑出来的而是从模块别名数据库里静态匹配出来的。换句话说哪怕某个驱动模块根本没被 modprobe 加载过只要它的 PCI ID 表覆盖了当前设备lspci 也会把它的名字列出来。这个数据库文件一般位于/lib/modules/$(uname -r)/modules.alias想看某个设备的候选模块直接反查这个文件grep -E pci:v00001002d000073FF /lib/modules/$(uname -r)/modules.alias | head一行一个候选格式是“别名模式”加“模块名”。所以 lspci 的Kernel modules字段本质上就是拿当前设备的 vendor、device、子系统、类别信息去 modules.alias 里做一次模式匹配然后把命中的模块名列出来。这个细节弄明白后面很多“为什么没列出来”的问题都能自己找到原因。3. 从驱动源码到 modules.alias 的完整链路3.1 MODULE_DEVICE_TABLE 是链路起点一切要从驱动源码说起。内核模块的作者在代码里声明一个pci_device_id数组再通过宏把它导出让系统知道“我这个驱动支持哪些 PCI 设备”。以网卡驱动为例简化后的代码长这样static const struct pci_device_id igb_pci_tbl[] { { PCI_VDEVICE(INTEL, 0x10D6), board_82576 }, { 0 } }; MODULE_DEVICE_TABLE(pci, igb_pci_tbl);这个宏会把 ID 表写进模块文件的一个专门 section。编译完成后modinfo就能看到一串 aliasmodinfo -F alias igb里面会出现类似pci:v00008086d000010D6sv*sd*bc*sc*i*的条目星号表示“这一项不要求匹配”。这些 alias 就是驱动对外表达的“我能认谁”。模块还没有安装进系统之前它们只是埋在 .ko 文件里的元数据。3.2 depmod把散落的 alias 攒成一本大字典内核镜像装好、模块拷贝进/lib/modules/$(uname -r)/之后真正干粗活的是 depmod。它负责扫描整个模块目录读取每个 .ko 文件里的 alias 数据汇总生成modules.alias、modules.dep等索引文件。运行后你看到的典型条目长这样alias pci:v00001002d000073FFsv00001462sd000073FFbc03sc00i00 amdgpu拆开看每个字段的含义v00001002是 vendor ID 0x1002对应 AMDd000073FF是 device ID 0x73FF对应 Navi 23 核心sv00001462是子系统 vendor ID 0x1462也就是微星sd000073FF是子系统的 device IDbc03sc00i00是 base class 0x03显示控制器、subclass 0x00、interface 0x00注意sv/sd/bc/sc/i这些字段不是必须出现的很多驱动只填 v 和 d后面的一律留星号。反过来说如果一个 OEM 卡改了子系统 ID而驱动 alias 里又严格写了 sv/sd那这张卡就可能不在候选列表里。这是Kernel modules字段让人疑惑的常见原因之一尤其是一些准系统或品牌机独享的非公版卡。3.3 modprobe 和 lspci 共用同一本字典内核在枚举到新 PCI 设备时会在 uevent 里生成类似MODALIASpci:v...d...sv...的字符串udev 拿到后调用 modprobe由 modprobe 去 modules.alias 里反查并加载模块。lspci 的-k同样基于这个映射文件只不过它的输入是 sysfs 里的静态设备 ID输出是候选模块列表。所以你会发现modprobe 能加载的模块和 lspci 列出的模块高度一致因为它们读的是同一份数据。这种“设备 ID 在模块侧声明”的设计有个明显好处设备类型和驱动支持保持解耦。新增一款新硬件只需要写一个模块不再需要动内核本体老模块不认新设备也不会妨碍新驱动正常加载。4. 排查实操Kernel modules 与预期不一致时怎么做4.1 先确认 ID 是在哪一级失配遇到 lspci -k 没有列出你预期的模块时第一步不是怀疑系统而是比对 ID。完整命令组合是lspci -nn -s 29:00.0 grep -E pci:v00001002d000073FF /lib/modules/$(uname -r)/modules.alias如果 grep 能出结果说明映射存在问题可能出在子系统或类别匹配上如果 grep 没有任何结果要么是模块的 alias 没汇聚进来要么是模块根本没装。先用modinfo 模块名验证模块是否存在于当前内核目录再手动重跑一遍 depmoddepmod -a很多 DIY 编译安装模块的朋友都会踩这个坑make install 把 .ko 放进去了但忘了重新生成 modules.alias新模块在模块系统里就是“隐形”的lspci -k 自然看不见它。跑一次 depmod -a 再查往往就好了。4.2 模块存在但没被列出的额外检查blacklist 与签名有些发行版会把某个驱动拉进 blacklist比如在/etc/modprobe.d/下写了blacklist radeon。这不会让模块从 modules.alias 里消失但会影响自动绑定。更隐蔽的是内核签名校验Secure Boot 开启但模块没有签名时dmesg 里会出现module verification failed之类的报错。模块文件存在、alias 也有、但永远加载不进来设备驱动自然就是空的。这三项是我固定会做的检查modinfo 模块名是否正常返回grep -R blacklist /etc/modprobe.d/确认有没有被禁用dmesg | grep -i module verification\|error看加载过程有没有报错4.3 手动绑定三步法如果模块存在、alias 也有、没被 blacklist但就是没绑定那就手动验证modprobe 模块名 echo 0000:29:00.0 /sys/bus/pci/drivers/模块名/bind readlink /sys/bus/pci/devices/0000:29:00.0/driver执行前如果设备已经被别的驱动占着需要先解绑echo 0000:29:00.0 /sys/bus/pci/drivers/旧驱动/unbind绑定成功后再跑lspci -k -s 29:00.0Kernel driver in use会立刻变化。bind 时报错的话先确认设备地址是不是0000:总线号:设备号.功能号的完整格式再确认模块里确实有对应设备的 ID 表别急着怪内核。4.4 把开头那个 AMD 案例完整收口回到开头那个“无反应”问题。如果换用lspci -nn | grep -i 1002能查到设备但grep -i amd无输出那基本是厂商名历史问题如果设备能查到lspci -k -s 地址的Kernel modules里有amdgpu但Kernel driver in use为空那就要重点检查两件事一是 firmwareamdgpu 驱动需要/lib/firmware/amdgpu/目录下的固件文件很多精简系统没装 linux-firmware 包二是 blacklist 配置。我实际处理过的机器里有一半是缺固件装好 linux-firmware 重启就正常。5. 多平台下容易忽略的 pci.ids 与容器细节5.1 Unknown device 与 pci.ids 过期lspci 把十六进制 ID 翻译成厂商名依赖/usr/share/hwdata/pci.ids这类数据库。新卡经常不被旧数据库认识显示成Unknown device [xxxx:xxxx]。更新数据库很方便update-pciids更新后马上再跑lspci就能看到正确名称。写自动化巡检脚本时我建议一律优先用lspci -nn的原始 ID 去匹配而不是匹配厂商名字能少踩很多环境差异的坑。5.2 容器和嵌套虚拟化环境下的坑容器默认没有完整的/sys/bus/pci树lspci 可能输出空列表或者只列出几个虚拟设备。这也会表现为 grep 无反应。判断方法是在宿主机上跑ls /sys/bus/pci/devices/再和容器内对比。做 GPU passthrough 或 SR-IOV 之前务必先在宿主机确认设备存在再决定要不要把 sysfs 暴露进容器。别在容器里折腾半天最后发现设备根本不在这个命名空间里。5.3 我固定使用的几组命令最后整理一个我日常排查 PCI 设备最常用的表想做的事命令列出所有 PCI 设备及候选驱动lspci -nnk查看某个设备的完整属性和 MODALIASudevadm info -q property -p /sys/bus/pci/devices/0000:29:00.0反查某个模块支持哪些 PCI 设备modinfo -F alias 模块名查看 modprobe 别名表里的 AMD 映射modprobe -c | grep -E ^alias pci:v00001002查看当前设备绑定的驱动链readlink /sys/bus/pci/devices/0000:29:00.0/driver这组命令的排序就是我的排查顺序先lspci -nnk看全局再udevadm看设备属性再用modinfo和 modules.alias 反查候选最后才决定要 bind 哪个驱动、要不要写 driver_override。我自己这些年最大的体会是lspci 看起来只是个列表小工具真把它背后的数据来源吃透对驱动加载问题的排查帮助非常大。那次用 GLM-4.7 做交叉验证时我还顺手确认了一条经验与其 grep 厂商名不如 grep vendor ID名字是给人看的ID 才是给机器和 grep 用的。要是你下次再遇到lspci | grep -i amd无反应先别急着怀疑命令坏了lspci -nnk出来看一眼答案大概率就在Kernel modules那一行里。