Linux识别GPU的完整过程:从PCI枚举到驱动probe的6步排查法
2026/9/5 21:16:05 网站建设 项目流程

先给结论:Linux 能不能“认出”一张 GPU,不只是“驱动装没装”的问题。在驱动参与之前,内核早就通过 PCI 子系统把设备从总线上扫出来了。很多人折腾显卡驱动,反复卸载重装、换版本,最后发现问题是 PCI 枚举阶段就失败了,或者 BAR 地址空间分配冲突,驱动根本没有机会执行 probe。

这篇文章把“Linux 认出 GPU”的完整过程拆成 6 步:

  1. PCI 总线枚举
  2. 读取配置空间,拿到设备身份
  3. 分配地址空间,主要看 BAR
  4. 驱动匹配与模块加载
  5. 驱动执行 probe
  6. 设备注册到用户空间,工具才能访问

下面不只讲原理,每步都会给出对应的观察命令和典型失败特征。学会之后,你可以用lspcidmesgsysfs自己定位“为什么 GPU 不被识别”。

1. 6 步流程总览:识别 GPU 是一条 PCI 链路问题

先建立整体视图。GPU 在 Linux 主机上是一个 PCIe 设备,不管它是 NVIDIA、AMD 还是集成显卡,内核都会按照 PCI 枚举、资源分配、驱动绑定的路径处理。整个过程可以理解为 6 个阶段。

阶段内核环节核心动作失败时的典型现象
1PCI 枚举通过 PCIe Root Complex 扫描总线,找到设备lspci完全看不到显卡
2配置空间读取读取 Vendor ID、Device ID、Class Code能看到设备,但厂商型号显示 unknown
3资源分配给 BAR、桥窗口分配 MMIO 地址空间内核日志报 BAR 分配失败
4驱动匹配根据设备 ID 匹配驱动的 id_tablelspci -k显示没有驱动绑定
5驱动 probe驱动初始化硬件,申请中断、寄存器、显存映射probe failed with error -xx
6设备注册注册 DRM 设备或厂商私有节点/dev/dri/dev/nvidia*不存在

实际排查时,不需要从内核源码开始读,而是从第 6 步往前倒推:如果nvidia-smicat /dev/dri/card0不可用,就去看内核模块是否绑定;如果没有绑定,就去看 probe 日志;如果 probe 没执行,就去看资源分配和 ID 匹配。

这篇文章的主要读者应该是这么几类人:

  • 在 Linux 服务器上装过 NVIDIA 驱动,遇到过“驱动装完但nvidia-smi没有输出”的人。
  • 做 GPU 直通、多卡训练环境,需要确认每张卡都被内核正确识别的运维或算法工程师。
  • 想理解probeBARPCIe这些概念,但不想直接啃内核源码的人。

2. 第 1 步:PCI 枚举,设备怎么被发现

GPU 开机后不会自己跑到某个文件夹里等内核来认。它作为 PCIe 设备挂在某个 Root Port 下面,CPU 需要主动去总线上扫描。

x86 平台上,ACPI 会提供 PCIe 配置空间的访问方式,内核拿到 MCFG/ECAM 映射后,从 Host Bridge 开始逐级扫描 bus。每找到一个设备,就给它分配一个 BDF 编号,格式是:

domain:bus:device.function

最常见的 domain 是 0,多卡服务器上第一张 GPU 可能出现在0000:01:00.00000:03:00.0,具体取决于主板 PCIe 槽位对应的 bus 编号。

枚举过程在内核日志里通常可以看到这类输出:

pci 0000:01:00.0: [10de:xxxx] type 00 class 0x030000 pci 0000:01:00.0: reg 0x10: [mem 0x00000000-0xffffffff pref]

其中[10de:xxxx]是厂商 ID 加设备 ID,NVIDIA 的 Vendor ID 是10declass 0x030000表示这是显示控制器。

枚举阶段最常见的失败是lspci完全看不到 GPU。这种情况和驱动关系不大,优先检查:

  • 显卡供电线是否接好,尤其是大功率 GPU。
  • 显卡是否完全插入 PCIe 插槽。
  • BIOS 里 PCIe slot 是否被禁用。
  • 主板是否处于异常状态,导致下游设备枚举失败。

之前有文章专门分析过这类场景,现象是“主板点不亮,进系统后 GPU 直接消失”。这种问题在软件层面没有任何办法,只能回硬件排查。

