先给结论:Linux 能不能“认出”一张 GPU,不只是“驱动装没装”的问题。在驱动参与之前,内核早就通过 PCI 子系统把设备从总线上扫出来了。很多人折腾显卡驱动,反复卸载重装、换版本,最后发现问题是 PCI 枚举阶段就失败了,或者 BAR 地址空间分配冲突,驱动根本没有机会执行 probe。
这篇文章把“Linux 认出 GPU”的完整过程拆成 6 步:
- PCI 总线枚举
- 读取配置空间,拿到设备身份
- 分配地址空间,主要看 BAR
- 驱动匹配与模块加载
- 驱动执行 probe
- 设备注册到用户空间,工具才能访问
下面不只讲原理,每步都会给出对应的观察命令和典型失败特征。学会之后,你可以用lspci、dmesg、sysfs自己定位“为什么 GPU 不被识别”。
1. 6 步流程总览:识别 GPU 是一条 PCI 链路问题
先建立整体视图。GPU 在 Linux 主机上是一个 PCIe 设备,不管它是 NVIDIA、AMD 还是集成显卡,内核都会按照 PCI 枚举、资源分配、驱动绑定的路径处理。整个过程可以理解为 6 个阶段。
| 阶段 | 内核环节 | 核心动作 | 失败时的典型现象 |
|---|---|---|---|
| 1 | PCI 枚举 | 通过 PCIe Root Complex 扫描总线,找到设备 | lspci完全看不到显卡 |
| 2 | 配置空间读取 | 读取 Vendor ID、Device ID、Class Code | 能看到设备,但厂商型号显示 unknown |
| 3 | 资源分配 | 给 BAR、桥窗口分配 MMIO 地址空间 | 内核日志报 BAR 分配失败 |
| 4 | 驱动匹配 | 根据设备 ID 匹配驱动的 id_table | lspci -k显示没有驱动绑定 |
| 5 | 驱动 probe | 驱动初始化硬件,申请中断、寄存器、显存映射 | 报probe failed with error -xx |
| 6 | 设备注册 | 注册 DRM 设备或厂商私有节点 | /dev/dri、/dev/nvidia*不存在 |
实际排查时,不需要从内核源码开始读,而是从第 6 步往前倒推:如果nvidia-smi或cat /dev/dri/card0不可用,就去看内核模块是否绑定;如果没有绑定,就去看 probe 日志;如果 probe 没执行,就去看资源分配和 ID 匹配。
这篇文章的主要读者应该是这么几类人:
- 在 Linux 服务器上装过 NVIDIA 驱动,遇到过“驱动装完但
nvidia-smi没有输出”的人。 - 做 GPU 直通、多卡训练环境,需要确认每张卡都被内核正确识别的运维或算法工程师。
- 想理解
probe、BAR、PCIe这些概念,但不想直接啃内核源码的人。
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.0或0000: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 是10de;class 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,里面最重要的字段是:
| 偏移地址 | 字段 | 作用 |
|---|---|---|
| 0x00 | Vendor ID | 厂商标识,比如 NVIDIA 是 0x10de |
| 0x02 | Device ID | 具体芯片型号 |
| 0x09 | Class Code | 设备类别,显示控制器是 0x03 |
| 0x0E | Header Type | 0 表示普通设备,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这段字符串里的v000010DE、d00001E07就是 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 步最常见的坑是nouveau和nvidia冲突。系统默认加载了nouveau,占住了设备,NVIDIA 官方驱动就无法绑定。解决方式是在/etc/modprobe.d/下添加 blacklist 配置,让nouveau不自动加载,然后重建 initramfs。
6. 第 5 步:驱动 probe,GPU 真正被初始化
驱动匹配上以后,内核会调用驱动的probe函数。这一步才是“设备真正开始工作”的地方。
在 PCI 驱动模型里,probe 函数承担这些任务:
- 调用
pci_enable_device启用设备。 - 调用
pci_request_regions或pci_request_mem_regions申请资源。 - 读取 BAR 地址,做
ioremap。 - 设置 DMA 掩码。
- 申请中断。
- 初始化硬件状态,注册 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 含义如下:
| 错误码 | 含义 | 常见触发场景 |
|---|---|---|
| -12 | ENOMEM,内存或资源不足 | BAR 分配失败、DMA 内存不足 |
| -16 | EBUSY,资源被占用 | 另一个驱动已经占用设备 |
| -5 | EIO,IO 错误 | 设备无法响应配置读写 |
| -22 | EINVAL,参数无效 | 固件版本不匹配、模块参数错误 |
排查 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下生成card0、card1、renderD128等节点。 - NVIDIA 官方闭源驱动会生成
/dev/nvidia0、/dev/nvidiactl,并在/proc/driver/nvidia/gpus/下暴露信息。 nvidia-smi是用户态工具,它必须通过设备节点访问内核模块,如果节点没生成,nvidia-smi自然找不到 GPU。
检查设备节点:
ls -l /dev/dri/ ls -l /dev/nvidia*在多 GPU 服务器上,每张卡会映射成不同的cardN、renderDN或者nvidiaN。对应关系可以通过以下方式查看:
ls -l /sys/class/drm/card*/device/driver如果想让某个进程只使用特定 GPU,在驱动正确识别后,可以通过环境变量控制。
export CUDA_VISIBLE_DEVICES=0,1这里要提一下虚拟化直通场景。如果你打算把 GPU 直通给虚拟机,宿主机上通常不让驱动绑定设备,这样lspci -k可能显示没有驱动,但这不代表设备有问题,而是设备已经被配置为直通模式,由虚拟机内的驱动接管。这种情况下不要盲目在宿主机上强绑驱动,否则直通会失败。
第 6 步完成后,GPU 才算“完全被 Linux 认出”。所以,判断一张卡是否工作正常,建议按这个顺序检查:
lspci -nn能看到设备。lspci -k能看到内核驱动。cat /sys/bus/pci/devices/0000:01:00.0/driver存在。/dev/dri/*或/dev/nvidia*存在。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/driverdriver符号链接如果不存在,说明驱动没有绑定。
第四步,查看内核日志:
dmesg | grep -Ei "pci|nvidia|amdgpu|BAR|resource"重点看有没有can't allocate或probe 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/resourceresource文件中如果只看到 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 和 modinfo | modprobe 加载模块,确认 ID 匹配 |
| probe failed with error -12 | 第 5 步 probe | 内存或资源不足 | dmesg 完整日志 | 调整资源预留、换驱动版本 |
| probe failed with error -16 | 第 5 步 probe | 设备被其他驱动占用 | 查 lspci -k | blacklist 冲突驱动,释放设备 |
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 步链路已经完整了。理解这套流程之后,最大的收益是排查问题不再靠猜。
如果再往深处走,建议按这几个方向扩展:
- 读内核源码,重点看
drivers/pci/probe.c和drivers/base/dd.c。前者负责枚举和资源分配,后者负责驱动绑定和 probe 调用。 - 手写一个最小 PCI 驱动,只要一张 id_table 和简单的 probe/remove,就能在虚拟机里验证完整流程。
- 弄懂 DRM 子系统的注册过程,尤其是
drm_dev_register和/dev/dri节点的关系。 - 结合 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,然后逐步执行前文步骤。这套链路跑通后,再处理多卡、直通或批量环境也会轻松很多。