开机之后,Linux 到底是怎么认出那张 GPU 的?
很多人把“装好驱动就能用”当成理所当然。真到了服务器上插四张卡只认三张、lspci 能看到但 /dev/dri 不存在、驱动模块加载后 dmesg 一直报 BAR 空间不足的时候,才会发现 Linux 识别显卡并不是一个黑盒动作,而是一条从 PCIe 枚举、配置空间读取、资源分配、设备模型挂载,再到驱动 match 和 probe 的完整链条。
这篇文章不谈用户态 API,也不讲 CUDA 怎么调用显存,直接往下走:把 Linux 从 PCIe 扫描到 GPU 驱动 probe 的完整过程拆成 6 步,讲清楚每一层内核在做什么、我们能在用户态看到什么痕迹、最常见的认卡失败发生在哪一步。无论你是做 GPU 服务器运维、AI 训练环境部署,还是嵌入式 Linux 驱动开发,这条链路都值得完整过一遍。
1. 先看主干:Linux 从 PCIe 枚举到驱动 probe 的完整链路
GPU 在 x86 平台上不是天生“被 Linux 认识”的设备。它是一张 PCIe 设备卡,必须走完标准 PCI 子系统协议,才能被内核纳入设备模型,最终由 GPU 驱动接管。
先看一眼完整链路,后面再逐段展开:
| 步骤 | 内核动作 | 关键机制 | 失败症状 |
|---|---|---|---|
| 第 1 步 | PCIe 总线枚举 | ACPI/PCI Host Bridge 驱动扫描总线 | 设备完全不可见,lspci 无输出 |
| 第 2 步 | 读取配置空间 | Vendor ID/Device ID/Class Code | 能看到设备但无法识别类型 |
| 第 3 步 | BAR 地址分配 | PCI 资源分配与桥窗口重排 | BAR 空间不足,内核报 no space |
| 第 4 步 | 挂入设备模型 | pci_dev 与 sysfs 生成 | lspci 有卡,/sys 节点异常 |
| 第 5 步 | 驱动匹配 | pci_device_id 匹配 | 设备无人接管,无驱动绑定 |
| 第 6 步 | probe 初始化 | 开启 DMA、映射 BAR、注册中断 | 驱动加载失败,GPU 不可用 |
从这个表能看出一个规律:“驱动加载”只是最后一步。如果前面第 3 步资源分配失败,即使驱动模块完全正确,照样认不出 GPU。这也能解释为什么很多显卡点不亮时,第一反应是“驱动坏了”,但真正的原因却在内核 PCI 子系统那一层。
对运维和开发人员来说,最重要的认知是:设备能不能用,取决于 6 步是否全部成功。下面按顺序拆解。
2. 第一步:PCIe 总线枚举,谁在扫描设备
2.1 CPU 不会自己发现显卡
x86 平台加电后,CPU 必须知道总线上挂了什么设备。这个动作早期由 BIOS/UEFI 固件完成,Linux 启动后接管 PCI 子系统,会重新对 PCIe 总线做枚举。
内核中真正执行扫描的模块是 Linux PCI 子系统核心代码。它通过读取 Host Bridge(主机桥)的配置,拿到根总线号,然后逐级扫描下面的 PCIe Bridge、Switch、Endpoint。GPU 通常挂在某个 PCIe Root Port(根端口)后面,总线号形如 0000:01:00.0、0000:03:00.0。
这段过程对应 dmesg 里常见的日志:
pci 0000:01:00.0: [10de:2684] type 00 class 0x030000 pci 0000:01:00.0: reg 0x10: [mem 0x00000000-0xffffffff pref]这里10de:2684是 NVIDIA 某款 GPU 的厂商 ID 和设备 ID,class 0x030000表示这是一张 VGA 兼容显示控制器。看到这些行,说明 Linux 已经完成枚举。
2.2 根总线从哪来
根总线信息不是内核“猜”的,而是来自 ACPI 表。PC 平台通过 ACPI 的_SB_作用域描述 PCI0 等设备,内核中的 PCI Host Bridge 驱动负责解析这些 ACPI 节点,并创建 pci_host_bridge 结构。
如果机器上有多个 PCIe 控制器,例如服务器同时有 CPU 自带的 PCIe 控制器和额外的 PCIe Switch 芯片,内核会为每个控制器枚举出一棵独立总线树。例如:
0000:00:00.0 Host bridge 0000:00:01.0 PCI bridge 0000:01:00.0 VGA compatible controller 0000:40:00.0 Host bridge 0000:41:00.0 VGA compatible controller服务器上多张 GPU 分布在不同的总线域和总线号之下,这是非常常见的现象。lspci 输出最前面的0000:01:00.0四段编号,含义分别就是 domain(域)、bus(总线号)、device(设备号)、function(功能号)。
2.3 枚举失败时你能看到什么
PCIe 枚举失败很极端,通常表现为:
- lspci 输出中根本没有 GPU 那行;
- dmesg 里看不到
pci 0000:xx:xx.x开头的内容; - 系统启动时显卡没有输出。
这种时候问题往往不在 Linux,而在于主板物理插槽、PCIe Link 训练失败、或者 UEFI 固件没有正确初始化根端口。排查手段也很直接:换槽位、检查 PCIe 供电、检查 BIOS 里 PCIe Slot 是否被禁用。
3. 第二步:读配置空间,从 Vendor ID 到 Class Code
设备被发现之后,Linux 要回答一个问题:这是不是 GPU?判断依据来自 PCI 配置空间。
3.1 PCI 配置空间里的关键字段
PCIe 设备有独立的配置空间。传统 PCI 配置空间是 256 字节,PCIe 扩展到了 4096 字节。内核最关注的是头部前 64 字节中的这些字段:
| 偏移 | 字段 | 示例值 | 含义 |
|---|---|---|---|
| 0x00 | Vendor ID | 0x10de | NVIDIA |
| 0x02 | Device ID | 0x2684 | 具体 GPU 型号 |
| 0x04 | Command | 0x0000 | 控制 IO/MEM/DMA 使能 |
| 0x08 | Revision ID | 0xa1 | 芯片步进版本 |
| 0x09 | Class Code | 0x030000 | 显示控制器 |
| 0x2c | Subsystem Vendor ID | 0x10de | 板卡厂商 |
| 0x2e | Subsystem ID | 0x16a1 | 板卡型号 |
| 0x10-0x24 | BAR0-BAR5 | 内存地址 | 设备资源窗口 |
其中 Class Code 是判断设备类型最直接的字段。0x030000是 VGA 兼容控制器,0x038000是其他显示控制器。部分计算卡(如早期 Tesla)可能显示为0x038000,而不是标准的0x030000,这也是为什么仅用 lspci 判断“有没有显卡”不完全可靠。
3.2 配置空间里的厂商 ID 是怎么来的
Vendor ID 不是操作系统分配的,而是 PCI-SIG 分配给芯片厂商的唯一编号。驱动和设备之间的身份匹配,最底层就是靠 Vendor ID + Device ID 组合。
内核中可以看到大量这样的设备 ID 表,例如 NVIDIA 开源驱动 nouveau 的某段声明:
static const struct pci_device_id nouveau_dmi_table[] = { { PCI_VDEVICE(NVIDIA, 0x2684), 0 }, { } }; MODULE_DEVICE_TABLE(pci, nouveau_dmi_table);这段代码的意思是:这个驱动声明自己能处理0x10de:0x2684对应的设备。后面第 6 步详细展开匹配机制。
3.3 如何在用户态查看配置空间
最方便的工具是 lspci:
# 查看所有 PCI 设备,显示厂商和设备 ID lspci -nn # 查看某张 GPU 的完整信息 lspci -vvv -s 01:00.0 # 只看内核驱动绑定情况 lspci -nnk -s 01:00.0-nnk输出会额外显示Kernel driver in use和Kernel modules。如果看到Kernel driver in use: nvidia,说明驱动已经绑定;如果显示Kernel modules: nvidia但没有 in use,说明驱动没 probe 成功。
配置空间不只能读,还能用 setpci 写。驱动开发调试中,setpci 常用于验证某个 Command 寄存器位是否生效:
# 查看 01:00.0 设备的 Command 寄存器 setpci -s 01:00.0 COMMAND # 打印 256 字节配置空间的原始内容 setpci -s 01:00.0 0.b物理机上乱写配置空间可能造成设备异常,调试时务必谨慎。
4. 第三步:BAR 地址分配,显存为什么能被 CPU 看见
这一步是 PCI 枚举过程中最“容易出事”的环节,也是大量 GPU 认卡失败的元凶。搜索引擎和论坛里常见的“Linux 内核无法给 PCIe 桥接器(PCI bridge)分配足够的内存映射空间(即 BAR 地址)”就是典型症状。
4.1 什么是 BAR
BAR 的全称是 Base Address Register,Base 地址寄存器。每个 PCIe 设备都有最多 6 个 BAR 区域,用于向 CPU 暴露自己的寄存器、显存、门控表等资源。
GPU 这类设备非常依赖 BAR 空间,尤其是:
- BAR0:通常映射 GPU 的 MMIO 寄存器区域;
- BAR1/BAR3:通常映射显存的一部分或全部,让 CPU 能直接访问显存;
- 其他 BAR:可能映射控制门、帧缓冲、重放寄存器等。
当 lspci 显示这类内容时,说明 BAR 已经成功分配:
Region 0: Memory at f0000000 (64-bit, prefetchable) [size=16M] Region 1: Memory at e0000000 (64-bit, prefetchable) [size=256M]如果用采 lspci -vvv 看到 Region 后面没有地址,而是显示disabled或<ignoring>,就说明资源分配没有完成。
4.2 地址怎么分:PCI 资源分配与桥窗口
现代 PCIe 系统中,每个 PCIe 桥都会划分一个“窗口”,只允许特定地址范围的事务往下走。GPU 想要获得一段地址,需要满足两个条件:
- GPU 自己声明的 BAR 大小要能落到某个空闲地址区段;
- 所在 PCIe 桥的窗口必须能覆盖这段地址。
内核负责把这些窗口重新排列组合。但如果 32 位地址空间被大量设备占满,或者桥的窗口配置受限,就可能出现 BAR 请求冲突。
dmesg 典型的报错是:
pci 0000:01:00.0: BAR 1: no space for [mem size 0x10000000 64bit pref] pci 0000:01:00.0: BAR 1: failed to assign [mem size 0x10000000 64bit pref]翻译成人话:GPU 想申请 256MB 的显存映射空间,但当前总线范围内没地方放了。
4.3 为什么多卡服务器更容易遇到 BAR 问题
多 GPU 服务器的资源窗口压力远大于单卡 PC。每张 GPU 要映射一段不小的 MMIO 和显存区域,加上 PCIe Switch 桥自身需要资源,累计起来非常可观。
如果主板固件的 PCIe 资源预留策略不合理,或者某些桥的 I/O 窗口和内存窗口互相挤压,几张大卡插进去就会出现“后面的卡 BAR 分配失败”。最直观的现象就是:6 张卡只认 5 张,而且认不出的是插在总线号更靠后的那张。
有几种常见化解方法:
# 方法1:重启时要求内核重新分配 PCI 资源 # 在某些发行版的内核命令行中追加: pci=realloc # 方法2:配合大 BAR 支持(如果设备支持 Above 4G Decoding) # BIOS 中打开 Above 4G Decoding / Resizable BARpci=realloc 允许内核忽略固件已经分配的资源,重新对所有 BAR 做分配。很多“插卡顺序一变就认不全”的机器,加上这个参数后能恢复正常,但也不是万能解。需要理解 pci=realloc 是通过使能 PCI 子系统的重新分配来缓解地址冲突,不保证解决全部固件兼容问题。
4.4 BAR 分配完成后,设备才能真正被访问
BAR 分配成功后,GPU 的寄存器、显存窗口就有了确定的物理地址。内核和用户态驱动才能通过 MMIO 去读写这些区域。如果这一步失败,后面第 6 步的 probe 即使执行了也会在ioremap等环节失败。
5. 第四步:设备挂入设备模型,sysfs 中的证据
BAR 分配完成后,设备还只是 PCI 设备树里的一个节点。为了让上层驱动框架能够管理它,内核会把这个 PCI 设备封装成一个struct pci_dev,并把它注册进 Linux 统一的设备模型。这一步的产物就是我们非常熟悉的 sysfs 目录。
5.1 从 pci_dev 到 device
Linux 设备模型的核心思想是“设备与驱动分离”。系统里存在两类对象:
- device:代表硬件设备实例;
- device_driver:代表能驱动某类硬件的代码。
struct pci_dev内部包含一个struct device,所以每个 PCIe 设备在 sysfs 中都有对应目录:
/sys/bus/pci/devices/0000:01:00.0/这个目录下的文件怎么看,是理解 Linux PCI 子系统的强大调试手段。进入目录后重点看这几个文件:
# 查看设备厂商 ID 和设备 ID 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/class # 查看当前绑定的驱动 ls -l /sys/bus/pci/devices/0000:01:00.0/driver # 查看已分配的 BAR 资源 cat /sys/bus/pci/devices/0000:01:00.0/resource如果driver符号链接不存在,说明设备还没有被任何驱动绑定,对应第 6 步还没成功。
5.2 sysfs 还能看什么
除了基本资源信息,sysfs 下还能看到中断信息、NUMA 节点、IOMMU 分组等。
# 查看设备的本地中断号 cat /sys/bus/pci/devices/0000:01:00.0/irq # 查看设备所在 NUMA 节点 cat /sys/bus/pci/devices/0000:01:00.0/numa_node # 查看 IOMMU 分组,直通调试常用 ls /sys/kernel/iommu_groups/其中 IOMMU 分组对虚拟化直通非常重要。如果一张 GPU 被分到了多个 IOMMU group,或者和其它设备在同一个 group 里,直通时就会遇到“存在 PCI/PCIe 直通设备时,部分虚拟机操作将不可用”的麻烦。搜索引擎里大量相关热词说明,判断 PCIe 设备能否直通过滤,第一步就是看 IOMMU 分组。
5.3 设备模型注册失败会怎样
如果设备模型注册失败,通常伴随更底层的内核错误,sysfs 目录可能不完整、设备出现在 lspci 中但无法被上层使用。这类问题大多不是驱动问题,而是 PCI 子系统的资源或一致性处理出错了,需要结合 dmesg 才能定位。
6. 第五步:驱动匹配,从模块声明到 pci_device_id
设备已经注册到系统里,接下来内核要回答:谁来驱动它?
这一步的核心机制是驱动模型匹配。PCI 驱动声明自己支持哪些设备,内核遍历总线上所有未绑定驱动的设备,找到 ID 匹配的那对,触发后续的 probe。
6.1 pci_driver 结构体与设备 ID 表
一个典型的 PCI GPU 驱动会定义一个pci_driver:
static struct pci_driver amdgpu_pci_driver = { .name = "amdgpu", .id_table = amdgpu_pci_table, .probe = amdgpu_pci_probe, .remove = amdgpu_pci_remove, };id_table是匹配的核心。它内部存放一个数组,每个元素都包含 Vendor ID、Device ID、Subvendor ID、Subdevice ID 和 class mask 等字段。当总线上的设备 ID 与表里某一行吻合时,匹配成功。
实际内核驱动中,匹配条件经常写得比较宽。比如一张卡可以匹配“Vendor ID + Device ID + Subsystem ID”的精确组合,也可以只匹配“Vendor ID + Device ID”,甚至只匹配 Class Code 范围。NVIDIA 闭源驱动的做法稍有不同,它会在安装时动态收集 GPU 的pci_device_id,生成映射表,再绑定到 nvidia 模块。
6.2 驱动模块手动绑定与解绑
实际运维中最常用的调试操作,是手动解绑和重新绑定驱动:
# 解绑 echo "0000:01:00.0" > /sys/bus/pci/devices/0000:01:00.0/driver/unbind # 重新绑定(先确认驱动支持这个设备) echo "0000:01:00.0" > /sys/bus/pci/drivers/nvidia/bind这种操作在排查驱动冲突时特别有效。例如机器上同时有 nouveau 和 nvidia 驱动,模块加载顺序可能导致设备先被 nouveau 绑定。此时可以依赖 driver_override 来指定某个驱动:
# 只允许 nvidia 驱动接管 01:00.0 echo "nvidia" > /sys/bus/pci/devices/0000:01:00.0/driver_override echo "0000:01:00.0" > /sys/bus/pci/drivers_probe注意,用户态直接写 bind/unbind 属于较危险操作,如果 probe 过程中显卡正在被使用,可能导致显存数据丢失或系统卡死。建议在测试环境操作,不要在承载业务的训练机器上临时乱试。
6.3 匹配成功之后,内核调用 probe
当驱动与设备匹配成功,内核会调用驱动结构体中注册的 probe 函数。GPU 的初始化真正从这里开始。如果匹配失败,设备仍然挂在总线上,但不会有任何驱动认领。
判断匹配是否成功,最直接的方法是 lspci -nnk:
01:00.0 VGA compatible controller [0300]: NVIDIA Corporation Device [10de:2684] Subsystem: NVIDIA Corporation Device [10de:16a1] Kernel driver in use: nvidia Kernel modules: nouveau, nvidia有Kernel driver in use说明匹配并 probe 成功;如果只有Kernel modules列表,说明驱动模块虽然安装了,但没有完成绑定。
7. 第六步:驱动 probe,把 GPU 从“PCI 设备”变成“可用计算设备”
驱动匹配只是“领养”,真正让 GPU 进入可用状态的是 probe。probe 本质是驱动的初始化入口函数,内核驱动通过它完成资源申请、中断注册、显存映射和后续的功能初始化。
7.1 probe 到底做了哪些事
不同类型的 GPU 驱动具体步骤不同,但都会遵循一套 PCI 驱动标准初始化流程。以通用 PCI 驱动为例,probe 中通常包含这些关键动作:
static int my_gpu_probe(struct pci_dev *pdev, const struct pci_device_id *id) { int ret; // 1. 使能 PCI 设备,开启 Memory/IO/DMA 访问 ret = pci_enable_device(pdev); if (ret) return ret; // 2. 申请 BAR 资源所有权 ret = pci_request_regions(pdev, "my_gpu"); if (ret) goto disable_device; // 3. 获取 BAR0 的物理地址和长度 unsigned long bar0_start = pci_resource_start(pdev, 0); unsigned long bar0_len = pci_resource_len(pdev, 0); // 4. 将物理地址映射到内核虚拟地址空间 void __iomem *mmio = ioremap(bar0_start, bar0_len); if (!mmio) goto release_regions; // 5. 设置 DMA 掩码,允许 GPU 访问 64 位地址 ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64)); if (ret) goto unmap; // 6. 注册中断或启用 MSI/MSI-X ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_MSI); if (ret < 0) goto unmap; // 这里继续做显存初始化、固件加载、命令处理器启动等... return 0; unmap: iounmap(mmio); release_regions: pci_release_regions(pdev); disable_device: pci_disable_device(pdev); return ret; }这段代码是一个结构模板,不是某个具体驱动源码,但完整表达了一个 GPU PCI 驱动 probe 的标准骨架。
7.2 pci_enable_device 的作用
很多入门开发者容易忽略 pci_enable_device。它不只是一个“形式调用”,内部会做:
- 设置 PCI Command 寄存器中的 Bus Master 位,使设备能够发起 DMA;
- 同时判断 Memory Space Enable 和 I/O Space Enable 是否正常;
- 该函数调用之后,设备才有资格访问主存。
如果驱动忘记调用 pci_enable_device,或者这一步失败,即使 BAR 分配成功,显存访问和 DMA 也无法工作。
7.3 ioremap 与显存映射
GPU 的显存通常通过 BAR 暴露给 CPU。但 CPU 不能直接访问物理地址,需要先建立页表映射。ioremap的作用就在这里:把 BAR 对应物理区域映射成内核虚拟地址,驱动后续才能通过 MMIO 操作 GPU 寄存器。
对大显存 GPU,驱动并不会把所有显存都一次性 ioremap 到内核线性地址空间。更常见的做法是只映射必要的控制区域,显存主体通过 Linux DMA API 来访问。这也是为什么 GPU 驱动会大量使用 dma_alloc_coherent 或流式 DMA 映射来管理显存缓冲区。
7.4 probe 失败时的返回值和 dmesg
内核驱动开发中,probe 返回值是排查问题的核心线索。常见返回值含义:
| 返回值 | 含义 | 常见触发场景 |
|---|---|---|
| -ENODEV | 设备不存在或不支持 | ID 表匹配但硬件初始化失败 |
| -ENOMEM | 内存不足 | 无法分配 DMA 缓冲或内核内存 |
| -EIO | IO 错误 | 读写配置空间或 MMIO 失败 |
| -ENXIO | 无此设备或地址 | ioremap 失败、BAR 无效 |
| -EPERM | 操作不允许 | 固件安全策略或 IOMMU 限制 |
dmesg 中如果出现:
amdgpu 0000:03:00.0: [drm] ERROR failed to initialize GPU, aborting.说明 probe 执行到了后期但 GPU 初始化失败。GPU 驱动 probe 走到一半失败,需要分前后两段看问题:如果是 BAR 申请失败,查资源分配;如果是 ioremap 失败,查地址冲突;如果是固件加载超时,查 GPU 供电或稳定性。
8. 实战排查:GPU 认不全时的典型问题
把 6 步拆完,再来反推实际运维中我们最常遇到的几种现象。下面按“现象 → 可能原因 → 排查思路”的顺序整理一个相对完整的排查表:
| 现象 | 可能原因 | 首选排查方式 | 可行解法 |
|---|---|---|---|
| lspci 完全看不到某张卡 | PCIe 链路训练失败/插槽接触不良 | dmesg 查 PCIe 错误,替换插槽 | 重新插拔、检查供电、升级固件 |
| lspci 能看到,但 Kernel driver in use 为空 | 驱动模块未加载或设备 ID 未被匹配 | lspci -nnk、modinfo | 安装对应驱动,检查模块黑名单 |
| 多卡机器后插的卡不认 | BAR 空间不足,桥窗口冲突 | dmesg 搜 “no space for” | 追加 pci=realloc,调整 BIOS 资源预留 |
| 驱动模块 load 成功但 probe 报 -ENOMEM | 内核内存不足或 CMA 区不足 | dmesg | 调大 CMA、关闭无关服务释放显存占用 |
| probe 中途报 GPU timeout 或 GPU crash dump triggered | 显存访问异常,电压不稳或固件 bug | 查 dmesg 时间点,对比温度 | 降频、换卡、升级驱动/固件 |
| 直通给虚拟机失败 | IOMMU 分组不合理或设备被宿主机占用 | ls /sys/kernel/iommu_groups | 打开 ACS 补丁或换插槽,确保独立分组 |
| CPU 能访问显存,但设备 DMA 不工作 | pci_enable_device 失败或 Bus Master 位未开 | setpci 查 COMMAND 寄存器 | 检查驱动是否调用 pci_enable_device |
| 出现 “BAR 0: no space for” 后设备功能异常 | 固件预分配策略错误 | dmesg 全文搜 BAR | 开启 Above 4G Decoding / Resizable BAR |
排查 GPU 问题的通用顺序是:lspci 确认设备存在 → lspci -vvv 确认 BAR 和 Capability → dmesg 搜 pci 与对应驱动日志 → 检查 sysfs 绑定状态 → 手动 unbind/bind 复现 → 定位到具体步骤。
9. 调试命令工具箱
日常工作中,最常用的一组命令应该形成肌肉记忆。下面按排查顺序整理。
9.1 看设备是否被发现
# 快速列出所有 NVIDIA GPU 对应的槽位 lspci -nn | grep -i nvidia # 或查找所有 VGA/3D 显示控制器 lspci -nn | grep -Ei 'vga|3d|display'输出中的 BDF(Bus:Device.Function)编号要记牢,后续所有操作都依赖它。
9.2 看设备详细信息
# 查看 BAR、Capability、MSI、LMEM 大小等 lspci -vvv -s 01:00.0 # 查看设备 ID 表是否被某个驱动识别 lspci -nnk -s 01:00.0-vvv输出的 Region 行可以帮助你判断 BAR 是否分配成功。如果 Region 后没有地址,大概率是第 3 步资源分配出了岔子。
9.3 看内核日志
# 查看最近 PCI 相关日志 dmesg | grep -i pci # 查看某张卡的具体日志 dmesg | grep '01:00.0' # 连续跟踪日志,适合驱动加载复现 dmesg -w需要说明的是,dmesg 目前在很多系统上需要 root 权限。执行 dmesg 之前先确认当前用户是否有权限读取 kernel ring buffer。
9.4 看 sysfs 状态
# 列出所有 PCI 设备 ls /sys/bus/pci/devices/ # 查看某设备当前绑定的驱动 readlink /sys/bus/pci/devices/0000:01:00.0/driver # 查看资源分配 cat /sys/bus/pci/devices/0000:01:00.0/resourcesysfs 中的内容是内核数据结构的实时投影,比 lspci 更接近内核视角。
9.5 动态加载/卸载驱动
# 加载驱动 modprobe nvidia # 卸载驱动 modprobe -r nvidia # 查看模块参数 modinfo nvidia如果机器上同时存在 nouveau 和 nvidia,可以通过黑名单方式避免 nouveau 抢先绑定:
# /etc/modprobe.d/blacklist-nouveau.conf blacklist nouveau options nouveau modeset=0写入后需要更新 initramfs 并重启。具体命令各发行版不同,Ubuntu/Debian 是update-initramfs -u,RHEL/CentOS 是dracut -f。
10. 最佳实践与安全边界
从 PCIe 枚举到 probe 这条链路并不复杂,但实际排障时容易因为跳步而浪费时间。下面几条值得长期遵守:
10.1 硬件检查先行
如果一台机器换了新卡后认不全,先不要陷入驱动安装的死循环。确认 lspci 能否看到设备;看不到时先从物理层查起。PCIe 链路训练失败、插槽供电不足、PCIe 线缆或转接卡接触不良,这些都会让内核根本看不到设备,再完美的驱动也无法解决。
10.2 给内核决策留出余地
固件预分配的 PCI 资源有时并不合理。在确认 GPU 和主板支持的情况下,开启 BIOS 中的 Above 4G Decoding 是一个常见的化解方案,它能让 64 位 BAR 使用更高的地址区域,减少 32 位地址空间耗尽问题。如果用户态能看到明显的 BAR 分配失败日志,内核启动参数中追加 pci=realloc 也值得在测试环境验证。
10.3 软件驱动与设备匹配必须看 ID 表
用户态经常遇到“明明安装了大驱动,卡却不亮”的问题。此时先看 lspci -nnk 输出的 Kernel modules 列表,如果驱动不在列表里,说明模块 id_table 不包含这张卡的 ID,或驱动安装后没有更新 modules.dep。盲目重装系统不是解法。
10.4 用最小复现环境验证
驱动开发阶段,不建议直接在承载生产业务的 GPU 服务器上反复 unbind/bind。建议先在一台带 GPU 的独立测试机上验证,或者使用虚拟机环境模拟 PCI 设备驱动开发,至少要做到能随时重启系统而不影响业务。
10.5 保持安全边界
对 GPU 配置空间的 raw 读写、driver_override 修改、手动 bind/unbind 都属于高风险操作。在生产环境执行这些步骤前,务必备份数据并确认操作不会影响正在运行的容器或虚拟机。特别是 GPU 直通场景,随意改动 PCI 设备的驱动绑定可能让虚拟机失去显存映射,导致访客系统崩溃。
10.6 输出从 CPU 视角理解,排查从总线视角出发
CPU 看到的 GPU 永远只是 PCIe 上的一个 Endpoint。无论是 lspci、dmesg、sysfs 还是 setpci,本质都是在回答同一个问题:这个 Endpoint 的配置空间是否健康、资源是否分配、驱动是否接管。
明白这 6 步之后,再遇到“Linux 识别不到 GPU”“BAR 空间不足”“Kernel driver in use 为空”这些报错,就不会再从网上乱抄删除命令了。先定位断点在第几步,再决定下一步动作,通常比反复重装系统快得多。如果你的目标是做服务器 GPU 集群管理,建议从理解整条链路开始,再尝试调通一张卡,最后扩展到多卡场景。