如果你在一台多 GPU 服务器上看到部分卡消失,还要注意 PCIe 桥的资源分配问题。现代服务器主板上每个 CPU 都有多条 PCIe 链路,多张卡分散在不同 Root Complex 下面。此时可以用下面命令看系统当前识别到的所有 PCI 设备:

lspci -nn

输出片段类似:

01:00.0 VGA compatible controller [0300]: NVIDIA Corporation Device [10de:xxxx] 02:00.0 VGA compatible controller [0300]: NVIDIA Corporation Device [10de:xxxx]

只要lspci能看到,第 1 步就算通过。

3. 第 2 步:读取配置空间,Linux 靠什么认出显卡

设备被发现后,内核会读取 PCI 配置空间。所有 PCIe 设备都有一块标准配置空间,前 64 字节是 Header,里面最重要的字段是:

偏移地址字段作用
0x00Vendor ID厂商标识,比如 NVIDIA 是 0x10de
0x02Device ID具体芯片型号
0x09Class Code设备类别,显示控制器是 0x03
0x0EHeader Type0 表示普通设备,1 表示 PCI 桥

有人问“Linux 怎么通过 PCI 板卡识别品牌”,答案就在这里。系统不会去读显卡外壳上的铭牌,而是直接读配置空间里的 Vendor ID 和 Device ID,再对照/usr/share/misc/pci.ids/usr/share/hwdata/pci.ids显示成文字。

如果你看到lspci输出是:

01:00.0 VGA compatible controller: NVIDIA Corporation Device [10de:xxxx]

说明 ID 已经被解析。如果显示unknown device,往往是pci.ids数据库太旧,但不影响驱动匹配,因为驱动使用的是编码后的 ID,不是文字。

手动读取配置空间可以使用setpci

# 读取 01:00.0 的 Vendor ID,偏移 0x00,读取 2 字节 setpci -s 01:00.0 0x00.w # 读取 Device ID,偏移 0x02 setpci -s 01:00.0 0x02.w # 读取 Class Code,偏移 0x09,读取 3 字节 setpci -s 01:00.0 0x09.l

输出结果每台机器不同,关键是明白这套 ID 机制决定了第 4 步驱动能不能匹配上。

Class Code 也值得多说一句。普通显卡是0x030000,但部分 NVIDIA 计算卡没有显示输出,Class Code 可能是0x030200,也就是 3D Controller。这种情况下系统仍然认为它是 GPU,但不会把它当传统 VGA 设备处理。有人发现lspci显示的不是 “VGA compatible controller” 而是 “3D controller”,会误以为是异常,其实这是正常的。

4. 第 3 步:BAR 地址空间分配,最容易被忽略的失败点

设备 ID 读到了,但设备要真正被 CPU 访问,还需要给它的寄存器窗口和显存映射分配物理地址。这就是 PCI BAR(Base Address Register)。

一个 PCIe 设备通常有多个 BAR,GPU 的典型布局是:

  • BAR0 映射显存或一大段预取内存。
  • BAR1 映射设备控制寄存器。
  • 有些 GPU 还有 BAR2、BAR3,用于门禁页表或其他功能。

BAR 机制的核心是:设备在配置空间里放一个只读的“大小指示器”,系统软件通过读取 BAR 寄存器获知设备需要多大地址空间,然后在 CPU 物理地址空间中找个空闲区域分配给它。

如果这个阶段失败,内核日志会出现类似信息:

pci 0000:01:00.0: can't claim BAR 0 pci 0000:01:00.0: can't allocate MEM resource

热门搜索里经常提到的“Linux 内核无法给 PCIe 桥接器分配足够的内存映射空间”,就是这第 3 步。多 GPU 机器上尤其常见,因为一张现代显卡可能要求几十 GB 的预取内存空间,而 PCIe 桥的 memory window 大小有限。

查看 BAR 分配情况可以用:

lspci -vvs 01:00.0

也可以直接读 sysfs:

cat /sys/bus/pci/devices/0000:01:00.0/resource

如果某个 BAR 显示全是 0 或没有分配地址,说明资源分配没成功。

解决方向主要有这几个:

  • 进 BIOS 开启Above 4G Decoding,让系统可以用 64 位地址访问大块 BAR。
  • 开启Resizable BAR,这也是很多 GPU 驱动推荐的做法。
  • 谨慎使用内核参数pci=realloc,让内核在启动阶段尝试重新分配资源。

