把一台陌生的设备插到Linux机器上,我的第一反应永远是先跑两条命令:lspci和lsusb。这俩命令看起来只是“列出PCI设备和USB设备”,但真正熟练的人能从中读出设备的厂商、型号、子系统、端口拓扑、驱动绑定状态,甚至通过它们把“驱动装不上”“设备不认”这一类玄学问题拆解得明明白白。这篇文章我就从自己多年的排障经验出发,带你彻底吃透这两个命令,并理解PCI设备与USB设备在Linux下到底是怎么被“看见”的,适合刚接触Linux硬件排查的运维、嵌入式开发和爱折腾硬件的玩家。
1. 为什么排查硬件问题第一步总是lspci和lsusb
1.1 从两个最经典的“设备列表”命令说起
先明确一个概念:lspci和lsusb不是“Linux系统自己发明的工具”,它们底层做的事情,是读取内核已经枚举好的设备信息。简单说,只要硬件在物理上连接、总线控制器正常工作,Linux内核就会在启动或热插拔时通过PCI总线或USB总线去扫描设备,然后把信息整理到内核的数据结构里。lspci和lsusb只是把这些信息用人类可读的格式打印出来。
所以你会看到,即使驱动完全没加载、设备在Windows下是“未知设备”,在Linux下依然有可能用lspci -nn或lsusb看到设备ID。这是我反复向用户强调的一个点:识别设备靠的是总线枚举,而不是驱动。只要设备回应了总线上的查询请求,它就会出现在列表里。有了这个前提,后面所有排查才有阶段可依。
1.2 lspci和lsusb到底在读什么
要深入理解,得知道它们背后有pci.ids和usb.ids这两个数据库文件。这两个文件维护了所有已知厂商ID、设备ID与名称的对应关系。发行版一般会把它们放在/usr/share/hwdata/或/usr/share/misc/下。当你运行lspci时,它先读取/sys/bus/pci/devices/下的每个设备目录,取出vendor、device等信息,再通过数据库翻译成“Intel Corporation”“Realtek Semiconductor Co., Ltd.”等字符串;如果数据库没有对应记录,就会退回到裸的十六进制ID。
同样的,lsusb通过读取usbfs或sysfs获取USB设备信息,再查询usb.ids显示厂商和产品名。这也是为什么有时候同一台设备,今天显示名字,明天显示ID 1bc0:0055,多半是你hwdata包更新了或者设备ID被新收录了。
1.3 适合哪些场景,能解决哪些问题
我总结了三个最常用的场景:
- 识别未知硬件:不知道这个PCIe插槽上的卡是什么,不知道这个USB加密狗是什么芯片,插上Linux跑一下指令就能得到ID。
- 判断驱动是否加载:通过
lspci -k看Kernel driver in use字段;通过lsusb -t看设备是否出现在USB树上,用dmesg确认驱动绑定。 - 定位枚举失败:USB设备插上后
lsusb里根本没有,说明枚举直接失败了。此时问题多半在物理层、供电或固件,你可以通过dmesg看到错误码来判断。
所以说,这两个命令就是我们这些“硬件赤脚医生”的听诊器和体温计。下面直接进入正题,看看它们的输出到底怎么读懂。
2. 解密lspci输出:一段字符串里藏了整个PCI设备的家底
2.1 一段真实lspci输出的逐字段拆解
先上最常见的输出:
$ lspci 00:00.0 Host bridge: Intel Corporation Device 9b53 (rev 03) 00:02.0 VGA compatible controller: Intel Corporation UHD Graphics 630 (rev 02) 00:14.0 USB controller: Intel Corporation Cannon Lake PCH USB 3.1 xHCI Host Controller (rev 10) 03:00.0 Ethernet controller: Realtek Semiconductor Co., Ltd. RTL8111/8168/8411 PCI Express Gigabit Ethernet Controller (rev 15)每条记录的格式是:总线号:设备号.功能号 设备类别: 厂商名 设备名 (修订号)。以03:00.0为例,03是PCI总线编号,00是设备编号,.0是功能编号。在多功能设备里,同一个物理设备可以有多个功能,比如一块显卡既有VGA功能,也可能有Audio功能,功能号就会不同。
设备类别是个很重要的信息,比如Ethernet controller、SATA controller、USB controller。当你看到USB controller里有xHCI字样,说明这是USB 3.x的控制器;如果是EHCI,则是USB 2.0控制器。结合lspci能看到系统里有哪些PCIe插槽和哪些板载设备。
2.2 用 -v、-vv 查看驱动绑定和资源
很多情况下知道设备名还不够,还想知道它目前用了什么驱动、内存地址范围、中断号。lspci -v能给出这些信息。同样拿上面的Realtek网卡举例:
$ lspci -v -s 03:00.0 03:00.0 Ethernet controller: Realtek Semiconductor Co., Ltd. RTL8111/8168/8411 PCI Express Gigabit Ethernet Controller (rev 15) Subsystem: Gigabyte Technology Co., Ltd Onboard Ethernet Flags: bus master, fast devsel, latency 0, IRQ 19 Memory at f7c00000 (64-bit, non-prefetchable) [size=4K] I/O ports at f000 [size=256] Capabilities: [50] Power Management version 3 Kernel driver in use: r8169 Kernel modules: r8169注意Subsystem这行特别重要。它表示这块网卡虽然芯片厂商是Realtek,但实际做板卡的是技嘉,子系统ID就能反映主板或设备的具体设计。如果驱动加载失败,Kernel driver in use可能会显示为空,但Kernel modules告诉你系统里有哪些可用驱动模块。
-vv会比-v显示更多能力集信息,比如PCIe链路速度和宽度,通常查链路问题时用得上。比如你的NVMe固态跑在x1而不是x4,通过lspci -vv看链路宽度就能发现插槽带宽不足。
2.3 用 -n 和 -nn 查看厂商ID与设备ID
直接运行lspci可能因为pci.ids版本老而无法显示设备名,或者你想后续用ID去搜索引擎查资料,这时候就需要把ID打在脸上:
$ lspci -nn 03:00.0 Ethernet controller [0200]: Realtek Semiconductor Co., Ltd. RTL8111/8168/8411 PCI Express Gigabit Ethernet Controller [10ec:8168] (rev 15)方括号里的10ec:8168就是PCI厂商ID与设备ID。10ec是Realtek,对应usb.ids里的v, 设备ID8168表示具体芯片型号。你可以在https://pci-ids.ucw.cz/或系统自带的ID库中查到这个组合。
-n是全数字格式,不显示厂商字符串,可以用来精确过滤。比如在网络社区求助时,给出lspci -nn的输出,别人一看便知你的网卡芯片是10ec:8168,驱动该选r8169还是r8168,一目了然。
2.4 实际应用:板载Realtek网卡驱动加载问题
我遇到过一个很典型的案例:一台老机器,lspci -nn显示网卡是10ec:8168,但系统里NetworkManager就是看不到有线连接。执行lspci -k -s 03:00.0得到:
Kernel driver in use: r8169明明驱动在,但网口还是不通。后来用ethtool查链路发现down,手动ip link set up之后才恢复。另一个常见情况是内核里同时有两个驱动模块,比如r8169和r8168冲突,导致加载顺序不对。此时可以通过lspci -k看到底哪个驱动抢占了设备,再用modprobe.blacklist处理。这就是lspci在驱动排查中的价值:它先告诉你设备在不在总线上,再告诉你驱动去没去认领它。
3. 解密lsusb输出:USB设备枚举的完整故事
3.1 一条lsusb输出里的关键信息
lsusb的默认输出比lspci简洁:
$ lsusb Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub Bus 001 Device 003: ID 0403:6001 Future Technology Devices International, Ltd FT232 Serial (UART) IC Bus 001 Device 008: ID 1bc0:0055 (null)前三列Bus 001 Device 003表示这个设备挂在系统中的第1条USB总线(通常对应一个USB控制器),且是这条总线上被枚举到的第3个设备。这个编号是临时的,每次重新插拔都可能变。
第四列ID 0403:6001才是关键,这是USB描述符里的idVendor:idProduct。0403是FTDI(Future Technology Devices International),6001是FT232系列芯片。很多USB转串口、USB转TTL模块用的都是这个芯片。接下来是缓存里查到的厂商名和设备名。如果显示(null)或Unknown,说明ID库没有收录,但并不意味着设备不能被识别,可能是太新或太偏门。
3.2 用 -t 查看USB设备树,理解Hub与设备层次
USB和PCI不同,USB天然是星型拓扑:根Hub在主机控制器里,下面可以挂各种设备,设备还可以是Hub,继续往外扩。lsusb -t可以把这棵家族树画出来:
$ lsusb -t /: Bus 02.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/6p, 5000M |__ Port 3: Dev 2, If 0, Class=Mass Storage, Driver=usb-storage, 5000M /: Bus 01.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/12p, 480M |__ Port 2: Dev 3, If 0, Class=Vendor Specific Class, Driver=ftdi_sio, 480M |__ Port 5: Dev 8, If 0, Class=Human Interface Device, Driver=usbhid, 12M从这棵树里你能直观看到:哪个设备接在哪个Hub的哪个端口上,驱动是什么,当前协商的速率是多少。注意Driver=ftdi_sio,这就是FT232R设备在Linux下被识别为串口的关键驱动。如果某台设备没有Driver=,说明内核里没有匹配的驱动,或者设备总线地址还没稳定。
速率项也能发现很多线索:一棵USB 2.0 Hub下的设备最大480M,某些USB 2.0接口只有12M,是因为设备枚举成了Full Speed,常见于老式低速设备或线材质量问题。lsusb -t是判断链路上限的利器。
3.3 USB设备描述符请求失败是什么鬼
用户最常见的报错词是“未知USB设备(设备描述符请求失败)”。这其实是从哪里来的?Windows设备管理器里这么写,Linux的dmesg里则会出现类似:
usb 1-4: new full-speed USB device number 6 using xhci_hcd usb 1-4: device descriptor read/64, error -71 usb 1-4: device descriptor read/64, error -71 usb 1-4: new full-speed USB device number 7 using xhci_hcd usb 1-4: device descriptor read/64, error -32这意味着主机控制器给设备发GET_DESCRIPTOR请求,但设备没有正确应答。此时lsusb里往往根本看不到这个设备,或者只能看到一个孤零零的Bus 001 Device 007: ID 0000:0000之类。为什么会出现?我总结了几类主要原因:
- 电源不足:USB Hub供电不够,设备复位后无法稳定响应。
- 线材和接触问题:USB线过长或内部断裂,造成信号质量差,尤其是USB 3.0高频信号。
- 设备固件bug:设备在枚举初期枚举失败,需要重新上电甚至强制用USB 2.0端口。
- 静电损坏:这个无解,只能换芯片。
排查时,先换USB口、换线、拔掉其他负载;再用dmesg -w实时看插拔瞬间的内核日志,错误码能帮判断方向。比如error -71是EPROTO协议错误,error -110是超时,error -62是系统调用超时之类的,可以通过错误码反查问题。
3.4 进阶:用lsusb -v查看接口描述符
lsusb -v是完整转储USB设备的所有描述符,非常长,但也是理解“这设备到底能干什么”的最终依据。比如FT232R设备,你会看到:
Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 idVendor 0x0403 Future Technology Devices International, Ltd idProduct 0x6001 FT232 Serial (UART) IC bcdDevice 4.00 iManufacturer 1 FTDI iProduct 2 USB <-> Serial iSerial 3 FT8ZNV0B bNumConfigurations 1 Configuration Descriptor: Interface Descriptor: bInterfaceClass 255 Vendor Specific Class bInterfaceSubClass 0 bInterfaceProtocol 0 iInterface 2 USB <-> Serial Endpoint Descriptor: bmAttributes 2 Bulk注意bInterfaceClass 255 Vendor Specific Class,说明这个设备不是标准CDC类型,而是FTDI私有协议,因此必须用FTDI驱动。通过lsusb -v可以彻底看破那些“厂商自定义设备”的底牌:接口类、端点数量、传输类型(中断/批量/等时)全在这里。比如一个USB HID设备,你会在Interface Descriptor里看到bInterfaceClass 3 Human Interface Device;一个摄像头通常是bInterfaceClass 14 Video。当你想知道这个USB设备走的是什么协议,不必拆开外壳,描述符已经交代了一切。
4. 从设备ID到驱动:一条把PCI和USB串起来的排查主线
4.1 驱动匹配机制:PCI和USB本来就很像
很多人把PCI和USB当成两个完全不相关的东西,但从驱动匹配的角度看,它们的逻辑几乎一样:总线上设备报告身份ID,驱动声明自己支持哪些ID,内核负责牵线搭桥。PCI靠vendor:device:subvendor:subdevice匹配,USB靠idVendor:idProduct,有时也看bcdDevice和接口类。
拿Linux驱动来说,比如网卡驱动源码里会有一个表:
static const struct pci_device_id rtl8169_pci_tbl[] = { { PCI_VDEVICE(REALTEK, 0x8129), 0 }, { PCI_VDEVICE(REALTEK, 0x8136), 0 }, ... };USB驱动则用usb_device_id表:
static struct usb_device_id ftdi_sio_ids[] = { { USB_DEVICE(0x0403, 0x6001) }, ... };所以,当lsusb显示一个设备ID但驱动没加载时,本质是“内核里没有驱动模块声明认识这个ID”,或者驱动模块没被自动加载。理解了这一层,就不会再迷信网上所谓的万能驱动了。
4.2 lspci -k 和 lsusb -t 结合dmesg定位驱动加载状态
排查时必须把三条信息串起来看:
lspci或lsusb确认设备是否被识别,拿到ID;dmesg看内核日志,找设备和驱动的交互记录;lsmod看模块是否加载,modinfo看模块支持的ID表。
举一个我自己亲历的案例。有一块PCIe转USB3.0的扩展卡,插上后系统里USB3.0接口全部不可用。lspci -nn显示:
02:00.0 USB controller [0c03]: ASMedia Technology Inc. ASM1142 USB 3.1 Host Controller [1b21:1242]lspci -k显示:
Kernel driver in use: xhci_hcd看起来正常,但设备插上去就是不行。再看dmesg | grep -i usb,发现xhci_hcd报了大量“error -110 timeout”。最后查到一个已知问题:ASM1142在部分主板上需要开启PCIe Native Power Management,否则会进入低功耗状态导致卸载失败。这是硬件固件与内核设置的冲突,而不是驱动缺失。所以你看,驱动在、设备也在,链路两头的握手失败同样会导致“看不到设备”。
4.3 解决“USB转串口驱动装不上”的经典五步法(FT232R为例)
FT232R这类芯片可以说是USB转串口界的扛把子,Linux内核自带ftdi_sio模块,大多数发行版插上就能用。但总有例外。如果遇到在/dev/ttyUSB0下找不到设备,我通常按这个顺序来查:
第一步:确认设备被枚举。
lsusb | grep 0403如果能输出ID 0403:6001,说明USB枚举成功,问题出在驱动绑定或权限。
第二步:查内核日志。
dmesg | tail -30正常情况会有usb 1-x: FTDI USB Serial Device converter now attached to ttyUSB0。如果只看到设备描述符错误,回到第3节提到的供电和物理链路检查。
第三步:查看驱动模块加载情况。
lsmod | grep ftdi_sio如果没加载,试sudo modprobe ftdi_sio。加载后马上看dmesg,确认驱动匹配成功。
第四步:检查设备节点和权限。
ls -l /dev/ttyUSB*如果没有ttyUSB,说明内核没有注册串口。如果存在但无法打开,检查用户是否在dialout组里。很多所谓“驱动装不上”,其实只是当前用户没权限访问/dev/ttyUSB0。
第五步:排查与板子的接线。有些“FT232R模块”虽然用的FT232芯片,但板子上的TXD/RXD标反了,或者芯片被防静电电路破坏,导致/dev/ttyUSB0出现但收发没有数据。这时可以用回环测试:把TXD和RXD短接,在minicom里敲键盘看是否回显。这一招能快速区分是驱动问题还是模块硬件问题。
4.4 解决“板载PCIe网卡在系统里不出现”的排查路径
和USB设备相比,PCIe设备一般不能热插拔,出问题的表现通常是一开机就根本没有设备。遇到这种问题:
lspci先看整个PCI设备树里有没有这个设备。如果完全没有,进入BIOS看是否被禁用,或者插槽物理接触不良。- 如果有设备但
lspci -k没有驱动使用,查驱动模块是否支持该ID。打个比方,旧内核的r8169驱动可能不支持新出的RTL8125,需要升级内核或换用r8168厂商驱动。 - 如果驱动绑定失败,
dmesg | grep -i pci会显示probe失败原因。比如固件缺失,常见于新的WiFi6无线网卡,需要机器的linux-firmware包支持。
这种排查思路和USB几乎一致:总线有没有看到设备 -> 驱动有没有匹配 -> 驱动运行是否报错。记住这条主线,再陌生的硬件都有章可循。
5. 完整实战:一个USB加密狗兼容性问题,从认出到解决
5.1 从描述符看设备类型:VID_1BC0 PID_0055到底是什么
开头提到的加密狗,具体ID是1bc0:0055。1bc0这个厂商ID在usb.ids库里一般没有收录完整名称,所以lsusb显示(null)。但这并不影响我们判断它的功能。执行:
$ lsusb -v -d 1bc0:0055你会看到它暴露的接口描述符。如果遇到HID接口,说明这个加密狗对系统来说是个键盘/鼠标类设备;如果遇到Vendor Specific接口,说明它需要专用软件通过bulk端点通信。大多数加密狗之所以能“插上没反应”,正是因为它根本不向系统暴露标准存储或串口功能,而是等待某个应用软件来“认领”。这一步看描述符就能确认它不是普通U盘。
5.2 抓取USB枚举数据:用lsusb -v看配置、接口、端点
比如某狗被系统分配了接口0,HID协议,lsusb -v会显示:
Interface Descriptor: bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 1 bInterfaceClass 3 Human Interface Device bInterfaceSubClass 0 bInterfaceProtocol 0 iInterface 0 HID Device Descriptor: bcdHID 1.11 Endpoint Descriptor: bmAttributes 3 Interrupt wMaxPacketSize 0x0008 1x 8 bytes这就解释了为什么Windows设备管理器里会显示“USB输入设备”,甚至会被识别成“USB键盘”。很多老式加密狗就是走HID通道和上位机通信的,如果它的HID report描述符被系统读取失败,就会变成“描述符请求失败”。
5.3 当系统不识别时,usbmon抓包与sysfs状态检查
如果加密狗插上后lsusb压根没有设备,那就要用更底层的usbmon去抓总线原始数据包。内核一般自带usbmon支持,但要先加载模块:
sudo modprobe usbmon sudo cat /sys/kernel/debug/usb/usbmon/0u | tee usbmon.log然后在另一个终端插拔设备。日志中会出现URB提交和完成事件,能看出设备是否只是在枚举阶段失败,还是根本没响应。对于不熟悉原始URB的人,可能感觉看不懂,但至少能从数据包的数量和返回错误码看出“设备有回应”或“主机不断重试”——这就是数据包级的事实。
也可以看sysfs里的状态:
cat /sys/bus/usb/devices/1-4/descriptors | xxddescriptors文件保存了设备枚举后缓存的原始描述符字节,即使设备后来断开,信息也能查到。有些情况下设备在描述符里写了错误的长度或校验值,导致系统拒绝加载,通过这个原始字节流能发现厂商固件bug。
5.4 我的处理建议:怎么判断是设备问题、驱动问题还是权限问题
我处理这类加密狗问题时,内心会按简单概率排序:
- 权限问题:加密狗对应的应用程序需要root权限,或需要加入某个用户组,占了30%的“不识别”。
- 设备描述符异常:尤其是一些国产加密狗,芯片固件里描述符不标准,导致系统无法正确解析。可以试着在VMware或Windows下对比,如果Windows也报描述符请求失败,基本就是设备问题。
- 驱动注册顺序:公司安全软件往往会注册驱动钩子,在Linux下就是某些内核模块影响了usbhid。可以试试
lsmod | grep usbhid是否正常加载。
最关键的排查逻辑是:lsusb能看到ID,说明枚举成功;lsusb看不到ID,先解决物理层和枚举;能看到ID但没有接入权,后面全是权限和软件层面的问题。
6. 一些能让你用得更顺手的补充技巧
6.1 原始配置空间与描述符的安全解读
lspci -xxx可以dump出PCI配置空间前256字节的十六进制,lspci -xxxx可以dump扩展配置空间,lsusb -v则能完整导出描述符。对于做驱动开发或搞硬件逆向,这些原始数据很有用,但我建议普通用户别过度依赖,因为看到一堆寄存器值反而容易误导。我见过有人根据lspci -xxx某个字节非零就判断硬件损坏,其实那可能只是保留位或者软件可写的状态。要理解原始配置,必须结合PCIe规范和芯片手册,这是另一重深度了。
6.2 交叉参考:Windows设备管理器与Linux下ID的一致性
Windows设备管理器里的“硬件ID”同样包含VID/PID,比如USB\VID_0403&PID_6001。这和Linux下lsusb打印的0403:6001完全对应。遇到问题求助时,如果Windows显示“设备描述符请求失败”,Linux下大概率也能看到lsusb -d 0403:6001或者dmesg里的错误。所以跨系统排查并不冲突,反而能相互印证:如果Windows和Linux都不识别,那就不是系统兼容问题,是设备本身坏了或线缆问题;如果Linux正常Windows不正常,多半是Windows驱动和固件冲突。
6.3 批量收集硬件信息的小脚本思路
运维需要采集一大批机器的硬件配置时,手工一条条敲太费劲。可以写个简单脚本,把关键ID汇总成表格:
#!/bin/bash echo "===== PCI Devices =====" lspci -nn | awk '{printf "%s | %s | ", $1, $2} {for(i=3;i<=NF-2;i++) printf "%s ", $i; print $NF}' echo "" echo "===== USB Devices =====" lsusb当然实际运维更推荐用lshw或dmidecode,但如果你只想快速列出设备ID和名称,用lspci -nn加lsusb足够。配合-d参数可以过滤特定厂商,比如:
lspci -nn -d 10ec: lsusb -d 0403:这种写法在写udev规则、做硬件白名单时特别有用。
在实际工作中,我见过太多人一遇到“USB转串口不识别”就想着去下载各种驱动,一遇到网卡不通就重装系统。但如果你肯花十分钟先跑一下lspci和lsusb,把设备ID、驱动绑定、内核日志这三件事查清楚,多半都能找到真正的症结所在。学会这两个命令之后,你会发现你不再怕未知设备,甚至能从一个VID:PID的组合里推断出设备的通信方式、驱动类型,这套“从总线看世界”的思路,比任何驱动工具都更持久管用。