Linux识别GPU的完整链路:从PCI枚举到驱动Probe的排查指南
每次遇到“Linux 没认出 GPU”这类问题我的第一反应都不是急着装驱动而是先确认内核走到了 PCI 枚举、驱动匹配、probe 这条链路的哪一环。因为同样一句话背后可能藏着完全不同的故障lspci里没有设备和lspci里有设备但nvidia-smi报错根本不是同一个问题。我处理过不少 GPU 服务器案例也见过开发者在自己的工作站上折腾好几天。最常见的场景是插了一张显卡系统能启动lspci也能看到设备但驱动就是加载不上或者驱动模块已经在列表里却始终没有真正 probe 成功。很多人这时候会开始反复重装驱动可结果往往不变。问题不是出在驱动本身而是“Linux 认出 GPU”这件事本身就不是一个瞬间而是一条有顺序、有依赖关系的链路。从 PCI 枚举到驱动 probe中间没有任何一步可以跳过。这篇文章想把这条链路拆开讲清楚。你会看到 Linux 到底在什么时候发现设备、什么时候决定它是 GPU、什么时候给它的地址空间腾位置、什么时候把驱动和硬件绑定在一起。也会明白为什么很多“装不上驱动”的问题其实卡在很前面的资源分配环节。1. 先把“认出 GPU”拆成三层不然排查方向一开始就是错的1.1 “能用”和“驱动加载成功”不是一回事很多人说“Linux 认出 GPU”其实心里想的是“这台机器能拿 GPU 跑计算”。但在 Linux 内部从硬件插入到用户态能用至少隔着三层。第一层是 PCI 枚举层。内核通过 PCI 总线扫描发现某个 bus 上存在一个设备读取到它的 Vendor ID、Device ID、Class Code 等信息。这一层完成之后lspci能看到设备。注意这一层只是“知道它存在”还不关心它到底是不是 GPU也不关心驱动是否匹配。第二层是驱动绑定层。内核会为这个 PCI 设备寻找合适的驱动如果找到了就调用驱动的probe函数。probe函数返回成功后驱动才算正式接管设备。到这一层/sys/bus/pci/devices/.../driver里会显示驱动的名字。第三层是用户态接口层。驱动 probe 成功后还要注册字符设备、DRM 设备、渲染节点等最终表现为/dev/nvidia0、/sys/class/drm/card0或者amdgpu相关节点用户态工具如nvidia-smi才能工作。“能用”一定要求三层都通“lspci能看到”只代表第一层通了。把这三层混在一起是排查 GPU 问题时最常见的思维陷阱。1.2 三层判断对应三类完全不同的故障如果用一句话描述问题要尽量说清楚是哪一层断了。下面这三种说法排查方向完全不同“lspci里根本没有这个设备”问题大概率在第一层也就是 PCI 枚举之前。可能是物理插槽、供电、UEFI/BIOS 设置、PCIe 链路训练失败也可能在内核启动参数里加了某些限制。这一阶段和驱动没有关系重装驱动完全没用。“lspci能看到设备但驱动列表为空”问题可能出在驱动匹配层。要么内核没有编译对应驱动要么驱动模块没有加载到当前内核要么设备的 ID 不在驱动支持列表里要么模块被 blacklist 了。“驱动已经绑定但nvidia-smi/渲染节点不可用”问题则可能出在 probe 后半段或用户态接口注册环节。驱动可能加载了但在 initialize 过程中失败或者因为权限、设备节点、容器隔离等外部原因没有把接口暴露出来。所以我的建议是遇到问题先不要执行“重装驱动”这条路径。先跑几条只读命令把问题的层级定位下来再决定下一步干什么。定位层级比下载新版驱动更有价值因为绝大多数 GPU 相关故障驱动版本并不是真正根因。2. 六步链路Linux 从 PCI 枚举到驱动 probe 到底做了什么2.1 第一步PCI 枚举从扫描总线和设备ID开始Linux 启动早期PCI 子系统会从 Host Bridge 开始扫描总线。系统先访问 bus 0 上的配置空间发现可能存在 PCIe Root Port然后继续往下扫描逐级枚举每一级 PCIe Switch/Bridge 后面的设备。这个过程不需要 GPU 驱动参与它属于 PCI 核心的基础能力。扫描完成后每个 PCI 设备都会被加入内核的设备模型。你可以通过lspci看到它们lspci -nn输出里会出现类似VGA compatible controller [0300]这样的行。方括号里的[0300]是设备类别后面还会显示 Vendor ID 和 Device ID。这一步代表 Linux 已经看到了硬件但还没有做任何驱动绑定。如果这一层就断了比如lspci里空无一物或者只能看到主桥却看不到下游设备问题通常出现在硬件链路和平台初始化上。可以优先排查物理连接、槽位、辅助供电、UEFI 里的 PCIe 相关选项而不是去检查驱动。2.2 第二步识别设备类型判断它是不是GPUPCI 设备配置空间里有 Class Code 字段用来告诉内核“我是什么种类的设备”。当 Class Code 的主类为0x03时代表显示控制器子类进一步区分 VGA 兼容设备、3D 控制器或非 VGA 显示设备。GPU 和网卡、NVMe 硬盘不一样。内核拿到设备后会把这个 Class Code 和 Vendor ID、Device ID 一起维护在设备模型里。后续 udev、驱动匹配、用户态工具都会参考这些信息。值得强调的是“内核知道它是 GPU”和“内核知道该用哪个驱动”还是两件事。Class Code 只解决分类问题真正的驱动匹配要到第四步才发生。很多人会在这个环节误判以为看到设备类别是 VGA就说明某个驱动应该自动加载。实际上驱动加载还需要一个更精确的匹配过程。2.3 第三步分配BAR空间先让设备能“被访问”这是整条链路里最容易被忽略、也最容易出问题的一步。PCI 设备要通过 Base Address RegisterBAR向系统请求一段地址空间。GPU 的显存、控制寄存器、MMIO 映射都需要通过 BAR 暴露给 CPU 和内核。在 x86 平台上设备通电后通常由 UEFI/BIOS 在启动阶段完成 BAR 空间分配。内核在某些情况下会调用pci_assign_unassigned_resources()或启动参数触发重新分配。如果你的平台是 ARM64、或者启用了 PCIe Resizable BAR、或者插了多张 GPU这一步就可能变得很不可控。分配完成后你可以查看设备当前的资源lspci -vvv -s 01:00.0或者直接看 sysfscat /sys/bus/pci/devices/0000:01:00.0/resource如果资源显示全 0说明 BAR 空间没有分配成功。这个时候GPU 驱动就算已经匹配probe 也大概率会失败因为驱动根本没有办法访问设备。更让人迷惑的是这个阶段往往还没有进入驱动加载阶段所以日志里可能看不到和显卡厂商相关的错误只有 PCI 核心在报资源不足。2.4 第四步寻找匹配驱动本质上是一次键值配对Linux 驱动模型里PCI 设备与驱动之间的匹配遵循一条非常简单直观的规则驱动通过id_table声明自己支持哪些 Vendor ID Device ID 组合PCI 核心再和设备配置空间里的 ID 比较。匹配成功才可能把驱动绑定到设备上。我们可以用lspci -nnk查看某设备当前是否已经绑定驱动lspci -nnk -s 01:00.0输出中的Kernel driver in use会告诉你当前驱动是谁Kernel modules则会列出内核内置了哪些可能支持的模块。如果这两项都是空的通常说明当前系统没有匹配的驱动可用。实际项目里这个环节最容易出问题的地方有两个。一个是模块没有加载。驱动编译好了也安装到系统里了但 initramfs 里没把它打进去或者模块被 blacklist 拦住了。重启之后驱动模块根本不在内存里自然谈不上匹配。另一个是设备 ID 太新。新发布的 GPU 采用了新 Device ID老版本驱动内核模块的id_table里没有这个 ID驱动更新前都不会去匹配。不少“新卡装旧驱动后不识别”的现象本质上就是这一步没有通过。2.5 第五步执行 probe驱动正式接管硬件匹配成功之后PCI 核心会调用驱动注册时填写的probe回调函数。这个函数是驱动真正开始初始化设备的地方。驱动会检查设备状态、请求并映射 BAR 对应的内存资源、注册中断处理函数、初始化内部子模块最后把设备标记为已驱动。很多看似“已经识别”其实“无法使用”的问题都会在 probe 阶段暴露。例如驱动尝试读取 GPU 的配置寄存器时失败了或请求 IRQ 时资源冲突又或者显存初始化不成功probe函数都可能直接返回错误。在日志里这一阶段通常会出现厂商相关的关键信息。你可以用一条命令集中查看sudo dmesg | grep -i -E pci|nvidia|amdgpu|nouveau|error|fail不要把目光只停留在error上。更可靠的判断方式是看设备是否被绑定以及 probe 到底有没有返回成功。很多时候驱动会尝试 load但最终失败然后回滚。如果你只看到“loaded”而没看到后续的初始化完成提示就已经说明 probe 链路没有走完。2.6 第六步注册用户态接口GPU 才真正可用probe 成功不代表用户在应用层马上就能用。GPU 驱动还需要向用户态暴露设备节点、渲染节点或管理接口。NVIDIA 闭源驱动会注册/dev/nvidiactl、/dev/nvidia0等节点AMD 的开源驱动通常通过 DRM 子系统暴露/dev/dri/card0和/dev/dri/renderD128。这一步完成后nvidia-smi或ls /sys/class/drm/才会看到可用信息。如果驱动绑定成功但用户态工具还是报错就要检查设备节点的权限、容器是否缺少宿主机设备映射、/dev目录是否被 udev 正确生成等。所以从 PCI 枚举到用户态工具能工作至少需要经过这六步。每一步的结果都依赖上一步。单独看任何一条命令都无法覆盖全程比如lspci只能证明前几步完成nvidia-smi能跑通则证明整条链路基本是通的。3. 为什么很多“装不上驱动”的问题其实卡在资源分配3.1 一个常见误判重装驱动解决不了“地址窗口不够”在排查 GPU 问题的过程中有一个现象很典型用户说“驱动装不上”但拿过日志一看问题根本不是驱动而是内核或者固件没有给 GPU 分到可用的 MMIO 空间。GPU 和普通 PCIe 设备不一样它的显存有可能占据几十 GB 的地址空间。服务器主板经过 UEFI 分配后通常能保证单张 GPU 正常工作。但如果插了两张、三张卡或者板上还插着大量 NVMe/网卡等需要 MMIO 的设备PCIe Root Port/Bridge 的窗口可能就不够用了。某些 PCIe Bridge 的地址窗口是固定的无法自动扩展到能覆盖 GPU 所有 BAR 的范围。这时候你在dmesg里可能看到这样的信息设备 BAR 无法分配、地址范围冲突、或 PCI bridge 窗口资源不足。很多人看到这些信息仍然不会往“资源分配”方向想而是继续重装驱动因为报错里没有直接出现显卡厂商的名字。错就错在这里驱动安装得再干净设备地址空间这一步不通后面所有工作都无法展开。要验证这类问题可以先看lspci -vvv中对应设备的Region是否都有实际地址再看对应 PCIe Bridge 的 BUS 和 MMIO 窗口。多 GPU 场景下还可以比较两张卡识别后的资源分布看看是不是第二张卡完全没有拿到地址。3.2 现代GPU的大BAR需求把资源分配问题放大了过去只要开启 PCIe Resizable BAR或者在 UEFI 中打开“Above 4G Decoding”很多单卡问题就能解决。但现在 GPU 显存越来越大单张卡请求的 BAR 空间能到 16GB、24GB 甚至更高这让资源分配问题变得更加敏感。如果主板 UEFI 没有正确开启 Above 4G Decoding32 位地址空间很快就可能耗尽。尤其是 x86 平台同时要映射很多 PCIe 设备的 MMIO32 位窗口本身就不够用。启动时这类设备可能只被分配部分 BAR或者干脆无法分配预取窗口。所以遇到多卡识别、新卡识别异常时先到 UEFI/BIOS 里确认PCIe Resizable BAR 是否需要打开Above 4G Decoding / Memory Mapped I/O above 4GB 是否开启有没有对 PCIe Slot 做带宽或链路宽度限制。这些设置看似和“驱动”离得很远但它们直接决定资源分配环节能不能成功。真实生产环境里我见过不少案例改完 UEFI 选项后系统无需重装驱动GPU 就能被完整识别。这说明很多问题不是驱动问题而是地址空间的“地基”没打好。3.3 IOMMU和虚拟化场景下资源问题会更晚暴露如果你的 GPU 要用于 PCIe passthrough也就是直通给虚拟机使用那还需要额外关注 IOMMU 分组和中断重映射。IOMMU 会在设备与物理内存之间做地址翻译因此地址空间问题的表现会更隐蔽。常见的情况是宿主机上看设备正常驱动也绑定了但虚拟机里看不到完整显存或者 GPU workload 跑起来后报 DMA 错误。这类问题往往不只在 Linux PCI 分配层面还涉及 IOMMU 页表、PCIe ACS 拓扑、中断路由等。对绝大多数只做单机 GPU 开发的人我的建议是不要一开始就上虚拟化直通。先在宿主机上把 PCI 枚举、资源分配、驱动绑定这三级全部跑通再用最小配置验证直通。跳过前面任何一层后面出了问题会非常难定位。4. 从“被认出来”到“稳定使用”还差几块工程拼图4.1 模块冲突、加载顺序和initramfs即便六步链路在原理上通了实际工程系统还会加入很多变量。NVIDIA 的开源驱动nouveau和闭源驱动不能同时绑定同一张 GPU。不少发行版默认加载nouveau如果你需要装闭源驱动通常要先通过/etc/modprobe.d/里的配置文件 blacklist 掉它。模块加载顺序同样重要。某些驱动需要在 GPU 设备出现前预留好内存比如 IOMMU、DMA 池、显存预留等参数。这些参数如果写在 modprobe 配置里但 initramfs 没有同步更新重启后依然不会生效。因此修改了 modprobe 配置后不要只在当前系统里测试模块还要重新生成 initramfs才能让下一次开机后的状态一致。常见命令结构大致是sudo modprobe amdgpu但这句话只能说明当前内核在尝试加载模块说明不了重启后也会加载。生产环境里我一般会先确认模块是否已在 initramfs 中lsinitramfs /boot/initrd.img-$(uname -r) | grep -E nvidia|amdgpu|nouveau不同发行版命令不一样但思维是一样的真正要查的是“重启后内核能不能在 probe 之前拿到驱动”。4.2 Secure Boot、DKMS与内核升级内核模块和普通用户态程序不同它运行在内核空间很多系统会要求模块附带签名。UEFI Secure Boot 开启时未签名或签名不匹配的模块会被拒绝加载。这时你lsmod看不到模块dmesg里可能会有模块加载被拒绝的记录但很多用户不会把问题往签名上想。DKMS 的用途是解决内核升级后的模块重建问题。它会在新内核安装后重新编译第三方驱动模块。只要内核源码和构建工具链没问题DKMS 能让模块跟随内核升级。但如果 DKMS 没有正确执行或者模块签名流程没有覆盖 DKMS 生成的新模块某个内核版本有可能突然失去 GPU 驱动。所以长期维护 GPU 机器不能只把驱动安装脚本跑一遍就结束。要记录当前内核版本、DKMS 状态、模块签名机制。当你执行uname -r后发现内核升级过而 GPU 驱动失效最先要确认的就是新内核是否也包含了对应模块。4.3 多卡、容器和虚拟化里的“再次识别”单卡跑通之后多卡和容器场景又会出现新的“识别”问题。nvidia-smi里能看到 GPU但容器里看不到这是因为容器需要额外的 runtime hook 来把宿主机驱动和设备节点注入容器。nvidia-smi在容器里能不能执行和宿主机驱动是否加载并不等价。容器场景下真正重要的是驱动版本、CUDA 用户态库版本、设备节点权限以及容器运行时是否能找到 GPU 设备。很多人会误以为“容器里没有 nvidia-smi 就是驱动没装”其实是容器没有继承宿主机设备路径。多卡场景则要注意总线地址和设备编号的对应关系。/dev/nvidia0、/dev/nvidia1的顺序并不一定和物理插槽顺序一致。如果你要固定某张卡给某个容器或进程最好使用 UUID 或 PCI bus ID 来选择设备而不是靠设备编号。4.4 运维视角先建立可观察性一台 GPU 机器长期运行最怕的是哪次重启之后一切正常但性能下降或者 GPU 没有被 NUMA 正确识别。要少踩这类坑建议一开始就建立几条固定命令作为巡检基线。我最常看的是lspci -nnk | grep -A3 -i vga这个命令能快速看到有哪些显示设备以及它们分别绑定了哪个驱动。再看 dmesg 里有没有 PCI 资源相关的错误。最后记录nvidia-smi -q或/sys/class/drm/card*/device/里的设备拓扑。这样下次变更后对比一下基线就能定位是哪一步变了。不要等到 GPU 工具全部不可用了才去查。中断的链路往往早就埋下了隐患只是用户态工具还在继续工作。建立可观察性是长期维护 GPU 环境最值得做的一件事。5. 现场排查链路按五层顺序定位而不是反复重装驱动5.1 先看现象和日志遇到 GPU 识别异常第一步不要改任何配置。先完整记录现象是开机后屏幕无输出还是服务器能 SSH但nvidia-smi报错是lspci里完全没有设备还是设备存在但渲染节点没有生成不同现象指向不同层级。屏幕无输出可能涉及显示输出、UEFI GOP、驱动 framebuffer 等而计算卡场景往往根本不关心显示输出。所以“没有画面”和“计算卡没有出现在系统里”是两类需要分开排查的问题。记录现象的同时立刻收集 dmesg 和 lspci 输出。如果问题在启动阶段出现可能还要看上一次启动的日志。日志是判断链路断点最重要的依据。5.2 再查设备与资源先执行lspci -nn确认设备是否存在。如果存在再看lspci -vvv -s bus:device.function在输出中注意Region、BAR、Kernel driver in use、Kernel modules等段落。如果设备没有拿到地址Region会显示为 0如果驱动已经绑定Kernel driver in use不会为空。资源分配问题的排查路径是看设备 BAR 是否都被分配了非零地址。看上游 PCIe Bridge 窗口是否覆盖了这些 BAR。看 UEFI/BIOS 里是否启用了 Above 4G Decoding 或 Resizable BAR。看内核启动日志中是否有cant allocate mem resource或类似关键词。如果发现资源分配失败优先处理平台/UEFI 设置而不是重新安装驱动。5.3 再查驱动匹配与probe当lspci -nnk显示Kernel modules为空说明当前内核没有能匹配的驱动。这时可以查设备 ID 是否在驱动的支持列表内。例如查看模块信息modinfo nvidia如果模块没有安装系统里就根本不会出现这个模块。如果模块已经安装但没有被加载先看是不是被 blacklist再看当前内核版本的模块目录里有没有对应的.ko文件。如果驱动已经匹配但 probe 不成功dmesg里通常会有更多信息。有些 GPU 驱动会把失败原因分成详细字段直接定位到硬件初始化或显存初始化的具体环节。此时除了驱动日志还可以临时加内核日志等级或按模块文档开启动态调试而不是盲目更换驱动版本。5.4 最小化验证与变更隔离最有效的定位方法不是在生产环境上不断尝试而是先做一个最小化验证环境。如果机器上有多张 GPU先只保留一张如果有多套驱动配置先恢复默认内核参数如果启用了容器或虚拟化先回到宿主机直接测试。最小化验证的意义在于把变量数量降到最低。做完一次变更后重新检查lspci -nnk、资源分配和dmesg。如果问题消失就能说明是某个变更或环境因素导致的。如果问题仍在至少证明核心链路本身有问题。生产环境里很多复杂故障最后都被证明是多个因素叠加的结果。比如 UEFI 设置导致资源紧张同时容器 runtime 配置错误又叠加了内核升级后 DKMS 失败。单独看每个因素都不致命但混在一起就让人误以为必须靠“重装驱动”解决。5.5 一张对照表收束排查方向下面这张表可以帮助你快速建立问题与排查方向之间的映射现象最可能断掉的环节优先检查项lspci看不到设备PCI 枚举之前物理插槽、供电、UEFI/BIOS、PCIe 链路训练lspci能看到设备但Kernel modules为空驱动匹配设备 ID 是否被支持、模块是否安装、blacklist模块存在但无法加载模块加载Secure Boot、DKMS、initramfs、依赖模块缺失设备绑定驱动但nvidia-smi不可用probe 或用户态接口dmesg、设备节点、权限、容器映射多卡只有部分卡可用资源分配PCIe Bridge 窗口、Above 4G、物理槽位、BAR虚拟机直通后 GPU 异常IOMMU/中断IOMMU 分组、ACS、直通配置、中断映射这张表不要求覆盖所有情况但它能让你在动手前先想清楚自己面对的究竟是哪一层的问题。定位到层再去找具体命令和修复方案远比随机尝试高效。6. 真正值得长期关注的是这条链路而不是某个驱动如果你只记住一个结论我希望是这句话Linux 认出 GPU 不是“驱动安装成功”这个单一结果而是一条从 PCI 枚举、BAR 分配、驱动匹配、probe 到用户态接口逐级打通的过程。理解这条链路短期能帮你少走弯路。比如看到lspci没有设备时不会去重装驱动看到nvidia-smi不可用时会先看驱动是否真的绑定了设备看到多卡资源不足时会想到 UEFI 设置而不是厂商驱动 bug。长期来看这条链路还能帮你在平台迁移、内核升级、容器化、虚拟化直通这些更复杂的场景里保持清晰的判断力。GPU 驱动的安装方式可能会随着发行版和内核版本变化但设备模型和 probe 机制不会轻易改变。底层机制稳定上层工具一直在变值得花时间掌握的永远是下层。下次再遇到 GPU 机器不认卡可以先按下重装驱动的念头。打开终端看看lspci -nnk看看dmesg看看设备的 BAR 资源到底有没有被分配。从 PCI 枚举走到驱动 probe每一步都确认一遍你会发现自己很快就能找到真正断掉的那一环。