这里要注意,pci=realloc不是万能钥匙。它确实可以解决一部分固件分配不合理的问题,但也可能在老主板上改变设备资源布局,导致其他设备异常。实际生产环境修改前,建议先记录原始的lspci -vvv输出,方便回滚对比。

第 3 步是一个“卡住后不知道从哪查”的重灾区。你可以通过一个简单原则判断是不是 BAR 问题:lspci能看到设备,但dmesg里出现can't allocate,并且/sys/bus/pci/devices/0000:01:00.0/resource里没有有效地址。这时不管驱动装多少遍都不会成功,因为驱动去ioremap时根本拿不到有效物理地址。

5. 第 4 步:驱动匹配与模块加载

第 2 步读到的 Vendor ID、Device ID、Class Code,在第 4 步会派上用场。内核维护一个 PCI 总线,每个 PCI 驱动通过pci_device_id数组声明自己支持哪些设备。

以开源驱动的写法为例,驱动核心代码里会有一张表:

static const struct pci_device_id mygpu_pci_ids[] = { { PCI_DEVICE(0x10de, 0x1e07) }, { PCI_DEVICE(0x10de, 0x1e81) }, { } }; MODULE_DEVICE_TABLE(pci, mygpu_pci_ids);

内核会拿设备的 Vendor ID、Device ID 和这张表比对,匹配成功后才调用驱动。不同 GPU 厂商对应的驱动:

  • NVIDIA 开源驱动是nouveau,闭源驱动是nvidia
  • AMD 是amdgpu
  • Intel 集成显卡是i915

驱动匹配成功后,lspci -k会显示:

Kernel driver in use: nvidia

如果匹配失败或模块没有加载,显示:

Kernel driver in use: (none)

自动加载模块的过程由 udev 和 kmod 完成。设备枚举时,内核会生成一个 modalias 字符串,用户态工具通过它找到对应模块。可以在 sysfs 下查看:

cat /sys/bus/pci/devices/0000:01:00.0/modalias

输出类似:

pci:v000010DEd00001E07sv000010DEsd00001E07bc03sc00i00

这段字符串里的v000010DEd00001E07就是 Vendor ID 和 Device ID。人工核对modinfo输出可以发现模块是否包含对应 alias。

如果设备 ID 匹配了,但模块没有自动加载,可以手动加载:

modprobe nvidia

加载后再看lspci -k,如果模块绑定成功,驱动名会出现在设备后面。

有些场景需要强制重新绑定驱动,此时可以通过 sysfs 操作:

echo "0000:01:00.0" > /sys/bus/pci/drivers/nvidia/unbind echo "0000:01:00.0" > /sys/bus/pci/drivers/nvidia/bind

注意,这样做之前要保证模块已经加载,并且设备当前没有被其他进程占用。

从实际经验看,第 4 步最常见的坑是nouveaunvidia冲突。系统默认加载了nouveau,占住了设备,NVIDIA 官方驱动就无法绑定。解决方式是在/etc/modprobe.d/下添加 blacklist 配置,让nouveau不自动加载,然后重建 initramfs。

6. 第 5 步:驱动 probe,GPU 真正被初始化

驱动匹配上以后,内核会调用驱动的probe函数。这一步才是“设备真正开始工作”的地方。

在 PCI 驱动模型里,probe 函数承担这些任务:

  1. 调用pci_enable_device启用设备。
  2. 调用pci_request_regionspci_request_mem_regions申请资源。
  3. 读取 BAR 地址,做ioremap
  4. 设置 DMA 掩码。
  5. 申请中断。
  6. 初始化硬件状态,注册 DRM 设备或厂商私有框架。

一个典型的驱动 probe 骨架如下:

static int mygpu_probe(struct pci_dev *pdev, const struct pci_device_id *id) { int ret; resource_size_t bar0_start, bar0_len; ret = pcim_enable_device(pdev); if (ret < 0) return ret; ret = pcim_iomap_regions(pdev, BIT(0), "mygpu"); if (ret < 0) return ret; bar0_start = pci_resource_start(pdev, 0); bar0_len = pci_resource_len(pdev, 0); pci_set_master(pdev); dev_info(&pdev->dev, "mygpu probed: BAR0=%pa len=%pa\n", &bar0_start, &bar0_len); return 0; }

