
PCIe链路训练这事儿说简单也简单说复杂能让人掉一把头发。我见过太多人遇到链路降速、设备识别异常、AER报错刷屏的时候第一反应就是重启或者换卡但真正能解决问题的往往是一次精准的Retrain操作。这篇内容就是把我这些年处理PCIe链路问题的实战经验整理出来从Retrain到底是什么、寄存器怎么配、setpci怎么用到实际案例中怎么排查和修复全部讲透。不管你是刚接触PCIe的驱动工程师还是已经在做硬件调试的老手应该都能从中找到能直接用的东西。1. Retrain不是万能药但不懂它万万不能1.1 链路训练的本质LTSSM状态机在干什么PCIe的链路训练核心就是LTSSMLink Training and Status State Machine这个状态机在跑。你可以把它想象成两个人打电话先要确认对方在不在线Detect然后协商用什么语言交流Polling再确认语速和口音能不能对上Configuration最后正式通话L0。Retrain就是让这个流程重新走一遍但不一定从最开始的Detect开始。LTSSM的主要状态包括Detect检测链路对端是否存在通过接收器检测电气空闲状态Polling交换TS1/TS2有序集确认双方支持的速率和宽度Configuration协商链路号、通道号、速率等参数完成位锁定和符号锁定L0正常工作状态开始传输TLP和DLLPRecovery链路出错或需要重新协商时进入Retrain主要发生在这个状态Retrain操作通常是从L0进入Recovery然后在Recovery里重新协商速率和宽度再回到L0。这个过程不会重新枚举设备所以对上层软件透明。但如果你操作不当比如在Recovery里卡住了那就可能直接导致链路挂死。注意Retrain和Link Reset是两回事。Link Reset会强制回到Detect状态重新走完整的训练流程设备会重新枚举。Retrain只是重新协商设备不会掉。1.2 什么时候需要Retrain典型场景盘点不是所有链路问题都需要Retrain。我总结了几种典型场景场景一链路速率降级。比如一个Gen3的卡插在Gen3槽位上但链路只跑到了Gen1。这种情况可能是信号完整性有问题也可能是双方在训练时协商失败。Retrain可以强制重新协商有时候能恢复到Gen3。场景二链路宽度不对。x4的卡只识别到x1或者x2。这通常是因为某些Lane的信号质量不达标训练时被降级了。Retrain可以重新尝试所有Lane。场景三AER报错后链路恢复。当链路出现可纠正错误或者不可纠正错误时硬件可能会自动触发Retrain。但有时候自动恢复失败需要手动干预。场景四热插拔后链路不稳定。设备热插入后链路训练可能没有完全成功导致设备识别异常。Retrain可以重新建立稳定的链路。场景五功耗管理退出后链路异常。从L1或者L2低功耗状态退出时链路可能没有正确恢复到L0需要Retrain。1.3 Retrain的两种触发方式硬件自动与软件手动硬件自动Retrain通常由链路错误触发。当LTSSM检测到足够的错误比如连续收到错误的TS1/TS2或者CRC错误超过阈值会自动进入Recovery并尝试重新训练。这个过程对软件完全透明你只会在dmesg里看到AER的报错记录。软件手动Retrain则是通过写寄存器来触发。主要有两种方式通过Link Control Register的Retrain Link位这是PCIe规范定义的标准方式写1到Link Control Register的bit 5硬件会自动开始Retrain。通过Secondary Bus Reset这个更暴力会触发下游设备的复位然后重新训练。但会影响整个下游子树。手动Retrain的好处是可控你可以在确认链路状态后主动触发而不是等硬件自己恢复。坏处是如果链路本身有问题Retrain可能会失败甚至让链路彻底挂死。2. 寄存器配置Retrain的底层操作2.1 Link Control RegisterRetrain的入口Link Control Register是PCIe Capability结构里的一个16位寄存器偏移0x10。它的bit 5就是Retrain Link位。写1触发Retrain硬件完成后会自动清零。读这个位可以判断Retrain是否还在进行中。这个寄存器的其他关键位Bit名称说明0ASPM Control控制ASPM电源管理1Reserved保留2RCBRead Completion Boundary3-4Link Disable禁用链路5Retrain Link触发Retrain6Common Clock Configuration公共时钟配置7Extended Sync扩展同步8-9Clock Power Management时钟电源管理10Hardware Autonomous Width Disable禁用硬件自动宽度调整11Link Bandwidth Management Interrupt Enable带宽管理中断使能12Link Autonomous Bandwidth Interrupt Enable自动带宽中断使能要操作这个寄存器首先得找到PCIe Capability的偏移。用lspci -vv可以看到Capabilities: [a0] Express (v2) Endpoint, MSI 00 LnkCap: Port #0, Speed 8GT/s, Width x4, ASPM L0s L1, Exit Latency L0s 1us, L1 4us LnkCtl: ASPM Disabled; RCB 64 bytes, Disabled- CommClk ExtSynch- ClockPM- AutWidDis- BWInt- AutBWInt- LnkSta: Speed 8GT/s, Width x4, TrErr- Train- SlotClk DLActive- BWMgmt- ABWMgmt-这里的LnkCtl就是Link Control Register。a0是Capability的起始偏移Link Control Register在a00x10的位置。2.2 Link Status Register判断Retrain是否成功Link Status Register偏移0x12也是16位。它的关键位Bit名称说明0-3Link Speed当前链路速率4-9Negotiated Link Width协商后的链路宽度10Undefined未定义11Link Training链路训练进行中12Slot Clock Configuration槽位时钟配置13Data Link Layer Active数据链路层活跃14Reserved保留15Link Autonomous Bandwidth Status自动带宽状态判断Retrain是否成功主要看bit 11Link Training是否清零以及bit 0-3的速率和bit 4-9的宽度是否符合预期。2.3 用setpci读写寄存器实操命令setpci是操作PCI配置空间的利器。基本语法setpci -s BDF offset.width比如要读0000:01:00.0的Link Control Registersetpci -s 01:00.0 a0.w这里a0是偏移w表示16位。读出来是一个十六进制数。要触发Retrain写bit 5setpci -s 01:00.0 a0.w0020但这样写会覆盖其他位。更安全的做法是先读出来然后按位或# 读取当前值 val$(setpci -s 01:00.0 a0.w) # 设置bit 5 new_val$(printf %04x $((0x$val | 0x20))) # 写回 setpci -s 01:00.0 a0.w$new_val不过setpci的写操作是直接写没有读-改-写的原子性。如果其他位有变化可能会被覆盖。所以更推荐用内核提供的接口比如通过sysfs或者debugfs。2.4 通过sysfs触发Retrain更安全的方式Linux内核提供了sysfs接口来操作PCIe链路。在/sys/bus/pci/devices/ /目录下有一个link子目录ls /sys/bus/pci/devices/0000:01:00.0/link/里面通常有current_speed当前链路速率max_speed最大支持速率current_width当前链路宽度max_width最大支持宽度retrain写1触发Retrain触发Retrainecho 1 /sys/bus/pci/devices/0000:01:00.0/link/retrain这个接口是内核封装的会正确处理读-改-写比直接用setpci安全。但要注意不是所有内核版本都支持这个接口。我实测下来4.15以上的内核基本都有。如果找不到retrain文件可以检查内核配置zcat /proc/config.gz | grep CONFIG_PCIEAER确保CONFIG_PCIEAER和CONFIG_PCIEPORTBUS都打开了。3. 实战案例从链路降速到恢复Gen33.1 问题现象Gen3卡跑在Gen1前段时间调试一块FPGA加速卡PCIe Gen3 x8的接口插在服务器的Gen3 x16槽位上。lspci看链路状态LnkSta: Speed 2.5GT/s, Width x8, TrErr- Train- SlotClk DLActive- BWMgmt- ABWMgmt-速率只有2.5GT/s也就是Gen1。宽度倒是x8说明所有Lane都训练上了但速率没上去。dmesg里有一些AER的报错pcieport 0000:00:1c.0: AER: Corrected error received: 0000:01:00.0 pcieport 0000:00:1c.0: AER: PCIe Bus Error: severityCorrected, typePhysical Layer, (Receiver ID) pcieport 0000:00:1c.0: AER: device [10ee:9038] error status/mask00000001/00002000 pcieport 0000:00:1c.0: AER: [ 0] RxErr (First)这是物理层的接收错误说明信号质量有问题。但错误是可纠正的链路没有挂。3.2 排查思路先确认硬件再动软件遇到链路降速我的原则是先确认硬件没问题再动软件。硬件排查包括检查金手指有没有氧化、脏污、划痕检查插槽有没有异物、针脚变形检查供电PCIe槽位的12V和3.3V是否正常检查参考时钟100MHz参考时钟的抖动是否超标检查信号完整性用示波器看TX/RX差分信号的眼图如果硬件没问题再考虑软件层面。这个案例里硬件排查都过了金手指干净插槽正常供电稳定。参考时钟用示波器看了抖动在允许范围内。信号完整性没有条件测但从AER报错来看接收端有错误可能是均衡器设置不对。3.3 Retrain操作从尝试到成功第一次尝试直接用sysfs触发Retrainecho 1 /sys/bus/pci/devices/0000:01:00.0/link/retrain等了几秒再看链路状态LnkSta: Speed 8GT/s, Width x8, TrErr- Train- SlotClk DLActive- BWMgmt- ABWMgmt-速率上到8GT/s了Gen3。但好景不长过了几分钟又掉回Gen1。dmesg里又有AER报错。这说明Retrain虽然能暂时恢复但链路本身不稳定。需要进一步调整。3.4 调整均衡器让链路稳定在Gen3PCIe Gen3引入了均衡器Equalizer包括发送端的前馈均衡FFE和接收端的判决反馈均衡DFE。均衡器的设置会影响信号质量。如果均衡器设置不当链路可能在Gen3下不稳定。Linux内核提供了调试接口来调整均衡器。在/sys/bus/pci/devices/ /目录下有pcie_eq_preset设置均衡器预设pcie_eq_phase设置均衡器阶段但这两个接口不是所有内核都有。我用的5.4内核有但需要CONFIG_PCIE_ECAM和CONFIG_PCI_DEBUG。先看当前预设cat /sys/bus/pci/devices/0000:01:00.0/pcie_eq_preset输出是0表示预设0。PCIe规范定义了11个预设0-10每个预设对应不同的FFE和DFE组合。预设0是最保守的预设10是最激进的。我尝试了几个预设echo 5 /sys/bus/pci/devices/0000:01:00.0/pcie_eq_preset echo 1 /sys/bus/pci/devices/0000:01:00.0/link/retrain预设5下链路稳定在Gen3跑了半小时没有掉。dmesg里也没有新的AER报错。但预设5不一定适合所有场景。不同的板卡、不同的槽位、不同的线缆长度最佳预设可能不同。需要根据实际情况调整。3.5 验证与监控确保链路长期稳定Retrain成功后不能就这么算了。需要持续监控链路状态确保长期稳定。我写了一个简单的脚本每分钟检查一次链路速率和宽度#!/bin/bash BDF0000:01:00.0 LOG/var/log/pcie_link.log while true; do speed$(cat /sys/bus/pci/devices/$BDF/link/current_speed) width$(cat /sys/bus/pci/devices/$BDF/link/current_width) timestamp$(date %Y-%m-%d %H:%M:%S) echo $timestamp Speed: $speed, Width: $width $LOG sleep 60 done同时监控AER报错dmesg -w | grep -i aer\|pcie如果发现链路又降速了或者AER报错增多说明均衡器预设还是不对需要继续调整。4. 那些年我踩过的Retrain坑4.1 坑一Retrain导致链路挂死有一次在调试一个PCIe Switch下游的设备时直接对下游设备触发Retrain结果链路直接挂了。lspci看不到设备了dmesg里全是Link Down的报错。原因是我对下游设备触发Retrain时Switch的上游端口没有正确处理。Switch需要同时协调上游和下游的链路训练单独对下游设备Retrain会导致Switch内部状态不一致。教训对Switch下游设备操作时要先确认Switch是否支持独立的下游Retrain。如果不支持需要通过Switch的上游端口触发全局Retrain。4.2 坑二setpci写寄存器覆盖了其他位前面提到过setpci的写操作是直接写没有读-改-写。我有一次用setpci写Link Control Register结果把ASPM的配置覆盖了导致设备进入了L1低功耗状态性能大幅下降。正确做法要么用sysfs接口要么在写之前先读出来按位操作后再写回。但即使这样也有竞态风险。最安全的是用内核提供的API比如在驱动里调用pcie_retrain_link()。4.3 坑三Retrain后设备重新枚举有一次Retrain后设备重新枚举了BDF号变了。原来在01:00.0变成了02:00.0。这是因为Retrain触发了Link Down然后Link Up内核重新枚举了设备。教训Retrain前要确认设备是否支持不重新枚举的Retrain。如果不支持要准备好重新扫描设备。可以用echo 1 /sys/bus/pci/rescan重新扫描PCI总线。4.4 坑四AER报错阈值设置不当AERAdvanced Error Reporting有错误阈值。如果阈值设置得太低链路稍微有点错误就触发Retrain导致链路频繁训练性能下降。如果阈值设置得太高链路已经很不稳定了还不触发Retrain导致数据丢失。建议根据实际链路质量调整阈值。对于信号质量好的链路可以适当提高阈值减少不必要的Retrain。对于信号质量差的链路要降低阈值及时触发Retrain。调整AER阈值setpci -s 01:00.0 ECAP_AER0x08.w0000具体偏移要看AER Capability的位置。4.5 坑五忽略LTSSM状态有一次Retrain后链路看起来正常了但性能就是上不去。后来用调试工具看LTSSM状态发现链路一直在Recovery和L0之间反复切换。这是因为链路虽然训练上了但信号质量刚好在临界点导致链路不稳定。教训Retrain后不能只看Link Status Register还要看LTSSM的实际状态。如果LTSSM频繁进入Recovery说明链路质量有问题需要进一步调整。5. 进阶用调试工具深入分析Retrain过程5.1 用lspci -vv看链路能力与状态lspci -vv是查看PCIe链路信息的基本工具。关键看两个地方LnkCap链路能力表示设备支持的最大速率和宽度。LnkCap: Port #0, Speed 8GT/s, Width x4, ASPM L0s L1, Exit Latency L0s 1us, L1 4usLnkSta链路状态表示当前协商后的速率和宽度。LnkSta: Speed 8GT/s, Width x4, TrErr- Train- SlotClk DLActive- BWMgmt- ABWMgmt-如果LnkSta的速率或宽度小于LnkCap说明链路没有训练到最大能力。这时候可以尝试Retrain。5.2 用dmesg看AER报错AER报错是链路问题的重要线索。dmesg里的AER报错格式pcieport 0000:00:1c.0: AER: Corrected error received: 0000:01:00.0 pcieport 0000:00:1c.0: AER: PCIe Bus Error: severityCorrected, typePhysical Layer, (Receiver ID) pcieport 0000:00:1c.0: AER: device [10ee:9038] error status/mask00000001/00002000 pcieport 0000:00:1c.0: AER: [ 0] RxErr (First)关键信息severity错误严重程度Corrected表示可纠正Uncorrected表示不可纠正type错误类型Physical Layer表示物理层错误error status/mask错误状态和掩码[ 0] RxErr具体的错误位RxErr表示接收错误如果看到大量的Corrected错误说明链路质量有问题但还能工作。如果看到Uncorrected错误说明链路已经不可靠了需要立即处理。5.3 用debugfs看LTSSM状态有些内核支持通过debugfs查看LTSSM状态。在/sys/kernel/debug/pcie/目录下可能有ls /sys/kernel/debug/pcie/里面可能有ltssm文件读取它可以看到当前LTSSM状态cat /sys/kernel/debug/pcie/0000:01:00.0/ltssm输出可能是LTSSM State: L0如果看到Recovery、Configuration等状态说明链路正在训练。如果频繁看到Recovery说明链路不稳定。但debugfs接口不是所有内核都有需要CONFIG_PCIE_DEBUG和CONFIG_DEBUG_FS。5.4 用perf看PCIe性能计数器PCIe设备通常有性能计数器可以统计链路层的错误和流量。用perf可以读取perf stat -e pcie/link_errors/ -a sleep 10但perf的PCIe事件不是所有平台都支持需要PMUPerformance Monitoring Unit的支持。如果perf不支持可以用设备自己的性能计数器。有些PCIe设备提供了寄存器来统计错误可以通过setpci读取。6. 不同场景下的Retrain策略6.1 服务器场景稳定优先服务器场景下链路稳定性是第一位的。Retrain策略要保守只在链路降速或者AER报错频繁时触发RetrainRetrain前先确认硬件没问题Retrain后持续监控确保长期稳定如果Retrain后链路仍然不稳定考虑降低速率或者宽度服务器场景下通常不需要追求最高速率。Gen3 x8的带宽已经足够大多数应用了。如果Gen3不稳定降到Gen2或者Gen1也能接受。6.2 嵌入式场景资源受限下的取舍嵌入式场景下资源受限Retrain策略要灵活如果链路降速不影响功能可以不Retrain如果Retrain会导致设备重新枚举要评估重新枚举的代价如果Retrain后链路不稳定可以考虑固定速率避免频繁训练嵌入式场景下有时候链路降速是不可避免的。比如PCB走线长度受限信号完整性不好Gen3就是跑不稳。这时候固定到Gen2或者Gen1反而更稳定。6.3 数据中心场景性能与稳定的平衡数据中心场景下既要性能又要稳定。Retrain策略要精细用AER阈值来平衡性能和稳定用均衡器预设来优化信号质量用监控脚本来及时发现链路问题用自动化工具来批量处理Retrain数据中心场景下通常有大量的PCIe设备。手动Retrain不现实需要自动化。可以写脚本定期检查链路状态发现降速自动Retrain。7. 自动化Retrain脚本与工具7.1 写一个自动Retrain脚本下面是一个自动Retrain脚本的示例#!/bin/bash BDF_LIST$(lspci -D | grep -i pcie | awk {print $1}) LOG/var/log/pcie_retrain.log for BDF in $BDF_LIST; do # 读取当前速率和宽度 current_speed$(cat /sys/bus/pci/devices/$BDF/link/current_speed 2/dev/null) max_speed$(cat /sys/bus/pci/devices/$BDF/link/max_speed 2/dev/null) current_width$(cat /sys/bus/pci/devices/$BDF/link/current_width 2/dev/null) max_width$(cat /sys/bus/pci/devices/$BDF/link/max_width 2/dev/null) # 如果当前速率或宽度小于最大能力触发Retrain if [ $current_speed ! $max_speed ] || [ $current_width ! $max_width ]; then echo $(date): Retraining $BDF, current: $current_speed x$current_width, max: $max_speed x$max_width $LOG echo 1 /sys/bus/pci/devices/$BDF/link/retrain sleep 2 # 检查Retrain后的状态 new_speed$(cat /sys/bus/pci/devices/$BDF/link/current_speed 2/dev/null) new_width$(cat /sys/bus/pci/devices/$BDF/link/current_width 2/dev/null) echo $(date): After retrain: $new_speed x$new_width $LOG fi done这个脚本会遍历所有PCIe设备检查链路状态如果发现降速就触发Retrain。7.2 用udev规则自动处理链路事件udev可以监听PCIe链路事件自动触发Retrain。创建一个udev规则# /etc/udev/rules.d/99-pcie-retrain.rules ACTIONchange, SUBSYSTEMpci, ATTR{link/current_speed}!8.0 GT/s, RUN/usr/local/bin/pcie_retrain.sh %k这个规则会在PCIe链路状态变化时触发如果当前速率不是8.0 GT/s就执行Retrain脚本。但udev规则要小心不要造成死循环。如果Retrain后速率还是不对udev会再次触发导致无限循环。需要在脚本里加判断避免重复Retrain。7.3 监控与告警用Prometheus和Grafana对于数据中心场景可以用Prometheus和Grafana来监控PCIe链路状态。写一个简单的exporter#!/usr/bin/env python3 from prometheus_client import start_http_server, Gauge import subprocess import time pcie_speed Gauge(pcie_link_speed, PCIe link speed, [bdf]) pcie_width Gauge(pcie_link_width, PCIe link width, [bdf]) def collect(): result subprocess.run([lspci, -D], capture_outputTrue, textTrue) for line in result.stdout.splitlines(): bdf line.split()[0] try: speed open(f/sys/bus/pci/devices/{bdf}/link/current_speed).read().strip() width open(f/sys/bus/pci/devices/{bdf}/link/current_width).read().strip() pcie_speed.labels(bdfbdf).set(float(speed.split()[0])) pcie_width.labels(bdfbdf).set(float(width.replace(x, ))) except: pass if __name__ __main__: start_http_server(9100) while True: collect() time.sleep(60)然后用Grafana展示链路速率和宽度的变化趋势设置告警规则当链路降速时发送告警。8. 关于Retrain的一些冷知识8.1 Retrain不影响TLP传输Retrain过程中LTSSM进入Recovery但数据链路层可能还在传输TLP。这是因为Retrain只影响物理层数据链路层和事务层不受影响。但Retrain期间链路带宽会下降因为部分Lane可能暂时不可用。8.2 Retrain可以只针对部分LanePCIe支持Per-Lane Retrain。也就是说可以只对某些Lane触发Retrain而不是全部Lane。这在某些Lane信号质量不好时很有用。但Per-Lane Retrain需要硬件支持不是所有设备都支持。8.3 Retrain的次数是有限的PCIe规范没有明确规定Retrain的最大次数但硬件通常有计数器。如果Retrain次数超过阈值硬件可能会认为链路不可靠直接禁用链路。所以不要频繁Retrain每次Retrain后要确认链路稳定。8.4 Retrain和ASPM的交互ASPMActive State Power Management会影响Retrain。如果ASPM使能了L1链路会进入低功耗状态。从L1退出时链路需要重新训练。如果ASPM配置不当链路可能频繁进出L1导致性能下降。建议在调试Retrain时先禁用ASPM。禁用ASPMsetpci -s 01:00.0 a0.w0000但这样会覆盖其他位更安全的是用sysfsecho performance /sys/module/pcie_aspm/parameters/policy8.5 Retrain对热插拔的影响热插拔场景下Retrain可能会影响设备的识别。如果设备热插入后链路不稳定Retrain可以重新建立链路。但Retrain后设备可能会重新枚举BDF号会变。所以热插拔场景下要准备好处理设备重新枚举。9. 从Retrain延伸PCIe链路调试的完整工具箱9.1 lspci不只是看设备列表lspci是PCIe调试的瑞士军刀。除了基本的设备列表还有很多有用的选项lspci -vv显示详细信息包括链路能力、状态、Capabilitylspci -xxx显示配置空间的十六进制dumplspci -t显示PCIe拓扑树lspci -nn显示设备ID和厂商ID我常用的组合lspci -vv -s 01:00.0 | grep -A 10 LnkCap\|LnkSta这样可以快速查看链路能力和状态。9.2 setpci直接操作配置空间setpci可以读写PCI配置空间的任意寄存器。除了前面提到的Link Control Register还有很多有用的寄存器Device Control Register控制错误报告、Relaxed Ordering等Link Capabilities Register链路支持的最大速率和宽度AER Capability高级错误报告的控制和状态但setpci操作要小心写错寄存器可能导致设备异常。建议在操作前先备份配置空间lspci -xxx -s 01:00.0 /tmp/pci_config_backup.txt9.3 dmesg内核日志里的线索dmesg是查看内核日志的工具。PCIe相关的日志通常包含设备枚举信息链路训练信息AER报错驱动加载信息我常用的过滤命令dmesg | grep -i pci\|pcie\|aer\|link如果日志太多可以用dmesg -T显示时间戳方便定位问题发生的时间。9.4 perf性能分析perf可以分析PCIe性能包括链路带宽利用率错误率延迟但perf的PCIe支持取决于平台。有些平台有PCIe PMU可以统计链路层事件。没有PMU的平台只能用软件方式估算。9.5 硬件调试工具示波器和协议分析仪软件工具只能看到链路的状态看不到信号的质量。要深入分析链路问题需要硬件工具示波器看TX/RX差分信号的眼图判断信号完整性协议分析仪抓取PCIe链路层的报文分析训练过程误码仪测试链路的误码率这些工具价格不菲但对于复杂的链路问题是必不可少的。10. 写在最后Retrain是手段不是目的折腾了这么多年PCIe链路我最大的体会是Retrain只是一个手段不是目的。真正要解决的是链路不稳定的根本原因。如果硬件设计有问题Retrain只能暂时缓解不能根治。如果信号完整性不好Retrain后链路还是会降速。所以遇到链路问题我的建议是先查硬件金手指、插槽、供电、参考时钟、信号完整性再看软件AER报错、LTSSM状态、均衡器设置、ASPM配置最后Retrain在确认硬件和软件都没问题后再尝试Retrain持续监控Retrain后要持续监控确保链路长期稳定Retrain操作本身不难难的是判断什么时候该Retrain以及Retrain后怎么确保链路稳定。这需要经验也需要对PCIe协议的深入理解。希望这篇内容能帮你少走一些弯路。