第4章 Linux下的两把利器:lspci 与 setpci
讲到配置空间读取,Linux 上最常用的就是 lspci 和 setpci。这两个命令我几乎每天都在用。一个负责看,一个负责改。配合起来,调试 PCIe 链路状态特别顺手。
4.1 lspci —— 读出配置空间的“眼睛”
lspci 默认会扫描所有 PCI/PCIe 设备,然后把配置空间头部信息吐出来。但默认输出太简略,想看到 Link Status,得加参数。
我个人习惯这样用:
# 列出所有设备,带上详细配置空间
lspci -vvv
# 只看某个 BDF 的设备
lspci -s 01:00.0 -vvv
# 用十六进制直接 dump 配置空间
lspci -s 01:00.0 -xxx
-vvv 会把 Capabilities 和 Extended Capabilities 都展开。你想想看,Link Status 藏在 PCIe Capability 的寄存器里,用这个参数一目了然。
我在项目中遇到过一块 NVMe 盘老是降速。直接跑 lspci -s 03:00.0 -vvv,在 "LnkSta:" 那一行清晰写着 "Speed 5GT/s, Width x2"。可这盘明明支持 8GT/s x4 啊!问题一下就锁定了——链路协商出了问题。
Speed 8GT/s —— 当前链路速率
Width x4 —— 当前链路宽度
(括号里是 Capable 值,比如 8GT/s, x4)
4.2 setpci —— 直接读写配置空间的“手”
lspci 只能看,不能改。setpci 就不一样了,它可以读写指定设备的配置空间寄存器。注意,修改配置空间可能让系统宕机,尤其是修改那些控制寄存器。我建议先在测试机上试。
基本用法:
# 读寄存器(十六进制)
setpci -s 01:00.0 CAP_ID.BYTE
# 写寄存器
setpci -s 01:00.0 CAP_ID.BYTE=0x01
这里的 CAP_ID 是 Capability 的偏移量或名称(比如 PCIe Capability 的 ID 是 0x10)。.BYTE 表示你要读/写几个字节(.B 一个字节,.W 两个字节,.L 四个字节)。
举个例子,想读 PCIe Capability 里的 Link Status 寄存器(通常偏移 0x12,2 字节):
setpci -s 01:00.0 CAP_EXP+0x12.W
不过呢,CAP_EXP 这个别名在不同发行版里可能不统一。我更喜欢直接用十六进制偏移:
# 先查 PCIe Capability 的指针位置(在配置空间 0x34 字节处)
setpci -s 01:00.0 0x34.B
# 假设得到 0x40,那么 Link Status 就在 0x40 + 0x12 = 0x52
setpci -s 01:00.0 0x52.W
4.3 实战:抓出链路降速的根因
好,我们走一遍完整流程。假设服务器上有一张 GPU,感觉带宽不对。
- 定位设备:
lspci | grep -i nvidia,拿到 BDF,比如 02:00.0。 - 看链路状态:
lspci -s 02:00.0 -vvv | grep -i lnk。看到 “LnkSta: Speed 2.5GT/s, Width x1”。 - 对比能力:同一行括号里写着 “Capable: 8GT/s, Width x16”。说明链路协商只谈到了最低档。
- 检查链路训练:查看 Link Status 寄存器的第 11 位(Link Training),如果是 1 表示正在训练。使用
setpci -s 02:00.0 0x52.W,把结果换算成二进制,看 bit 11。 - 尝试复位:如果怀疑链路卡死,可以写 Link Control 寄存器(偏移 0x10)的 bit 5(Retrain Link)让链路重新训练。但先读出来,只改一个 bit:
setpci -s 02:00.0 0x50.W # 读 Link Control setpci -s 02:00.0 0x50.W=0x0020 # 只置 bit 5,其他位不变
4.4 两个工具对比
| 特性 | lspci | setpci |
|---|---|---|
| 默认读取范围 | 配置空间头部 + 常用 Capability | 任意偏移(需手动指定) |
| 写入能力 | 无 | 可写,有风险 |
| 数据格式 | 解析好的文本(速度/宽度/状态) | 原始十六进制数字 |
| 最佳用途 | 快速诊断、日常巡检 | 深度调试、修改控制寄存器 |
说白了,日常排查先用 lspci 看,发现问题但想进一步操作,再上 setpci。我很少直接用 setpci 读 Link Status,因为 lspci -vvv 已经解析得很清楚。倒是写操作,比如“强制链路复位”或者“模拟热插拔事件”,setpci 才是唯一途径。
4.5 知识结构一览
下面这张图总结了本章的核心逻辑:
4.6 避坑指南
- 大小端问题? PCIe 配置空间是小端字节序。setpci 读出来的数字,比如 0x1201,高位是偏移+1,低位是偏移+0。要看 Link Width 在 bit 20-23,其实对应寄存器的第 2 个字节。我刚开始搞混过,以为宽度是 1,结果是 4,因为字节颠倒了。
- 权限不够:普通用户跑 lspci 没问题,但 setpci 需要 root。我曾经在调试脚本里忘了加 sudo,结果写操作静默失败,排查了半天。
- 虚拟化环境:如果是 VM 直通设备,lspci 和 setpci 看到的配置空间是虚拟化软件(KVM/Xen)模拟的,不一定反映物理链路真实状态。得在宿主机上跑才准。