真实项目中,这个函数会复杂得多,尤其是 NVIDIA/AMD 的官方驱动,probe 里会加载固件、初始化显存控制器、建立显存管理结构。

probe 失败时,内核会返回一个负 errno。例如在 dmesg 中看到:

mygpu: probe of 0000:01:00.0 failed with error -12

常见的 errno 含义如下:

错误码含义常见触发场景
-12ENOMEM,内存或资源不足BAR 分配失败、DMA 内存不足
-16EBUSY,资源被占用另一个驱动已经占用设备
-5EIO,IO 错误设备无法响应配置读写
-22EINVAL,参数无效固件版本不匹配、模块参数错误

排查 probe 失败时,优先看 dmesg 中的完整报错,而不是只看最后一行:

dmesg | grep -E "nvidia|amdgpu|pci 0000|mygpu"

也要关注有没有 GPU crash dump 之类的信息。一些显卡在 probe 阶段如果初始化超时,会触发硬件侧的错误记录,这些信息对判断“是驱动配置问题还是硬件电气问题”很有帮助。

从实际角度说,如果驱动能走到 probe,说明前面第 1 到第 4 步都正常,问题范围已经缩小到内核模块和硬件的交互。此时可以尝试:

  • 换一个驱动版本,优先匹配当前内核版本。
  • 检查 BIOS 里的 PCIe 链路速度和电源管理设置。
  • 调整模块参数,比如 NVIDIA 驱动的某些电源管理选项。

7. 第 6 步:设备注册,用户空间工具才可见

probe 成功不代表用户马上能用。最后一步是驱动把设备注册成用户空间可以访问的节点。

不同类型的 GPU 注册方式不同:

  • AMDGPU、Intel i915、Nouveau 这类 DRM 驱动会在/dev/dri下生成card0card1renderD128等节点。
  • NVIDIA 官方闭源驱动会生成/dev/nvidia0/dev/nvidiactl,并在/proc/driver/nvidia/gpus/下暴露信息。
  • nvidia-smi是用户态工具,它必须通过设备节点访问内核模块,如果节点没生成,nvidia-smi自然找不到 GPU。

检查设备节点:

ls -l /dev/dri/ ls -l /dev/nvidia*

在多 GPU 服务器上,每张卡会映射成不同的cardNrenderDN或者nvidiaN。对应关系可以通过以下方式查看:

ls -l /sys/class/drm/card*/device/driver

如果想让某个进程只使用特定 GPU,在驱动正确识别后,可以通过环境变量控制。

export CUDA_VISIBLE_DEVICES=0,1

这里要提一下虚拟化直通场景。如果你打算把 GPU 直通给虚拟机,宿主机上通常不让驱动绑定设备,这样lspci -k可能显示没有驱动,但这不代表设备有问题,而是设备已经被配置为直通模式,由虚拟机内的驱动接管。这种情况下不要盲目在宿主机上强绑驱动,否则直通会失败。

