ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

lspci背后:Linux内核PCI子系统与sysfs的完整链路

lspci背后:Linux内核PCI子系统与sysfs的完整链路 说实话在AI Infra这个岗位上待久了lspci应该是我敲得最频繁的命令之一。新机器到位先敲一遍看GPU/NIC在不在域驱动装完再敲一遍确认driver绑定线上掉卡的时候更是要反复敲。但绝大多数时候我们只看它的输出很少去想Linux内核在背后做了多少事。今天这篇AI Infra每日一问的Day 11就想把这个最常见的问题聊透当你在终端里敲下lspci的一瞬间内核里发生了什么这个问题并不只是“AI Infra八股”。lspci的数据不是凭空来的它背后是Linux内核从固件枚举PCI设备、建立sysfs设备模型、再到通过file_operations把配置空间暴露给用户态的完整链路。搞懂这条链路你在GPU服务器排障、内核升级、设备直通这些场景里会少走很多弯路。这篇文章我会按一次真实执行lspci的路径从用户态命令拆到内核的PCI子系统再聊到file_operations动态拦截、透明加密这类安全模块为什么会出现在同一条链路上最后附上几张我自己排障时常用的操作表。适合刚接触AI Infra的运维、做内核相关开发的同行以及单纯想搞明白“命令背后发生了什么”的人。1. 先搞清楚 lspci 到底在问谁1.1 敲下命令后用户态程序的入口lspci是pciutils包里的工具它自身不感知设备只是通过libpci向内核要数据。libpci的初始化逻辑并不复杂先扫描可用的访问方法比如sysfs、proc/bus/pci、x86 IO端口直读然后选择优先级最高的一个。现代主流Linux发行版上sysfs几乎总是首选所以我们可以把lspci等同于“读sysfs树解析配置空间”。普通用户执行的lspci只会显示vendor/device/class等最基本的字段这些字段大多来自/sys/bus/pci/devices/目录下每个设备的vendor、device、class文件。完整的信息比如BAR、IRQ、扩展配置空间则需要读取config文件而config通常只有root或具有CAP_SYS_ADMIN的进程能打开。所以如果你在一个普通容器里执行lspci -vvv大概率会看到“Permission denied”的警告这是第一个值得记住的现象。提示看到“Permission denied”不代表内核没数据只是sysfs节点的权限限制。生产环境建议用privileged容器或者通过主机的root执行不要图省事chmod 777。1.2 sysfs内核暴露设备模型的一面镜子在Linux里sysfs是一个基于内存的虚拟文件系统挂载在/sys。它的目录结构对应内核里的设备模型/sys/bus/pci/devices/下面每个子目录都以“域:总线:设备.功能”命名比如0000:3b:00.0这就是一个PCIe设备的BDF地址。目录里的属性文件由内核PCI子系统注册背后对应的是struct pci_dev的成员字段。lspci读取vendor文件时内核返回的是pci_dev-vendor的数值再被lspci通过pci.ids数据库翻译成“NVIDIA Corporation”这样的字符串。这里的“读文件”不是一个比喻而是真正的文件系统读操作会发生路径解析、inode查找、权限检查、open/read系统调用。换句话说lspci本质上是一个非常特殊的文本处理程序。这个设计对AI Infra很重要。比如你想快速遍历一台8卡机器上有哪些PCIe设备不需要自己写内核模块用shell循环读/sys/bus/pci/devices/*/vendor就够了。这也是为什么很多GPU巡检脚本都喜欢基于sysfs做而不是去解析lspci文本因为文件接口稳定且不依赖locale。1.3 从目录项到内核对象kobject 给了名字pci_dev 给了属性sysfs里的一个目录在内核中并不是一个真实的磁盘目录而是一个kobject。每个设备在注册时会创建一个kobject名字就是BDF。而设备本身的类型、属性、show/store回调则由更上层的struct device和struct pci_dev描述。sysfs的属性接口有两种一种是设备驱动通过device_create_file注册的普通属性另一种是总线/驱动核心注册的默认属性。PCI设备的config就是后者它对应的show回调会读取PCI配置空间并拷贝到用户缓冲区。kobject/ktype在这里的作用是提供统一的release、uevent、属性组管理等机制。理解这一点你就可以明白为什么用rmdir去删/sys里的设备目录不会成功因为那根本不是你能控制的东西它反映的是内核里的设备状态。2. 内核里的 PCI 子系统设备从哪来lspci 又依赖什么2.1 枚举硬件树其实是固件先搭好的在x86平台上PCI/PCIe设备的发现顺序大体是上电后由BIOS/UEFI固件扫描总线为每个设备分配BDF、BAR地址、中断等资源然后内核在启动早期读取配置空间并建立pci_dev对象。这段枚举逻辑在Linux内核源码的drivers/pci/probe.c里核心函数是pci_scan_bus/pci_scan_single_device。内核通过访问配置空间的CF8/CFC IO端口传统PCI或ECAM内存映射PCIe来遍历总线。有一个容易被误解的点lspci执行时并不会触发内核重新扫描硬件。它只是把内核启动时已经枚举出来的设备树展示出来。如果你在系统运行中拔插了一张卡然后想在不重启的情况下让内核发现它需要主动触发rescan比如写1到/sys/bus/pci/rescan或者用echo 1 /sys/bus/pci/devices/.../remove然后重新扫描。否则lspci不会看到新设备。注意生产环境热插拔GPU后一定记得先确认驱动已经卸载再写remove文件。顺序反了可能导致内核访问已移除的设备出现AER报错甚至主机hang住。2.2 配置空间、BAR 与 MMIOGPU 显存怎么被 CPU 看到每个PCIe设备至少有一个256字节的配置空间PCIe还扩展到了4096字节。配置空间最前面是Vendor ID、Device ID、Class Code、Status、Command等公共字段后面是BARBase Address Register。BAR保存的是设备内部资源被映射到CPU物理地址空间的基地址和大小比如显存、门铃寄存器、MSI-X表等。当lspci -v显示“Region 0: Memory at fa000000 (64-bit, prefetchable) [size256M]”意思就是该设备BAR0占用了从0xfa000000开始的256MB物理地址。GPU驱动probe时会把这块区域ioremap到内核虚拟地址空间CPU随即能通过这块地址访问显存。AI Infra中如果看到某个GPU的BAR大小为0或地址异常通常说明固件没有正确分配资源常见原因包括没开启Above 4G Decoding、PCIe slot供电不足、或者主板的PCIe switch有缺陷。BAR资源的分配也非常依赖内核的pci_resource_*系列函数。驱动不应该自己去猜地址而是调用pci_resource_start(pdev, bar)获得物理地址再ioremap。这个“不自己猜”的原则在内核开发里是底线因为资源可能被固件、内核realloc和IOMMU重新安排过。2.3 总线-设备-驱动三者如何匹配内核设备模型用bus_type、device、driver三个核心对象描述硬件关系。PCI设备注册时会把自己的vendor/device/class等信息挂在device上PCI驱动注册时用pci_device_id表声明自己支持哪些设备。内核的匹配逻辑就是遍历总线上的设备比对ID表匹配成功则调用驱动的probe函数。lspci -k里显示的“Kernel driver in use: nvidia”就是匹配成功的证据。它本质上是读设备目录下driver符号链接指向的驱动名。我们日常排障时如果lspci能看到设备但“Kernel driver in use”为空通常有几种可能驱动没装、驱动模块没有自动加载、或者设备被driver_override锁定了。这时候可以查看/sys/bus/pci/devices/ /driver_override文件有时虚拟化平台会利用它强制绑定某个vfio-pci驱动。3. 敲下 lspci 的一瞬间一条包含 file_operations 的完整链路3.1 从终端到内核openat 与 VFS 的一次握手执行lspci后libpci会调用open()打开/sys/bus/pci/devices/0000:3b:00.0/config。这个open会转化为openat系统调用现代glibc统一用openat进入内核。内核VFS层根据路径逐级查找目录项得到对应的inode然后通过该文件系统注册的file_operations完成打开动作。关键点在于sysfs里看到的文件并不是普通磁盘文件它没有块设备、没有磁盘缓存。它的inode在内存中生成inode-i_fop指向sysfs/kernfs自己的file_operations。正因为所有sysfs文件共用一套file_operations内核才能在open时根据属性类型分发到不同的show回调函数。这也是很多安全模块选择拦截file_operations的原因只要把inode或struct file里的f_op替换一层就能在不改sysfs核心代码的情况下在所有属性读写上加钩子。3.2 sysfs 读路径kernfs 与 show 回调展开看读config的路径进程调用read()后内核入口是ksys_read经过VFS到达kernfs_fop_read_iter。kernfs会把用户的缓冲区和一个seq_file缓冲区绑定然后调用kernfs_seq_show最终执行到属性文件注册时的show回调。对PCI config属性来说show回调是pci_read_config内部调用pci_user_read_config_*函数按offset读取配置空间的字节再拷贝给用户态。这套多级转发看起来很绕但它带来了一个好处用户态从来不需要知道如何直接访问CF8/CFC端口或ECAM地址内核把底层硬件差异全部封装了。在虚拟化平台上config的读取甚至会被Hypervisor拦截并返回虚拟化后的数据guest里的lspci看到的和宿主机看到的可以不一样。这对我们用SR-IOV、设备直通时理解“设备到底透传了什么”特别有帮助。3.3 file_operations 动态替换透明加密和审计为什么能插进来在AI Infra场景里sysfs的这个读路径并不一定永远“干净”。很多发行版和安全产品会通过内核模块在设备文件或sysfs文件打开时替换struct file-f_op从而在read/write前后加入自己的逻辑。最常见的用途是透明加密拦截对某个文件内容的read/write自动解密/加密也用于审计记录谁在什么时间读了哪个设备属性。实现原理不复杂模块在open阶段保存原始f_op把当前file的f_op指向自己的包装结构在包装的read里先做检查再调原read。但动态替换f_op在Linux内核里属于“高危行为”需要读持有file spinlock或者通过security_file_open钩子更安全。内核社区并不是一味反对所有拦截而是要求钩子设计必须可追溯、不破坏默认语义。我们自己在做内核改造时也尽量优先使用LSM hook、tracepoint、fprobe这些更规范的机制而不是一上来就替换f_op。实操心得我在排查一次“为什么容器里lspci读到的是加密后的设备名”问题时最后发现是某agent在sysfs读路径上做了透明加密只对特定进程解密。这类问题用strace看不出来需要在kprobe里把f_op地址打出来对比。遇到诡异行为先怀疑file_operations是否被人为替换过。3.4 数据组装从配置空间字节到人类可读文本内核返回给lspci的只是一串raw bytes比如config前64字节里的vendor0x10de、device0x2684。pciutils拿到这些字节后会去/usr/share/hwdata/pci.ids或/usr/share/misc/pci.ids里查表把ID翻译成“NVIDIA Corporation”和“GA102 [GeForce RTX 3090]”。pci.ids这个文件可以直接编辑所以如果你在一台机器上看到厂商名显示为“Unknown”先检查pci.ids是否存在、更新日期是否太旧。另外lspci显示的很多信息其实不是直接读config而是对配置空间的解析。例如Class Code决定设备类型0x0300代表VGA compatible controller子系统ID从配置空间0x2C偏移读取IRQ则可能同时来自config和内核中断子系统。理解这些字段的offset在写监控脚本或者做性能分析时会很有用。4. AI Infra 排障实录lspci 常见问题与内核层面的排查4.1 一张速查表lspci 不对劲时先看哪里我把自己在GPU服务器上遇到最多的问题整理成了一个表格这也是我团队新人培训时会发的那一页。现象可能原因排查命令/动作lspci看不到某张GPUPCIe链路训练失败、供电不足、固件禁用dmesg搜“link down”检查物理槽位BIOS里关闭Fast Boot能看到设备但没有驱动驱动未安装/module未加载lspci -nnk 查drivermodprobe nvidia检查DKMSBAR地址显示为0固件资源分配失败查BIOS Above 4G Decodingdmesg搜resource读config时Permission denied非root/容器无CAP_SYS_ADMIN主机root执行或在privileged容器执行lspci -vvv卡住设备D3冷状态或软件锁strace看block在哪个fd尝试echo 1 /sys/bus/pci/devices/.../remove再rescan设备信息显示Unknownpci.ids太旧update-pciids更新数据库设备在lspci里重复出现SR-IOV的VF和PF同时列出lspci -nn -d 厂商ID按BDF区分这张表的价值在于不要只盯着lspci的输出它在很多问题里只是“侦探”真正的线索在dmesg和系统日志里。4.2 内核参数与固件lspci 背后的隐形开关很多PCIe问题并不是硬件坏了而是内核参数或固件配置不对。比如IOMMU开启时设备DMA地址被重新映射lspci看到的BAR物理地址可能不再是直通的物理地址对AI训练卡来说这会影响是否能把GPU显存直接暴露给用户态。常见内核参数有pcirealloc允许内核重新分配资源、pcinoaer关闭Advanced Error Reporting、pciassign-busses等。不要随便在生产环境加这些参数一定要先理解影响面。还有一个常被忽略的点PCIe AER错误。当设备出现可修正错误时内核会记录到dmesg但lspci本身不会提示。所以排查掉卡问题时正确姿势是同时跑lspci -vvv和dmesg -T | grep -i pci只看一边很容易被误导。如果是NVMe或GPU出现大量AER优先查链路速率、供电、PCIe retimer。4.3 自己写一个最小版“lspci”直接读配置空间为了加深理解建议你亲手写一个最小的读取工具。以下Python代码可以直接读取指定BDF的配置空间前64字节解析出Vendor ID和Device IDimport os, struct bdf 0000:3b:00.0 path f/sys/bus/pci/devices/{bdf}/config try: with open(path, rb) as f: cfg f.read(64) vendor, device struct.unpack_from(HH, cfg, 0) print(fBDF {bdf}: vendor0x{vendor:04x}, device0x{device:04x}) except PermissionError: print(需要root权限读取config)运行前先确认BDF存在ls /sys/bus/pci/devices/。这段代码虽然简单但把“用户态通过sysfs访问配置空间”这条路径走了一遍。如果你想复现lspci -xxx的效果直接把config文件全部读出来按PCI配置空间标准解析即可。写入config则要更小心用setpci命令或ioctl不建议新手直接改。5. 从 lspci 延展到 AI Infra这些内核知识到底怎么用5.1 GPU/NIC 排障里的组合拳lspci从来不是单独用的。我常用的一组命令lspci -nnk | grep -iA3 -E nvidia|mellanox快速看设备ID和内核驱动。lspci -vvv -s 3b:00.0看链路状态LnkSta、带宽、MaxPayload、ASPM。lspci -t看PCIe拓扑理解设备挂在哪个root port下。setpci -s 3b:00.0 COMMAND06修改设备的Command寄存器。这个操作会暂时禁用/启用IO和内存访问千万别在训练任务跑着的时候用。拓扑信息对多卡通信非常关键。AI服务器里GPU和NIC的PCIe树结构直接决定跨卡通信是否经过CPU、是否需要抢占同一根root port带宽。lspci -t只能看到静态拓扑要完全理解带宽瓶颈还得结合nvidia-smi topo -m看实际互联矩阵。5.2 内核版本、驱动与固件的三角关系AI Infra日常最多的问题之一升级内核后NVIDIA驱动失效。这是因为NVIDIA等GPU驱动以内核模块形式动态加载模块必须和内核版本、内核配置严格匹配。新的内核可能改变了PCI子系统的内部API老模块无法加载。lspci这时依然能显示设备但“Kernel driver in use”会变成空dmesg里往往有“version magic”或“unknown symbol”报错。固件层面设备自身的VBIOS、网卡固件也需要与驱动配合。lspci读取的Class Code、子系统ID可能与固件版本相关。遇到奇怪的资源冲突先检查固件版本再检查内核参数最后才考虑硬件故障。这个顺序能省下很多无用功。5.3 值得继续深挖的几个方向如果你看完这篇想去读内核源码我建议按这个顺序来drivers/pci/probe.c了解PCI设备枚举和资源分配。drivers/pci/access.c了解config空间访问的各种方法。fs/kernfs/理解sysfs的读写路径。drivers/pci/quirks.c看真实世界里硬件bug如何被内核兼容。另外内核里的file_operations动态替换机制也是理解容器安全、透明加密、审计系统的一把钥匙。这些年内核社区提供了更多偏好的方式比如LSM、fprobe、tracepoint但底层f_op的替换思路仍然被不少产品使用检查这类行为也成了我排查诡异问题时的保留节目。做AI Infra不只是会敲命令能看懂内核在这一层做了什么排障效率是真的不一样。最后聊一点我自己的体会。lspci这个命令太常用以至于我刚入行时也觉得它没什么可研究的。直到有次线上GPU“消失”lspci明明看得到设备但nvidia-smi报找不到我顺着sysfs路径一层层剥下去发现问题根本不在PCIe枚举而是某个agent在sysfs read路径上加了过滤把特定设备的config读返回了空数据。那次以后我再不把lspci当黑盒。你在实际排障中如果也遇到“命令输出正常但业务异常”的情况不妨先strace一下再看dmesg如果都正常就往内核模块和钩子上想想。这套思路比背多少条lspci参数都管用。好了Day 11的“每日一问”就聊到这儿。如果你也在做AI Infra相关的工作欢迎把你遇到的lspci怪事翻出来对照看看大概率能发现一些平时被忽略的线索。
返回列表