1. 从一块网卡说起:PCIe直通与SR-IOV到底在解决什么问题
搞虚拟化的朋友大概率都遇到过这个场景:宿主机上插了一张万兆网卡或者一张计算卡,虚拟机里跑业务却只能走虚拟网桥,吞吐上不去、延迟下不来,CPU还被软中断吃得干干净净。这时候老鸟通常会甩出两个词——PCIe直通和SR-IOV。听起来很硬核,但说白了就是两件事:前者是把整块物理设备"整租"给一台虚拟机,后者是把一块物理设备"隔断成多个独立房间"分租给多台虚拟机。
这两个技术背后牵扯的东西非常多:PCIe总线的枚举与配置空间、IOMMU的地址翻译与中断重映射、ACS对P2P流量的隔离控制、VFIO框架如何把设备安全地交给用户态、以及SR-IOV里PF和VF的层级关系。任何一个环节没搞明白,轻则设备直通失败、虚拟机起不来,重则整机掉卡、链路降速、AER报错刷屏。热词里提到的"掉卡、降speed/lane、aer等问题"、"pcie枚举过程"、"pcie稳定性/兼容性问题",全都是这条链路上的真实痛点。
这篇内容适合谁看?如果你是在做虚拟化平台、云主机底层、DPDK/SPDK高性能网络、或者AI训练集群里做GPU/网卡资源切分的工程师,那这篇基本就是给你写的。如果你只是刚接触PCIe、想搞清楚"为什么我的设备直通进去识别不到",也能从里面找到排查思路。我会尽量把底层原理讲透,同时给出可以直接抄的配置和排查命令,不玩虚的。
需要先说明一点:下面涉及的具体命令、参数、寄存器偏移,都是基于业界常见实践和公开规范整理的通用做法,不同平台(Intel/AMD/ARM)、不同内核版本、不同设备厂商会有差异,实际落地时请以你手上的硬件手册和内核文档为准。
2. 先搞懂PCIe枚举:设备是怎么被"发现"的
2.1 枚举过程的本质是一棵树的深度优先遍历
很多人一上来就研究直通,结果连设备在系统里怎么出现的都没搞明白。PCIe的拓扑是一棵树:Root Complex是根,下面挂Root Port、Switch、Endpoint。系统启动时,固件(BIOS/UEFI)或者操作系统会从总线0、设备0、功能0开始,逐个扫描每个总线号下的设备号,读Vendor ID。如果Vendor ID不是0xFFFF,说明这个位置有设备,再去读Header Type判断它是桥还是端点。如果是桥,就给它分配一个新的总线号,继续往下扫。
这个过程就是PCIe枚举。它决定了你系统里lspci能看到什么。理解枚举对直通至关重要,因为直通的前提是设备必须被正确枚举出来,并且挂在一个支持隔离的Root Port或Switch端口下。
你可以用下面这条命令看整棵拓扑树:
lspci -tv输出会以树形展示桥和设备的关系。如果发现某张卡挂在了一个来路不明的桥后面,或者多个设备共享同一个Root Port且没有ACS支持,那直通的隔离性就要打问号了。
2.2 配置空间:直通时真正被"搬走"的东西
每个PCIe设备都有一段配置空间,传统PCI是256字节,PCIe扩展到了4KB。前64字节是标准头,里面有Vendor ID、Device ID、Command、Status、BAR(Base Address Register)等。BAR决定了设备需要多少MMIO空间和IO空间,枚举时固件或内核会给BAR分配物理地址。
直通的时候,虚拟机要看到的其实是"设备原样的配置空间",但BAR指向的物理地址需要经过IOMMU重映射才能被虚拟机安全访问。这就是为什么IOMMU是直通的基石——没有它,虚拟机里的驱动直接拿物理地址去访问,等于让租客拿着整栋楼的钥匙。
查看某个设备的配置空间:
lspci -s 01:00.0 -xxx-xxx会 dump 前256字节,-xxxx能看完整的4KB。排查BAR分配异常、Capability结构错乱时非常有用。
2.3 枚举阶段的常见坑:掉卡与降速
热词里"掉卡、降speed/lane"是枚举和链路训练阶段的经典问题。链路训练(Link Training)发生在设备上电后,PCIe双方协商链路宽度(lane)和速率(speed)。如果信号完整性差、金手指氧化、供电不稳,就可能协商到x1或者Gen1,甚至直接训练失败导致设备不出现。
排查链路状态:
lspci -s 01:00.0 -vvv | grep -i "LnkCap\|LnkSta"LnkCap是链路能力(最大能到多少),LnkSta是当前状态。如果Cap是x16 Gen4,Sta却只有x4 Gen1,那基本就是物理层或者插槽的问题。这时候别急着怀疑驱动,先换插槽、擦金手指、检查供电。
注意:有些主板会把CPU直连的x16拆分成x8+x8给两个插槽,如果你只插一张卡却只跑出x8,先查主板手册的lane分配策略,不一定是故障。
3. IOMMU与ACS:隔离性的两道命门
3.1 IOMMU不只是地址翻译,还有中断重映射
IOMMU(Intel叫VT-d,AMD叫AMD-Vi)干两件事:一是DMA重映射,把设备发起的DMA地址从IOVA翻译成物理地址,并且限制设备只能访问被授权的区域;二是中断重映射(Interrupt Remapping),把设备的中断请求安全地投递到指定CPU和虚拟机。
没有IOMMU,设备直通就是灾难——设备可以DMA到任意物理内存,虚拟机之间毫无隔离。开启IOMMU通常需要在BIOS里打开VT-d/AMD-Vi,内核启动参数加:
intel_iommu=on iommu=ptiommu=pt是pass-through模式,对没被直通的设备用恒等映射,减少性能开销。AMD平台对应amd_iommu=on。
验证IOMMU是否生效:
dmesg | grep -i -e DMAR -e IOMMU看到"DMAR: IOMMU enabled"之类的字样才算成功。
3.2 ACS:决定P2P流量能不能被隔离
ACS(Access Control Services)是PCIe的一个Capability,它控制Peer-to-Peer流量能不能在Switch内部直接转发,还是必须上送到Root Complex。为什么直通要关心ACS?因为如果两个设备挂在同一个没有ACS的Switch下,它们之间的P2P DMA可以绕过IOMMU的隔离,A设备能直接读写B设备甚至B设备背后的内存,隔离就形同虚设。
检查设备是否支持ACS:
lspci -s 01:00.0 -vvv | grep -i ACS如果显示ACSCap但ACSCtl里没有开启,可以用内核参数强制:
pci=acs_mask=01:00.0或者更常见的做法是用pcie_acs_override补丁强制拆分IOMMU组。但这里要提醒一句:ACS override是"我知道有风险但我接受"的操作,它牺牲了部分安全性换取分组灵活性,生产环境慎用。
3.3 IOMMU分组:直通的最小单位
IOMMU以"组"(group)为单位做隔离,同一个组里的设备要么一起直通,要么都不直通。查看分组:
for d in /sys/kernel/iommu_groups/*/devices/*; do n=${d#*/iommu_groups/*}; n=${n%%/*} printf 'IOMMU Group %s ' "$n" lspci -nns "${d##*/}" done如果发现你要直通的网卡和一堆无关设备(比如USB控制器、SATA控制器)分在同一个组,那直通就会把那些设备一起带走,宿主机可能直接失去磁盘或键鼠。这时候要么换插槽(换到独立的Root Port下),要么上ACS override,要么放弃。
实操心得:优先通过物理插槽调整来获得干净的IOMMU分组,这比打补丁靠谱得多。服务器主板通常有多个CPU直连的PCIe插槽,换一个往往就分开了。
4. VFIO:把设备安全地交给用户态
4.1 VFIO的定位与工作模型
VFIO(Virtual Function I/O)是Linux内核的一套框架,它让用户态程序(比如QEMU)能够安全地访问物理设备。它依赖IOMMU做隔离,把设备的各种资源(MMIO区域、配置空间、中断)抽象成文件描述符,用户态通过ioctl来操作。
VFIO的核心组件:
- vfio-pci:绑定设备的驱动,接管设备后原驱动不再工作
- vfio_iommu_type1:IOMMU类型1的容器驱动,管理IOMMU映射
- vfio platform:用于平台设备
直通时,设备从原驱动解绑,绑定到vfio-pci,然后QEMU通过-device vfio-pci把它交给虚拟机。
4.2 绑定与解绑的完整操作
先确认设备ID:
lspci -nns 01:00.0假设输出是01:00.0 0200: 8086:10fb,厂商8086,设备10fb。
解绑原驱动:
echo 0000:01:00.0 > /sys/bus/pci/devices/0000:01:00.0/driver/unbind绑定vfio-pci:
echo 8086 10fb > /sys/bus/pci/drivers/vfio-pci/new_id或者用driver_override方式更稳妥:
echo vfio-pci > /sys/bus/pci/devices/0000:01:00.0/driver_override echo 0000:01:00.0 > /sys/bus/pci/drivers/vfio-pci/bindQEMU启动参数大致是:
-device vfio-pci,host=01:00.0,id=net04.3 直通后设备识别不到的排查顺序
这是最高频的问题。按下面顺序查:
- 宿主机
lspci能不能看到设备?看不到就是枚举/物理层问题 - IOMMU组是否干净?
dmesg有没有"not in a group"报错 - 设备是否成功绑定vfio-pci?
lspci -k看Kernel driver in use - QEMU是否报"Failed to bind"或"group not viable"?
- 虚拟机里
lspci能否看到?看不到可能是QEMU参数或BIOS问题 - 虚拟机里能看到但驱动加载失败?多半是BAR映射或中断问题
常见坑:有些设备有多个Function(比如网卡带一个管理功能),只直通其中一个Function会导致整个设备异常。要么全直通,要么用FLR(Function Level Reset)先复位。
5. SR-IOV:一块卡切成多块用的底层逻辑
5.1 PF与VF的层级关系
SR-IOV(Single Root I/O Virtualization)的核心思想是:物理设备(PF,Physical Function)支持创建多个虚拟功能(VF,Virtual Function),每个VF有自己独立的配置空间、BAR、中断,可以被独立分配给虚拟机。PF负责管理,VF负责干活。
一个支持SR-IOV的网卡,PF是01:00.0,创建出来的VF可能是01:00.1、01:00.2……每个VF在系统里都是一个独立的PCIe设备,有独立的BDF。
开启VF:
echo 4 > /sys/class/net/eth0/device/sriov_numvfs这会在PF下创建4个VF。查看:
lspci | grep -i "virtual function"5.2 VF的隔离依赖什么
VF虽然独立,但它和PF共享物理链路和部分硬件资源。隔离性依赖:
- IOMMU:每个VF的DMA必须被独立翻译和限制
- ACS:如果VF之间或VF与PF之间有P2P路径,需要ACS来阻断
- 硬件队列隔离:网卡的TX/RX队列、中断向量必须硬件级隔离
这也是为什么有些廉价网卡号称支持SR-IOV,实际用起来VF之间互相干扰——硬件隔离做得不到位。
5.3 SR-IOV与直通的取舍
| 维度 | PCIe直通 | SR-IOV |
|---|---|---|
| 粒度 | 整设备 | 单VF |
| 隔离性 | 强(独占) | 依赖硬件和IOMMU |
| 密度 | 低(一设备一VM) | 高(一设备多VM) |
| 迁移 | 难 | 相对容易 |
| 性能 | 接近原生 | 接近原生但有共享开销 |
| 适用 | GPU、专用卡 | 网卡、部分加速卡 |
选型逻辑很直接:设备稀缺、要极致性能、不需要多VM共享,就直通;设备贵、要多租户共享、要密度,就SR-IOV。GPU现在也有SR-IOV(比如某些厂商的vGPU方案),但成熟度和网卡比还有差距。
6. 稳定性与兼容性:掉卡、降速、AER的实战排查
6.1 AER报错怎么读
AER(Advanced Error Reporting)是PCIe的错误上报机制。dmesg里出现AER: Corrected error通常无害,但Uncorrected (Fatal)就要重视了。常见的有:
- Bad TLP:链路传输错误,多为信号完整性问题
- Bad DLLP:数据链路层错误
- Receiver Error:接收端物理层错误
- Completion Timeout:设备没在规定时间响应
排查思路:先看是Correctable还是Uncorrectable,再看是哪个设备报的。如果是直通设备报错,先确认是不是ACS/IOMMU配置问题导致设备行为异常。
lspci -s 01:00.0 -vvv | grep -i "AER\|UESta\|CESta"6.2 降速降lane的定位
前面提过LnkCap和LnkSta的对比。如果Sta低于Cap,逐项排查:
- 插槽是否支持该速率和宽度
- 线缆/转接卡是否达标(尤其是M.2转PCIe、Oculink等)
- 主板lane拆分策略
- 设备固件是否需要更新
有些平台还支持动态降速省电,LnkCtl里的ASPM设置会影响。如果确认是ASPM导致的性能波动,可以在内核参数里关掉:
pcie_aspm=off6.3 热插拔与枚举的交互
热词里提到"pcie热插拔功能"。热插拔依赖设备的Hot-Plug Capability和平台的Attention Button/Power Indicator支持。在虚拟化场景里,热插拔常用于动态给虚拟机加设备。但要注意:直通设备的热拔除必须先在虚拟机里安全卸载驱动,再在宿主机操作,否则容易触发AER或者设备状态错乱。
7. 常见问题速查表与避坑清单
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| 虚拟机看不到直通设备 | 未绑定vfio-pci / IOMMU组不干净 | lspci -k、IOMMU分组脚本 |
| 直通后宿主机失去磁盘 | 设备与存储控制器同组 | 换插槽或ACS override |
| 设备频繁掉线 | 供电/信号/ASPM | 换插槽、关ASPM、查AER |
| 链路降速 | 插槽/线缆/拆分策略 | 对比LnkCap与LnkSta |
| VF创建失败 | 硬件不支持/固件未开 | 查/sys/class/net/*/device/sriov_totalvfs |
| 中断不触发 | 中断重映射未开 | 查IOMMU中断重映射日志 |
| 性能不达预期 | 未开iommu=pt / 队列未隔离 | 检查内核参数和队列配置 |
避坑清单:
- 直通前一定先确认IOMMU分组干净,别等虚拟机起不来才回头查
- SR-IOV的VF数量不要超过硬件
totalvfs上限- 生产环境慎用ACS override,它牺牲隔离性
- 直通设备的热拔除要按"虚拟机内卸载→宿主机解绑"的顺序
- 遇到AER先分清Correctable和Uncorrectable,别一上来就换硬件
8. 我个人在实际操作中的几点体会
折腾这套东西这么多年,最大的感受是:大部分直通问题都不是虚拟化层的问题,而是PCIe物理层和固件层的问题。我见过太多人一上来就改QEMU参数、换内核版本,结果最后发现是插槽lane分配不对,或者BIOS里VT-d根本没开。
第二个体会是,IOMMU分组是直通体验的分水岭。分组干净,后面一路顺;分组混乱,你会花大量时间在ACS override和各种workaround上,而且心里始终不踏实。所以选主板和插槽的时候,多花十分钟看手册,能省后面十个小时。
第三个是SR-IOV的VF隔离,别只看厂商宣传。实际压测一下VF之间的带宽干扰、中断延迟,才能知道这块卡到底能不能上生产。有些卡VF数量一多,单个VF的性能断崖式下跌,这种就得重新评估方案。
最后分享一个小技巧:排查直通问题时,养成先看dmesg和lspci -vvv的习惯,把设备的链路状态、ACS、AER、IOMMU组信息一次性抓全,比东查一点西查一点效率高得多。很多时候答案就明明白白写在dmesg里,只是被刷屏淹没了。