第 6 步完成后,GPU 才算“完全被 Linux 认出”。所以,判断一张卡是否工作正常,建议按这个顺序检查:

  1. lspci -nn能看到设备。
  2. lspci -k能看到内核驱动。
  3. cat /sys/bus/pci/devices/0000:01:00.0/driver存在。
  4. /dev/dri/*/dev/nvidia*存在。
  5. nvidia-smi能列出 GPU。

8. 全链路实战排查:把 6 步变成命令清单

用一个实际场景演示排查思路。假设服务器上有一张 NVIDIA GPU,但nvidia-smi报找不到设备。

第一步,先确认 PCI 枚举:

lspci -nn | grep -Ei "vga|3d|display"

能看到设备就继续;看不到设备,直接回到硬件层面。

第二步,确认当前驱动绑定状态:

lspci -nnk

如果显示Kernel driver in use: nvidia,说明第 4、5 步已经过了,问题可能在用户态工具或设备节点。如果显示Kernel driver in use: nouveau,说明官方驱动没有抢到设备,需要处理 blacklist。如果显示none,跳到下一步。

第三步,查看设备 sysfs 状态:

cat /sys/bus/pci/devices/0000:01:00.0/vendor cat /sys/bus/pci/devices/0000:01:00.0/device cat /sys/bus/pci/devices/0000:01:00.0/modalias ls -l /sys/bus/pci/devices/0000:01:00.0/driver

driver符号链接如果不存在,说明驱动没有绑定。

第四步,查看内核日志:

dmesg | grep -Ei "pci|nvidia|amdgpu|BAR|resource"

重点看有没有can't allocateprobe failed

第五步,确认设备节点:

ls -l /dev/dri/ ls -l /dev/nvidia0 /dev/nvidiactl 2>/dev/null

如果/dev/dri正常但/dev/nvidia*缺失,可能是驱动模块没加载,也可能是驱动版本和用户态工具版本不一致。

第六步,检查显存资源和 BAR 分配:

lspci -vvs 01:00.0 | grep -A10 "Region" cat /sys/bus/pci/devices/0000:01:00.0/resource

resource文件中如果只看到 0,说明 BAR 没有分配成功,问题在第 3 步。

这套流程的价值在于,每一步都能把问题归位到具体的 6 个阶段之一,避免“反复重装驱动”式的低效操作。

9. 常见问题与排查方法

下表对应前文 6 个阶段,结合日常运维遇到的典型问题整理。

问题现象对应阶段可能原因排查方式解决方案
lspci看不到 GPU第 1 步 PCI 枚举物理链路、供电、BIOS 禁用检查插槽、供电、BIOS重新插拔、接独立供电、开启 PCIe slot
lspci显示 unknown device第 2 步 配置空间pci.ids 数据库过旧更新pciutils更新 hwdata 数据库
dmesg 报can't allocate BAR第 3 步 BAR 分配固件资源分配不合理查看 resource sysfs开启 Above 4G Decoding,谨慎使用 pci=realloc
lspci -k没有驱动绑定第 4 步 驱动匹配模块未加载、ID 表不匹配查 modalias 和 modinfomodprobe 加载模块,确认 ID 匹配
probe failed with error -12第 5 步 probe内存或资源不足dmesg 完整日志调整资源预留、换驱动版本
probe failed with error -16第 5 步 probe设备被其他驱动占用查 lspci -kblacklist 冲突驱动,释放设备
nvidia-smi找不到 GPU第 6 步 用户空间设备节点缺失或工具版本不一致查 /dev/nvidia*重装用户态工具,修复节点权限
多 GPU 只有部分识别第 3/4 步桥资源不足或模块参数限制比较多卡 dmesg逐卡查看 BAR,确认模块参数
重启后驱动丢失第 4 步 模块加载模块未配置开机加载,nouveau 冲突查 modprobe 配置添加 blacklist,重建 initramfs
虚拟机直通后宿主机看不到卡第 4 步设备被 PCI Stub 接管lspci -k 查看这是预期行为,不要强制绑定

前 6 行分别对应文章拆解的 6 个阶段,排查时可以按表对比。如果一个问题同时涉及多个阶段,比如nvidia-smi找不到设备且 dmesg 有 BAR 报错,优先处理 BAR,因为资源分配失败会直接让 probe 走不到成功分支。

10. 深入方向与最终建议

到了这里,6 步链路已经完整了。理解这套流程之后,最大的收益是排查问题不再靠猜。

如果再往深处走,建议按这几个方向扩展:

  1. 读内核源码,重点看drivers/pci/probe.cdrivers/base/dd.c。前者负责枚举和资源分配,后者负责驱动绑定和 probe 调用。
  2. 手写一个最小 PCI 驱动,只要一张 id_table 和简单的 probe/remove,就能在虚拟机里验证完整流程。
  3. 弄懂 DRM 子系统的注册过程,尤其是drm_dev_register/dev/dri节点的关系。
  4. 结合 NVIDIA 官方驱动文档,理解它为什么比开源驱动多了/dev/nvidiactl/proc/driver/nvidia这些结构。

最值得先跑通的是这套最小验证命令:

lspci -nn lspci -nnk dmesg | grep -Ei "pci|nvidia|amdgpu" ls -l /dev/dri/

如果这 4 条命令的输出能对应上,你对“Linux 认出 GPU”的理解就已经超过大多数只会在nvidia-smi看不到卡时重装系统的人。

先不要直接在生产服务器上尝试强制 unbind/bind 或者pci=realloc。找一个有 GPU 的测试机,记录基线dmesg,然后逐步执行前文步骤。这套链路跑通后,再处理多卡、直通或批量环境也会轻松很多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询