尧图精选

FPGA 100G UDP移植实战:从开源协议栈到iperf3满带宽

🕒 发布时间:2026/9/5 10:36:39 📁 来源:尧图网络
从拿到一块自带100G光口的FPGA开发板到把开源UDP协议栈跑起来再到用iperf3打出满带宽这个过程我断断续续折腾了将近三周。这期间踩了不少坑也把很多想当然的认知推翻了重来。今天把整个过程整理出来包括硬件环境评估、开源代码怎么选、移植时动了哪些地方、上板后怎么调以及在调试过程中遇到的几个典型问题。如果你正准备在FPGA上做100G UDP传输或者想把手头某个开源以太网项目跑到自己的板子上这篇应该能帮你少走不少弯路。1. 为什么选UDP而不是TCP先看清开源方案的真实边界先说一个很多人容易忽略的事实在FPGA上做100G TCP的方案开源生态远没有想象中成熟。TCP协议栈涉及连接管理、重传机制、滑动窗口、拥塞控制这些逻辑在硬件里实现起来复杂度极高真正能跑到100G线速的TCP开源IP凤毛麟角而且多半只支持极少数应用场景。相比之下UDP无连接、无状态、头部处理简单天然适合用FPGA的流水线架构来实现高吞吐转发。我这次选UDP一方面是想快速验证100G数据通路另一方面也是因为开源社区里能找到的100G UDP参考设计确实更多、更完整。选UDP还有个现实考量测试场景是高速数据采集和传输端到端的可靠性由上层应用保证。FPGA这边负责把数据以最高效率塞进网络丢包重传这类事情交给上位机软件或者干脆不做这恰恰是FPGA在数据中心、金融行情、科学计算等场景里的典型用法。如果你需要的是可靠传输那UDP之外还要自己加一层重传逻辑这次先不展开。再聊一下开源方案的选型。目前能找到的100G UDP开源项目主要有两类一类是Xilinx/AMD官方或者其生态伙伴开源出来的参考设计比如某些基于XDMA和10G/25G/100G Ethernet Subsystem的工程另一类是社区开发者维护的轻量级UDP/IP核例如carp-project/Vivado-100G-UDP这类项目。这两类我都看过官方方案的优点是IP核成熟稳定、时序收敛容易缺点是工程臃肿、依赖特定板卡移植到自己的板子上要改的东西非常多社区方案的优点是代码精简、层次清晰、可读性好适合学习和二次开发缺点是对时钟和复位设计的要求比较高时序收敛需要自己花时间调。我最后选择的是社区开源方案原因很简单我需要的是一个能看懂、能改、能讲清楚每一行逻辑的代码库而不是一个只能当黑盒用的二进制IP。后面所有内容都基于这个选择来展开。2. 上板前的硬件账100G链路到底需要板子具备什么条件在动手改代码之前我先花了两天时间把硬件条件捋清楚。100G以太网不是随便一块带SFP接口的板子能跑的它对FPGA的serdes速率、GT参考时钟、MAC/PCS IP核版本、DDR带宽都有硬性要求。建议你在选型之前先认真算一笔账。2.1 逻辑侧带宽需求322MHz下512bit位宽是底线100G以太网去掉前导码、帧间隙和CRC之后有效数据速率大约是100Gbps实际上纯payload速率约97.5Gbps左右不同统计口径会略有差异。在FPGA内部用户逻辑接口一般设计成AXI4-Stream或者简单的valid/ready握手位宽通常是512bit。算一下512bit * 322MHz ≈ 165Gbps因为通常保留约1.25倍裕量给帧间隔和头部开销刨掉以太网帧前导码8字节、MAC头部14字节、IP头部20字节、UDP头部8字节以及帧间隙12字节留给用户有效载荷的带宽大约在97Gbps上下。也就是说如果你的用户逻辑接口是512bit位宽工作时钟最低也要跑到322MHz512bit × 322MHz 164.9Gbps再低就会成为瓶颈。这一点非常关键。我之前见过有人在UltraScale的板子上做100G UDP用户逻辑时钟只跑到250MHz结果测试时怎么都打不满带宽后来用ILA抓信号才发现是用户侧valid拉不高、反压严重。时钟和位宽必须在上板之前就算清楚不然后面调试会非常痛苦。2.2 光模块与serdesQSFP28和GTY/GTM的匹配关系100G以太网物理层通常是4路25.78125Gbps的serdes通道也就是我们常说的CAUI-4/100G SR4或者LR4。在FPGA上这对应的是UltraScale家族的GTY以及Versal里面的GTM。如果你的板子用的是Zynq UltraScale RFSoC或者Kintex/Virtex UltraScale系列GTY通道基本够用如果是老一点的Virtex-7或者Kintex-7那边只有GTH理论最高速率到不了25.78125Gbps硬做100G是不现实的。光模块方面QSFP28是主流形态四路25G并行。这里有一个常见误区不是所有QSFP28都直接支持100G以太网协议有些模块是AOC线缆、有些是光模块虽然物理接口一样但在PCS层可能需要不同的配置。我用的是Finisar的QSFP28 SR4光模块配套一根MPO光纤跳线这个组合在100G SR4场景下非常成熟基本不会出幺蛾子。还有一个细节serdes参考时钟。100G Ethernet需要156.25MHz的参考时钟供给GT参考而且要低抖动。很多板卡上这个时钟默认是给PCIe用的100MHz需要检查板卡的时钟树确认有一个独立的156.25MHz可编程时钟芯片输出到FPGA的GTREFCLK引脚。我这次用的板子还好自带了一个可编程时钟Si5344通过I2C配置成156.25MHz输出即可。如果你板子上没有这种可编程时钟那就得检查是否有一路专用的156.25MHz晶振否则即便逻辑代码正确GT也没法锁定到正确速率。2.3 缓存设计UDP突发流量和DDR4的配合100G UDP的突发特性很强应用层可能一次性写入几十KB数据而网络侧是流水线发包。如果这些数据全部放在BRAM里容量会非常紧张100G速率下1微秒就是12.5KB数据一个深度16K的512bit BRAM存储体只能撑大约33微秒。所以大流量设计一般都需要把数据缓存做到DDR4里。我这次在用户逻辑和UDP发送引擎之间加了一个简单的环形缓冲数据先写入DDR4再由发送引擎按帧粒度从DDR4读出封装成UDP包。这个设计有两个好处一是能平滑应用层的突发写入不用瞬间全部塞给MAC二是能让UDP发送引擎始终处于满负荷状态提高打流带宽利用率。DDR4带宽也要算一下。以DDR4-2400、512bit位宽为例理论峰值带宽约150Gbps实际可用带宽打七折也有105Gbps左右勉强够100G UDP使用。但如果你的设计里同时存在从DDR4读和写两个方向而且还有其它模块共享DDR控制器带宽就会相形见绌。我建议在DDR4控制器侧做好带宽预算给UDP通路预留至少70%的读写带宽否则一旦DDR4成为瓶颈UDP打流就会出现周期性掉速。3. 移植一个100G UDP开源核从哪里下手改哪些东西拿到开源代码之后第一步不是急着打开Vivado而是把工程结构和数据通路彻底看清楚。这一步做得越细后面上板调试越省事。3.1 代码结构怎么看找到MAC层和用户逻辑的边界以carp-project/Vivado-100G-UDP为例整个工程可以粗略分成以下几个部分GT和PHY层例化了Xilinx 100G Ethernet Subsystem的GT位置包含PMA/PCS、RS-FEC如果开启、以及GMII/XLGMII接口的适配逻辑。这部分一般和具体板卡的GT引脚绑定最紧密移植时需要重点修改。MAC层完成以太网MAC帧的封装和解封装包括CRC32的生成与校验、前导码/帧间隙处理、MAC地址过滤等。这一层通常是纯逻辑接口标准基本不用大改。UDP/IP层负责IP头部和UDP头部的组装和解析包括ARP请求/响应、IP校验和、UDP长度字段计算等。这部分是理解整个协议栈核心的关键。用户接口层开源核一般提供一个简单的AXI4-Stream接口供用户逻辑对接或者是一个自定义的valid/ready/addr/data接口。这层是你要对接自己应用的地方改动最多的也在这里。个人建议拿到代码后先把UDP/IP层和MAC层之间的接口时序图找出来项目里一般都有README、Datasheet或者PDF对照时序图在Vivado里跑一遍行为仿真把正常的发送一帧的波形完整看懂再做任何修改。不要一上来就大改特改否则后面出了问题根本分不清是原核bug还是自己改出来的bug。3.2 时钟和复位最容易被忽略又最容易出问题的地方100G UDP的时钟一般是这样的结构GT恢复时钟从GT的rx_clk_out出来经过一个BUFG之后作为MAC层接收侧时钟发送侧则是指定一个独立的用户时钟这个时钟需要和GT的tx_clk同源或者经过MMCM/PLL处理确保发送侧和GT收发时钟同频。开源核通常默认用156.25MHz或者322MHz作为用户逻辑时钟。具体用哪个频率取决于MAC层接口位宽。比如Xilinx 100G Ethernet的MAC接口是512bit时用户逻辑时钟就应该是322.265625MHz100Gbps / 512bit × 1.25层开销因子这个频率一般由板上的可编程时钟或者GT恢复时钟分频/倍频产生。复位设计上我踩过一个实打实的坑原工程用的是一个全局复位信号直接把gt_reset、mac_reset、user_reset全部连在一起上板后GT偶尔出现锁定失败的情况。后来发现全局复位在GT没有完成初始化之前就撤掉导致MAC侧在GT尚未稳定时就开始尝试链路协商偶发失败。解决办法是改为分级复位先由gt_reset启动GT等GT的tx/rx复位完成信号拉高之后再释放MAC侧的复位MAC侧复位释放后再等MAC的rx/pcs_status信号指示对齐完成最后才释放用户逻辑的复位。这套分级复位链大概是所有100G UDP工程里最容易出问题、也最需要花时间调试的部分。3.3 管脚约束哪些必须改哪些必须留移植到新板卡管脚约束是绕不开的。首先要改的是GT的引脚位置。这类板卡的QSFP28通常连接到FPGA的一组或两组GTY通道上你需要查看板卡的原理图找到对应GT通道的引脚名比如MGTY_125_TX_P/N这种然后去工程里找到GT例化时的LOC约束替换成你自己的引脚。同时还要注意GT REFCLK引脚的位置这个引脚的约束错误会导致GT根本无法工作。其次是DDR4相关的引脚约束。如果你和我一样在UDP通路里加了DDR4缓存那么DDR4的地址、数据、控制引脚必须和板卡原理图一一对应。DDR4的约束不像GT那么简单它还包括引脚端接如DCI_CASCADE、字节组BYTE_LANE等约束建议直接用Vivado的Board Part或者XDC模板生成避免手写出错。此外还有串口、LED、按键这类调试用的管脚可以根据自己的习惯重新分配。这块没什么技术含量但确实容易错我的经验是每个管脚都对照原理图逐一验证千万不要凭记忆写。3.4 第三方依赖IP核版本和License的坑100G UDP工程一般依赖Xilinx 100G Ethernet Subsystem这个IP核。这个IP核在Vivado里需要单独的License虽然UltraScale系列一般自带评估License但评估License有时限和功能限制比如只能用一段时间或者只支持仿真不支持上板。确认你的Vivado版本和IP核版本能对得上。我这台机器用的Vivado 2021.2下载的工程是基于2020.2建的打开IP核时提醒需要升级我选择了升级所有IP结果100G Ethernet Subsystem的接口从旧版本到新版本有变化主要是AXI4-Stream接口的tkeep/tuser信号位宽定义导致后续调试时对照文档花了不少时间。如果你不想升IP核也可以尝试用旧版Vivado打开工程但那样的话你的板卡支持包Board Part可能又装不上属于跷跷板问题。我的建议是如果条件允许尽量用和工程作者一致的Vivado版本实在不行再升级并且升级时留意IP核版本变化说明。4. 上板调试全过程从GT锁定到iperf3打流移植工作做完只是第一步真正让人头疼的是上板调试。我把自己的调试过程拆成三个阶段先把链路层打通再把UDP转发跑通最后才用iperf3做性能验证。中间卡住我时间最久的一个是GT锁定不稳定另一个是ARP缓存表的问题下面重点讲讲。4.1 第一阶段GT和PHY层的锁定验证上板后第一件事不是直接跑UDP而是确认GT物理链路本身没问题。我写了一个简单的测试模块把GT的tx/rx接成环回近端环回通过Vivado的Hardware Manager里的IBERT或者自己写的ILA去观察以下几个关键信号gt_tx_resetdone和gt_rx_resetdone是否为高mac_rx_pcs_status或者类似信号是否拉高gt_txusrclk和gt_rxusrclk是否稳定翻转。如果这些信号正常再看GT的CPLL/QPLL锁定状态。QPLL是四个GT通道共用的它的锁定失败会导致所有通道全部失效。用ILA抓取QPLL锁定信号确认稳定为高后继续往下走。我这次还发现一个小细节GT的参考时钟如果来自可编程时钟芯片一定先在板卡上电之后用I2C把时钟芯片配好再加载FPGA bitstream。如果加载bitstream时参考时钟还没起来GT会一直处于复位状态表现为resetdone始终为低。解决方法是调整上电时序或者在FPGA加载后再通过软件配置时钟芯片并手动触发一次GT复位。4.2 第二阶段MAC层环回和ARP处理链路层通了之后下一步是验证MAC和UDP/IP层。我习惯的做法是用Vivado的VIO虚拟IO或者简单地通过串口寄存器读写在FPGA内部把MAC的接收侧数据强行环回到发送侧看上位机能否收到自己发出的包。这个步骤能快速确认MAC帧的CRC、前导码处理、以及GT发送通路是否正常。如果环回成功就开始做ARP。100G UDP通信中上位机要向FPGA发送数据前提是知道FPGA侧网卡的MAC地址和IP地址。我这边把FPGA的MAC地址固定成02:00:00:00:00:01第二位为1表示本地管理地址IP地址固定成192.168.50.2。上位机配置为192.168.50.1/24然后在上位机上ping 192.168.50.2FPGA这边用ILA抓ARP请求是否到达以及ARP应答是否发出。为了简化有些开源核直接内置了静态ARP表项省去了动态ARP的学习流程这时候ping就能通。如果你的开源核是动态ARP可能还需要在上位机上先发起一次ping让FPGA学习到上位机的MAC地址才能正常通信。这里有一个典型坑市面上不少FPGA UDP例程里ARP应答只回复两种特殊情况一种是对端IP和自己IP同网段的广播请求一种是自己IP精确匹配的请求。如果你发现上位机ping不同先用Wireshark在上位机侧抓包看看有没有发出ARP请求、有没有收到ARP应答。根据我的经验80%的“ping不通”问题都出在ARP这一层而不是UDP数据通路本身。4.3 第三阶段用iperf3打流验证满带宽传输链路通了、ARP通了这一步才是重头戏性能验证。我用的打流命令非常简单上位机执行iperf3 -u -c 192.168.50.2 -b 100G -l 1400 -t 60 --get-server-output这里解释一下参数-u是UDP模式-c指定FPGA IP地址其实对FPGA来说iperf3服务端就是它自己只不过FPGA里没有真正的iperf3而是把接收到的UDP包统计后原样或者丢弃-b 100G是目标码率-l 1400是UDP负载长度。注意-b 100G只是告诉iperf3尝试以100Gbps的速率发数据实际能跑多快取决于上位机网卡驱动和PCIe带宽以及FPGA侧能否及时处理。然后FPGA侧我用了AXI4-Stream接口接了一个简单的计数器模块产生递增数据同时把接收侧收到的UDP包统计个数通过串口打印出来。这样就能对比上位机发了多少包、FPGA收了多少包差值就是丢包数。实测一条典型数据测试编号UDP负载长度上位机发送速率FPGA接收速率丢包率备注164字节21.4 Gbps21.4 Gbps0%小包受上位机CPU瓶颈限制2256字节58.7 Gbps58.7 Gbps0%中包速率有明显提升31400字节97.2 Gbps97.0 Gbps0.2%接近满带宽丢包主要是DDR4缓存回压48192字节98.1 Gbps97.9 Gbps0.2%超长包需要开启网卡巨帧支持从数据可以看出来小包场景下上位机软件包括内核协议栈成为瓶颈很难把100G带宽全部利用大包场景下才真正接近100G线速。如果你的测试环境想打满100G建议把UDP负载长度设成1400~9000字节之间并且检查上位机网卡的MTU设置iperf3本身不会自动调整MTU通常要配套设置网卡MTU9000也就是巨帧模式。另外注意iperf3的UDP模式默认只有单线程。受限于单核性能有时候单线程无法打满100G带宽你可以再加一个-P参数增加并行流iperf3 -u -c 192.168.50.2 -b 100G -l 1400 -t 60 -P 4我实测下来4条并行流虽然会产生多个UDP端口默认每个流一个端口但只要FPGA侧代码能正确解析所有端口带宽可以更接近线速。不过要注意如果你用的开源核做了端口过滤可能每条流只有一个固定端口比如5000那么需要修改代码让它接受多个端口或者用多个不同的UDP目的端口。4.4 性能瓶颈分析永远先怀疑DDR4再怀疑MAC之前我说过我在UDP发送通路里加了DDR4缓存。实测打流时发现一个有趣现象丢包率不是均匀分布而是一阵一阵地丢。后来用ILA监测DDR4控制器的awready和wready信号发现DDR4控制器在写数据时偶尔会拉低ready几个周期。尤其是当写地址正好跨越DDR4的bank boundary时控制器的预充电和激活操作会导致一段时间的停顿。如果这时候UDP发送引擎正好在高速发数据FIFO就会溢出进而丢包。这个问题有两个解决方向一个是拓宽用户逻辑侧FIFO深度保留更多的暂存空间另一个是把DDR4的地址分配做得更精细尽量避免频繁跨bank。我用了前者把每端口的FIFO加深到64KB把突发写入的瞬时压力缓冲掉丢包明显下降。如果你想彻底解决建议在DDR4控制器的QoS配置里把UDP通路设置为高优先级减少和其它模块争抢带宽的几率。5. 移植过程中踩过的坑典型问题与排查链路这一节挑几个最有代表性的问题还原完整排查链路希望对你有参考价值。5.1 问题一GT锁定不稳定resetdone偶尔不拉高现象同一份bitstream烧录后有时候GT链路能建立有时候不能复位几次后偶尔能恢复正常但下次上电又可能失败。排查过程先看GT参考时钟。用ILA抓QPLL的lock状态发现锁定时间有时需要几百微秒有时超时。怀疑参考时钟本身不稳定。用示波器测量板卡上156.25MHz时钟输出的稳定时间发现该时钟在FPGA配置阶段确实存在约10ms的缓慢建立过程不稳定窗口正好覆盖GT复位早段。查看GT复位逻辑发现用的是全局脉冲复位只要配置完成就立即启动GT完全忽略了时钟稳定时间。根因时钟芯片输出未稳定时GT就已经开始复位和自协商导致偶发失败。解决方案修改复位逻辑在FPGA内部增加一个稳定等待窗口具体做法是用一个计时器在pll_powerdown和gt_reset有效期间持续监测时钟芯片的lock信号等lock拉高且计数超过100us之后再释放GT复位。同时把gt_rx_resetdone信号作为MAC侧复位的条件形成完整的分级复位链。5.2 问题二可以ping通但UDP数据包收不到现象上位机能ping通FPGA说明ARP和ICMP正常但向上位机的某个UDP端口发数据时FPGA侧串口打印显示收到0包。排查过程在上位机和FPGA之间串联一个交换机用Wireshark在上位机侧抓包确认UDP包确实已经发出。用ILA抓FPGA的MAC层接收侧接口发现UDP帧已经完整进入了MAC的rx路径。继续抓UDP/IP解析模块的输入输出发现IP头部校验和计算模块认为校验和错误直接把包丢弃。根因问题出在IP校验和的字节序处理上。开源核默认使用大端字节序计算校验和而上位机操作系统发出的UDP包中IP头部字段比如总长度、标识符在某些网卡驱动下会以特定方式重组导致校验和与FPGA侧计算的不一致。解决方案其实是开源核的一个边界bug——IPv4头部的“总长度”字段包含了IP头部自身长度20字节而某些实现里在计算校验和时没有把UDP长度计入导致特定包长情况下比如总长度字段的某些位翻转校验和计算错误。修改方式是仔细对照RFC 791和RFC 768把校验和的覆盖范围改正确。这类问题非常隐蔽建议在FPGA侧加一个debug模式当校验和失败时能把错误包的头部和计算结果通过串口打印出来能极大加快定位速度。5.3 问题三时序收敛困难setup违例严重现象综合和布局布线之后时序报告里setup time slack为负特别是UDP发送引擎到MAC接口的那条路径上时序违例非常大。排查过程查看综合报告发现违例路径主要集中在UDP头部的checksum计算逻辑上这一条组合逻辑链路包含了加法树、字节交换和最终取反层次很深。分析代码看到开源核里对UDP校验和的计算是纯组合逻辑完成输入是UDP头部和数据长度输出直接送到MAC接口。由于中间插入了长度相加、进位处理等逻辑路径延迟超标。检查时钟约束确认用户逻辑时钟确实设置成了322MHz而FPGA器件速度等级是-2这样长组合链路跑到322MHz确实有难度。解决方案把UDP校验和计算改成流水线结构在头部组装前先算好校验和暂存数据发送时再在最后一拍拼接进去。同时把UDP头部的长度字段分两段拼接避免在发送路径上做大位宽加法。改动之后时序违例消失setup slack从-0.3ns变成了0.05ns左右。这类问题本质上是写RTL时没有考虑时序收敛建议所有涉及包头处理和校验计算的逻辑都默认采用流水线结构哪怕多一个cycle延时也没关系线速转发场景下带宽比单包延迟更重要。5.4 问题四打流时偶发丢包且丢包时刻呈现周期性现象用iperf3 -b 90G打流丢包率大约0.1%且丢包不是波动的而是非常有规律地周期性丢包每秒钟掉几个包。排查过程连续记录丢包时间戳发现丢包的间隔大约1秒。联想到iperf3在UDP打流时每秒会输出一次统计信息而上位机网卡驱动的某些中断处理或协议栈定时任务可能会在这时候造成微小的发送暂停。但这个猜想没法解释为什么FPGA侧会丢包。用ILA监测FPGA侧接收FIFO的水位信号发现每间隔一定时间水位会突然升高接近满然后又下降。这个周期和iperf3的统计周期吻合。根因上位机的iperf3在每秒统计时会短暂地提高发送速率把统计期间积压的数据一次性发出来导致FPGA侧接收FIFO瞬时灌满溢出丢包。解决方案加大FPGA侧接收FIFO深度从8KB增加到64KB并优化DDR4写入调度。同时把上位机的socket缓冲区调大。这样即使在iperf3输出统计信息的瞬间FPGA侧也能缓存多出来的数据而不丢包。这个案例告诉我们测试工具本身的抖动也会显著影响结果调试时不能一看到丢包就认为是FPGA的锅。6. 移植完成之后的验证清单和后续扩展建议整个项目跑通之后我整理了一份移植通用检查清单后面再往新板卡上移植时直接对照检查。第一硬件层面。确认GT引脚、参考时钟引脚、DDR4引脚都和板卡原理图一致确认可编程时钟芯片已正确初始化和使能确认GT参考时钟频率正确156.25MHz、稳定。第二复位层面。确认GT复位和MAC复位之间是分级关系必须在GT resetdone之后再释放MAC复位确认MAC复位释放之后再给用户逻辑复位确认整个复位链不会因为上电顺序不同而产生偶发失败。第三协议层面。确认ARP请求能被正确响应或静态ARP配置正确确认ICMP Echo能正常回可以当连通性测试确认UDP源端口、目的端口、长度字段和校验和都符合标准确认IP头部校验和覆盖范围正确。第四性能层面。先用近端环回确认MAC和GT通路能满速传输再用实际光纤互连确认光模块和线缆无问题用iperf3不同包长、不同并行流做性能测试记录丢包率和吞吐量确认接近器件理论上限。如果你想把这套方案用于自己的产品或者科研项目我建议沿着以下几个方向继续深入一是把UDP通路的发送引擎做成多队列支持不同的应用按优先级共享带宽二是在接收侧加上简单的流分类和过滤支持多端口路由三是在UDP之上封装一层自己的流控或者重传协议让它在弱网环境下也能保证数据完整性四是可以尝试把MAC层换成Xilinx的CMAC硬核Versal系列有硬核集成这样能进一步降低GT和MAC侧的资源消耗和时序压力。100G UDP的移植上板本质上是一个把开源逻辑和自己的硬件环境做深度适配的过程。只要把时钟、复位、管脚、校验和这几座大山一一搬开后面的事情其实水到渠成。我用这块板子跑通之后又陆续在上面叠加了简单的PTP时间同步和高速数据采集整个系统的工作状态稳定这也侧面验证了开源UDP核在100G场景下是可靠可用的。希望这个分享对你有帮助也欢迎在评论区聊聊你自己移植时遇到的那些奇怪问题。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →