ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Linux识别GPU的完整过程:从PCI枚举到驱动probe的6步排查法

Linux识别GPU的完整过程:从PCI枚举到驱动probe的6步排查法 先给结论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 个阶段。阶段内核环节核心动作失败时的典型现象1PCI 枚举通过 PCIe Root Complex 扫描总线找到设备lspci完全看不到显卡2配置空间读取读取 Vendor ID、Device ID、Class Code能看到设备但厂商型号显示 unknown3资源分配给 BAR、桥窗口分配 MMIO 地址空间内核日志报 BAR 分配失败4驱动匹配根据设备 ID 匹配驱动的 id_tablelspci -k显示没有驱动绑定5驱动 probe驱动初始化硬件申请中断、寄存器、显存映射报probe failed with error -xx6设备注册注册 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 加设备 IDNVIDIA 的 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 是 0x10de0x02Device ID具体芯片型号0x09Class Code设备类别显示控制器是 0x030x0EHeader 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 BARBase Address Register。一个 PCIe 设备通常有多个 BARGPU 的典型布局是BAR0 映射显存或一大段预取内存。BAR1 映射设备控制寄存器。有些 GPU 还有 BAR2、BAR3用于门禁页表或其他功能。BAR 机制的核心是设备在配置空间里放一个只读的“大小指示器”系统软件通过读取 BAR 寄存器获知设备需要多大地址空间然后在 CPU 物理地址空间中找个空闲区域分配给它。如果这个阶段失败内核日志会出现类似信息pci 0000:01:00.0: cant claim BAR 0 pci 0000:01:00.0: cant allocate MEM resource热门搜索里经常提到的“Linux 内核无法给 PCIe 桥接器分配足够的内存映射空间”就是这第 3 步。多 GPU 机器上尤其常见因为一张现代显卡可能要求几十 GB 的预取内存空间而 PCIe 桥的 memory window 大小有限。查看 BAR 分配情况可以用lspci -vvs 01:00.0也可以直接读 sysfscat /sys/bus/pci/devices/0000:01:00.0/resource如果某个 BAR 显示全是 0 或没有分配地址说明资源分配没成功。解决方向主要有这几个进 BIOS 开启Above 4G Decoding让系统可以用 64 位地址访问大块 BAR。开启Resizable BAR这也是很多 GPU 驱动推荐的做法。谨慎使用内核参数pcirealloc让内核在启动阶段尝试重新分配资源。这里要注意pcirealloc不是万能钥匙。它确实可以解决一部分固件分配不合理的问题但也可能在老主板上改变设备资源布局导致其他设备异常。实际生产环境修改前建议先记录原始的lspci -vvv输出方便回滚对比。第 3 步是一个“卡住后不知道从哪查”的重灾区。你可以通过一个简单原则判断是不是 BAR 问题lspci能看到设备但dmesg里出现cant 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 步驱动 probeGPU 真正被初始化驱动匹配上以后内核会调用驱动的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 含义如下错误码含义常见触发场景-12ENOMEM内存或资源不足BAR 分配失败、DMA 内存不足-16EBUSY资源被占用另一个驱动已经占用设备-5EIOIO 错误设备无法响应配置读写-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下生成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_DEVICES0,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重点看有没有cant 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 slotlspci显示 unknown device第 2 步 配置空间pci.ids 数据库过旧更新pciutils更新 hwdata 数据库dmesg 报cant allocate BAR第 3 步 BAR 分配固件资源分配不合理查看 resource sysfs开启 Above 4G Decoding谨慎使用 pcirealloclspci -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 步链路已经完整了。理解这套流程之后最大的收益是排查问题不再靠猜。如果再往深处走建议按这几个方向扩展读内核源码重点看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 或者pcirealloc。找一个有 GPU 的测试机记录基线dmesg然后逐步执行前文步骤。这套链路跑通后再处理多卡、直通或批量环境也会轻松很多。
返回列表