资讯动态

Linux内核识别GPU的6步链路:从PCI枚举到驱动probe

发布时间:2026/9/3 2:13:53 来源:尧图企业网站定制
在 Linux 服务器上安装 GPU 驱动或排查 GPU 不可见问题时很多人都遇到过这种场景lspci里明明能看到 NVIDIA 显卡但进入系统后nvidia-smi却提示找不到设备或者主机插了多张 GPU重启后其中一张卡就像“凭空消失”一样dmesg里还能看到 PCIe 资源分配失败。这些问题看似零散其实都指向同一条链路Linux 内核是如何一步步“认出”GPU 的。本文不绕弯子直接从 PCI 枚举讲到驱动 probe再把 6 个关键步骤对应的工具、sysfs 节点和排查方法完整梳理一遍。无论你是做 Linux 运维、AI 平台管理还是刚开始接触内核驱动开发都能从这套链路里找到排错方向。1. 先建立直觉6 步链路是什么先思考一个基本问题GPU 插在服务器的 PCIe 插槽上对 CPU 和 Linux 内核来说它首先是一块“PCIe 设备”。Linux 要让它真正可用并不是简单地“加载驱动”就结束而是要经过一条相对固定的链路。对应lspci和nvidia-smi这两个常见工具可以这样理解lspci能看到设备代表 Linux 已经完成了 PCI 枚举、配置空间识别、基本资源读取。nvidia-smi能看到设备代表驱动已经成功 probe并且用户态工具通过内核模块可以访问 GPU。中间隔着的正是驱动绑定、硬件初始化和用户态接口注册这些步骤。把这套流程拆开本文所说的 6 步分别是PCI 总线枚举扫描 PCIe 拓扑找到设备所在的 Bus/Device/Function。配置空间识别读取 Vendor ID、Device ID、Class Code、BAR 等关键信息确认设备身份。BAR 资源分配为 GPU 的寄存器窗口和显存映射窗口分配 CPU 物理地址空间。设备注册与驱动匹配把pci_dev注册到内核设备模型并让驱动通过id_table找到它。驱动 probe调用驱动的.probe回调完成 DMA 设置、中断申请、显存映射等硬件初始化。用户态接口注册创建/dev/nvidia0、/dev/dri/card0等设备节点CUDA、PyTorch 等应用才能访问 GPU。后面几章就按这 6 步逐层展开。每一部分都会先讲原理再给出可执行的命令或代码并指出最常见的问题点。2. 第 1 步PCI 总线枚举先确认拓扑和 BDF2.1 谁在做枚举什么时候做PCI/PCIe 总线枚举是指系统扫描 PCI 总线拓扑的过程。x86 平台在开机时固件BIOS/UEFI会先对 PCIe 总线做第一轮初始化Linux 内核启动时通常也会重新扫描并对资源进行再分配。Linux 内核里对应的代码路径在drivers/pci/probe.c核心动作就是从一个已知的 PCI 域和总线号出发逐段读取配置空间找到桥设备再继续往下扫描。枚举的实际对象是设备的 BDF也就是 Bus总线号、Device设备号、Function功能号。我们经常看到的0000:01:00.0就是标准格式0000PCI Domain多数 x86 机器只有一个域 0。01Bus 号。00Device 号。0Function 号。2.2 PCIe 拓扑与桥的关系PCIe 拓扑是树状的。CPU 内部或主板芯片组引出 Root PortRoot Port 下面可能接 Switch交换机芯片Switch 再分出多个端口接 Endpoint。GPU 就是典型的 PCIe Endpoint。用下面命令可以一目了然地看到整棵拓扑lspci -tv在一台插了 NVIDIA GPU 的服务器上输出大致是这种结构不同机器差异较大仅作示意--[0000:00]--00.0 Intel Corporation Host Bridge | -01.0-[01]----00.0 NVIDIA Corporation Device 2230 | -01.1-[02]----00.0 NVIDIA Corporation Device 2230 | -01.2-[03]----00.0 NVIDIA Corporation Device 2230 | -02.0 Intel Corporation PCIe Root Port其中-[01]表示总线号 01----00.0表示在 Bus 01 上的设备 00 的功能 0。这个编号直接影响后文的 sysfs 路径和驱动绑定方式。2.3 为什么枚举有时候会失败枚举能否成功物理前提是 PCIe Link 已经 Train 成功。PCIe 设备上电后链路两端要完成速率协商和链路训练配置空间才能被访问。如果 GPU 供电不足、金手指没有插好、插槽物理损坏或者 PCIe CEM 规范要求的边带信号异常配置空间读出来都是0xFF系统就会认为该位置不存在设备。所以排查“Linux 完全看不到 GPU”时第一步是确认物理链路本身是否正常而不是急着装驱动。服务器层面常见的做法是看 BMC/IPMI 日志、重新插拔或更换插槽。2.4 平台差异x86 和 ARM64 的一点点区别x86 平台访问 PCI 配置空间早期使用 I/O 端口0xCF8/0xCFC的 Configuration MechanismPCIe 时代大量使用 MMCONFIG/ECAM 方式把配置空间直接映射到内存地址。ARM64 和嵌入式平台比如常见的 ZynqMP 等通常没有传统 I/O 空间更多依赖 Device Tree 或 ACPI 中的 PCIe 控制器节点来描述总线范围和地址映射。对普通运维排查来说不需要修改这些底层逻辑但要建立概念Linux 枚举 PCIe 设备所依据的总线范围、MMIO 窗口在 x86 上可能来自 ACPI 表和固件在 ARM64 上可能来自设备树。看到“PCIe 设备不识别”类问题时先确认平台层是否给控制器分配了足够的总线号和地址空间往往能省很多时间。3. 第 2 步读取配置空间识别设备身份找到设备之后下一步是读取 PCI 配置空间。每个 PCIe 功能都有一份 256 字节的标准配置空间PCIe 又扩展到了 4KB但前 256 字节的布局与 PCI 兼容。3.1 配置空间里的关键字段配置空间里的字段很多对设备识别来说最关键的是下面几个配置偏移宽度含义0x0016 bitVendor ID比如 NVIDIA 是 0x10DE0x0216 bitDevice ID比如具体 GPU 型号编号0x088 bitRevision ID0x098 bitProgramming Interface0x0A8 bitSubclass0x0B8 bitBase Class0x03 表示显示控制器0x10~0x2432 bitBAR0~BAR4记录 MMIO/I/O 资源地址0x3C8 bitInterrupt Line0x3D8 bitInterrupt Pin3.2 用 lspci 查看设备身份lspci内部就是解析配置空间后把数字翻译成字符串。查看 NVIDIA 设备的常用命令如下# 只看 NVIDIA 设备的数值 ID lspci -nn -d 10de: # 查看内核驱动绑定情况 lspci -nnk -d 10de: # 查看某一个 BDF 的完整能力 lspci -vvv -s 01:00.0第一行输出通常类似01:00.0 VGA compatible controller [0300]: NVIDIA Corporation GA102 [GeForce RTX 3080] [10de:2206] (rev a1)方括号里有两个关键数字10de是 Vendor ID表示 NVIDIA。2206是 Device ID表示具体型号。如果插的是无显示输出的计算卡Class Code 可能是0302也就是 3D controller而不是 VGA compatible controller。3.3 用 setpci 直接验证配置空间如果你想更底层地“看到”配置空间可以用setpci读取原始数值。以读取 Bus 01 上的设备为例# 读取 Vendor ID输出类似 10de setpci -s 01:00.0 0.w # 读取 Device ID输出类似 2206 setpci -s 01:00.0 2.w # 读取 Class Code输出类似 0300 setpci -s 01:00.0 0a.w说明setpci需要 root 权限而且不同发行版不一定默认安装pciutils。这里只介绍只读操作不要在排查时随意向配置空间写值写错很可能导致设备状态异常。3.4 为什么驱动识别靠 ID而不是靠字符串内核驱动不会拿着字符串去匹配“RTX 3080”而是维护一张pci_device_id表。表中包含 Vendor ID、Device ID、Subsystem Vendor ID、Subsystem Device ID、Class 等字段。驱动加载后内核会拿设备的配置空间信息去遍历这张表匹配成功才会进入后续 probe 流程。同一个 GPU 核心可能被多个显卡厂商做成不同整卡所以 Subsystem ID 用来进一步区分板卡厂家和具体型号。系统里的modalias就是把设备身份拼成一段字符串供模块自动加载使用。4. 第 3 步BAR 空间分配资源不够会怎样4.1 BAR 是什么BAR 是 Base Address Register直译是“基地址寄存器”。它决定 CPU 可以从哪个物理地址访问设备的寄存器窗口和内存窗口。对 GPU 来说BAR0 通常映射设备内部寄存器后面可能还有 BAR1、BAR3 等映射显存部分区域。如果 BAR 没有被分配有效地址设备对驱动来说就是不可用的。查看设备资源分配情况的最直接入口是 sysfs。假设设备 BDF 是0000:01:00.0# 查看系统为该设备分配的 MMIO 窗口 cat /sys/bus/pci/devices/0000:01:00.0/resource正常的 NVIDIA GPU 输出大致如下0x00000000fd000000 0x00000000fdffffff 0x0000000000140204 0x00000000c0000000 0x00000000cfffffff 0x000000000014220c每行三列分别表示起始地址、结束地址、资源标志。如果某一行全是0x0000000000000000说明该 BAR 没有被分配这是驱动 probe 失败的常见原因之一。4.2 Bridge 窗口与多 GPU 资源不足PCIe 拓扑是树状的因此 CPU 访问下游设备时需要经过中间的 PCIe Bridge。每一个 Bridge 都会把下游所有设备的 BAR 区间合并成一个或多个窗口Windows 和 Linux 统称这些为桥窗口。服务器插多张 GPU 时常见问题就出现了每张大显存 GPU 都要求几十 GB 的 MMIO 空间如果主板 BIOS 没有开启 Above 4G Decoding系统只能把资源压缩在 32 位地址空间内很快就分配完了。这时dmesg会报类似错误pci 0000:03:00.0: cant allocate mem resource pcieport 0000:00:01.0: cant allocate memory window这类“无法给 PCIe 桥或设备分配足够 BAR 空间”的问题在多卡 AI 服务器上尤其常见。解决办法通常包括进入 BIOS/UEFI开启 Above 4G Decoding。按平台支持情况开启 Resizable BAR。更新主板/服务器 BIOS。尝试调整多 GPU 的插槽位置让每张卡尽量分散在不同 Root Port 下。在 Linux 启动参数中加入pcirealloc允许内核重新分配桥和设备资源。这个参数在部分场景有效但生产环境要评估后再使用。4.3 BAR 空间与 CPU 访问显存现代 GPU 的本地显存HBM/GDDR通常不会全部映射到 CPU 地址空间。CPU 访问显存更多是通过 GPU 的 DMA 引擎和驱动管理的映射机制完成。BAR 里直接映射出来的只是设备需要 CPU 直接访问的那部分资源。所以不要把“BAR 空间大小”和“显存容量”画等号。GPU 显存 80 GB不代表 CPU 物理地址空间里要留出 80 GB 的 BAR 映射而是指设备内部资源需要的窗口。5. 第 4 步设备注册与驱动匹配5.1 Linux 设备模型里的三件套Linux 设备模型中有三个核心概念总线Bus、设备Device、驱动Driver。PCI 总线对应pci_bus_typePCI 设备对应pci_devPCI 驱动对应pci_driver。它们三者的关系可以简化理解为设备说“我在这里我的身份信息是什么”。驱动说“我能支持这些设备这是我的 id_table”。总线负责在设备和驱动之间牵线搭桥。当内核枚举出一个新的 PCI 设备并完成资源分配后它会生成一个pci_dev设备对象挂到pci_bus_type上。与此同时如果驱动模块已经加载总线就会尝试让驱动与设备进行匹配。5.2 pci_driver 与 id_table一个最小的 PCI 驱动骨架通常长这样#include linux/pci.h #include linux/module.h static int demo_probe(struct pci_dev *pdev, const struct pci_device_id *id) { int ret; ret pci_enable_device(pdev); if (ret) return ret; pci_set_master(pdev); dev_info(pdev-dev, demo GPU driver probed\n); return 0; } static void demo_remove(struct pci_dev *pdev) { dev_info(pdev-dev, demo GPU driver removed\n); } static const struct pci_device_id demo_pci_ids[] { { PCI_DEVICE(0x10de, 0x2206) }, { 0, } }; MODULE_DEVICE_TABLE(pci, demo_pci_ids); static struct pci_driver demo_pci_driver { .name demo_gpu_driver, .id_table demo_pci_ids, .probe demo_probe, .remove demo_remove, }; module_pci_driver(demo_pci_driver); MODULE_LICENSE(GPL);这里最重要的就是demo_pci_ids表。当设备 Vendor ID 是0x10de、Device ID 是0x2206时总线匹配成功内核就会调用demo_probe。5.3 查看设备当前由哪个驱动绑定在真实系统里用下面命令就能知道设备当前被谁绑定# 查看驱动符号链接 ls -l /sys/bus/pci/devices/0000:01:00.0/driver # 查看设备 ID 与模块自动加载信息 cat /sys/bus/pci/devices/0000:01:00.0/uevent cat /sys/bus/pci/devices/0000:01:00.0/modaliasdriver文件是指向/sys/bus/pci/drivers/xxx的符号链接。如果链接不存在说明还没有驱动绑定。如果链接指向nouveau而不是nvidia说明 NVIDIA 官方驱动没抢到设备这是常见的驱动切换冲突。modalias的内容类似pci:v000010DEd00002206sv000010DEsd00002206bc03sc00i00用户态工具modprobe可以根据这段字符串自动查找并加载对应的内核模块。这也是为什么设备出现时系统能自动拉起来一个驱动。5.4 driver_override 与强制绑定有时系统里存在多个驱动都支持同一设备例如开源驱动和闭源驱动并存。此时可以通过driver_override指定某个驱动来绑定设备。# 强制让设备优先绑定 nvidia 驱动 echo nvidia /sys/bus/pci/devices/0000:01:00.0/driver_override # 重新触发驱动绑定 echo 0000:01:00.0 /sys/bus/pci/drivers_probe

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价