ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

AutoSar诊断之UDS 0x11服务:Dcm、BswM与EcuM复位链路深度解析

AutoSar诊断之UDS 0x11服务:Dcm、BswM与EcuM复位链路深度解析 1. 诊断复位这条链路到底在干什么做AutoSar诊断开发的朋友早晚都会碰到0x11服务。这个服务的名字叫ECUReset按协议规定客户端通常是诊断仪或者产线工装发一条请求ECU收到后执行复位动作。听起来特别简单对吧不就是重启一下嘛。但如果你的项目用的是完整AutoSar协议栈这个“简单”的动作会被拆成Dcm、BswM、EcuM三个模块分工协作任何一个环节配置不对都会出现“请求发了但车没反应”“复位移位了但NvM数据丢了”“诊断仪等了半天没收到正响应”这类疑难杂症。我最早接触这个服务的时候天真地以为在Dcm里勾一个“Reset支持”就行了结果上了台架怎么都触发不了复位。后来一点点把Dcm到BswM再到EcuM的调用链捋清楚才发现AutoSar把这件小事拆得这么细是有原因的。这篇文章就按我自己的实操路径把0x11服务从报文解析、Dcm配置、BswM规则设计、EcuM复位执行到常见坑位全部走一遍。适合刚接手AutoSar诊断开发、被BSW配置工具搞得头疼的工程师也适合想搞清楚“复位到底经过谁”这个问题的人。先说结论0x11服务在完整协议栈里不是一个模块的活而是一条模式请求链。Dcm负责解析诊断报文BswM负责根据当前系统状态判断能不能复位EcuM负责真正执行复位动作并且管理复位前后的系统状态。下面我按这条链逐个拆。2. 0x11服务本身有哪些门道2.1 报文格式与子功能定义0x11服务属于UDS统一诊断服务里的一个基础服务它的请求报文格式很固定第一个字节是SIDService ID也就是0x11第二个字节是子功能Sub-function用来告诉ECU你要执行哪种复位方式。ISO 14229-1标准里定义了以下几种0x01hardReset硬复位。模拟ECU断电重启所有RAM内容丢失电源管理相关硬件会执行一次下电再上电的动作。0x02keyOffOnReset钥匙电复位。模拟点火钥匙从OFF到ON的过程适用于那些不能随便断电、但需要模拟整车重新上电的场景。0x03softReset软复位。程序跳转到复位向量重新执行不涉及电源下电RAM如果没做初始化处理里面的数据可能还在。0x04enableRapidPowerShutDown快速下电。这个用得少主要配合某些需要在极短时间内完成下电存储的场合。0x05disableRapidPowerShutDown禁止快速下电。同样是辅助性质。实际项目里用得最多的是0x01和0x03。0x02在一些车身控制器上会用来模拟KL15从OFF到ON的完整流程方便产线做完刷写后让ECU带着新软件重新跑一遍初始化。正响应的格式是0x51加子功能回显比如你发了0x11 01正常回复就是0x51 01。负响应则是0x7F 0x11 NRC比如0x7F 0x11 0x22表示条件不满足0x7F 0x11 0x12表示子功能不支持。2.2 时序问题比你想的更重要0x11服务和其他诊断服务最大的区别在于ECU马上就要重启了正响应能不能发出去是个大问题。很多工程师第一次调试时会发现诊断仪这边发完0x11之后一直等不到正响应然后ECU自己倒是重启了。这个现象的本质是响应还没来得及上总线复位动作已经执行了。所以标准做法是Dcm在转发复位请求给BswM之后会有一个等待机制。这个机制可以是一个短延时也可以是等待BswM返回特定状态目的是保证正响应有足够时间通过CanIf和Can驱动发送到总线上然后再真正执行复位。具体等待多久、谁来等不同架构有不同的配置方法。有的项目用DcmDspResetType里的ResetTime参数有的项目把等待逻辑放在BswM的Action里先延时再请求EcuM。我后面会详细讲这两种方式的差异。2.3 会话和安全的检查不能漏0x11服务属于功能性诊断服务通常要求ECU处于非默认会话并且安全等级要解锁到某个级别。这些检查由Dcm自己在上层完成只要DcmDspReset对应的会话列表和安全等级配置正确协议栈会自动返回NRC 0x7F服务不支持或0x33安全访问失败。这里有一个很容易忽略的点不同子功能可能对应不同的安全等级要求。比如0x01只要求扩展会话加解锁0x03可能要求编程会话加解锁。在Dcm配置里可以针对整个0x11服务统一设置也可以拆成多个reset type分别设置。如果你遇到“0x03能用但0x01报0x33”这种诡异现象多半就是这里配置拆分了但安全等级没跟着分开。3. Dcm侧配置把诊断层的事情做扎实3.1 Dcm模块里要配哪些东西Dcm是Diagnostic Communication Manager负责诊断报文接收、PDU路由、会话管理、安全访问、DTC相关服务以及我们这里关心的0x11服务处理。对于复位服务Dcm要配的东西主要集中在DspDiagnostic Service Processing部分。先找到DcmDspReset这个配置项。你会看到下面有DcmDspResetType每一个type代表一种复位子功能。例如你新建一个ResetHard把DcmDspResetType的SubFunction设成0x01然后关联到一个或多个DcmDspResetSubFunction。这些子功能配置决定了服务在哪个会话下可用、需要哪个安全等级、有没有额外的时序要求。还有几个关键参数要说一下DcmDspResetType::ResetTime这个参数用于设置从Dcm触发复位请求到实际执行复位之间的等待时间。注意这个时间只在Dcm这个层面生效BswM处理模式请求的时间不包含在内。如果你的BswM动作链比较复杂建议把ResetTime留足余量或者干脆把延时放在BswM里统一管。DcmDspReset::DcmResetType用于配置该服务支持的复位类型映射关系对应ECU复位类型的内部表示。DcmDspResetSubFunction和诊断会话的绑定通过DcmDspResetSubFunction里的SessionRef / SecurityLevelRef来指定该子功能在哪些会话下有效。3.2 一个可落地的Dcm配置实例假设我的项目里有四个复位子功能0x01硬复位、0x02钥匙电复位、0x03软复位、0x04快速下电。我要在Dcm里做的事情大致如下在DcmDspReset下新建四个DcmDspResetTypeResetHard、ResetKeyOffOn、ResetSoft、ResetRapidShutdownSubFunction分别填0x01、0x02、0x03、0x04。新建DcmDspResetSubFunction分别绑定到上面四个Type并配置SessionRef为扩展会话0x03SecurityLevelRef为解锁等级比如0x03表示已解锁。在DcmDspResetType里设置是否支持SuppressPosRespMsgBit抑制正响应位。这个通常不勾因为复位类服务一般需要正响应。确认DcmDspReset的DcmResetType和EcuM里的复位模式对应关系。Dcm的复位类型要能映射上EcuM定义的ResetMode这样Dsp处理完之后才能把模式请求发出去。有些工具链在生成代码之后还会要求你手动在Dcm_Cfg.c里检查生成的配置确认DcmResetType的枚举值和EcuM的ResetMode值是对的。这个对应关系一旦错位就会出现“Dcm明明收到了0x11但BswM始终等不到请求”的情况。3.3 Dcm请求转发到BswM的内部机制Dcm处理完0x11请求后并不会自己去调用复位函数。它会把请求转成BswM能理解的形式。具体来说Dcm会调用BswM的DcmCommunicationModeRequest或者BswM_DcmResetRequest之类的接口把复位请求作为一条模式请求发给BswM。这里需要注意接口差异不同AutoSar版本、不同工具链生成的接口名可能不一样但本质都是“通过ModeRequestPort向BswM发送一条复位模式请求”。BswM在收到这条请求后会根据你配置的仲裁规则决定是否接受、以及触发哪些Action。这就是为什么我们要在BswM里专门建一个复位相关的规则而不是靠Dcm直接蹦到EcuM。换句话说Dcm的本职工作是“告诉别人我要复位了”而不是自己动手。4. BswM侧配置复位的“交通警察”4.1 BswM为什么能管复位BswM是Bus State Manager它的核心职责是仲裁来自各个模块的模式请求然后根据当前状态决定要执行哪些动作。你可以把它理解成一个交通警察各个模块报上来“我要进XX模式”BswM根据当前道路状况其他模式请求、系统条件决定放行还是拒绝并且指挥相关模块执行动作。对于复位场景BswM收到Dcm发来的复位请求后要检查至少以下几件事当前是否有其他关键模式请求在占用系统比如网络管理正在请求总线通信保持此时复位会导致通信突然中断可能不合适。当前是否处于刷写状态如果Flash编程还在进行贸然复位会把ECU刷成砖。是否有NvM写操作还没完成如果直接复位RAM里的待写数据会丢。当前点火状态是否允许复位比如KL15还在ON且车速不为零某些安全策略会拒绝复位请求。这些条件判断在BswM里就是一条条ModeCondition每个条件可以绑定一个或多个数据源比如ComM的模式状态、NvM的写状态、EcuM的唤醒原因等。只有所有条件都满足BswM才会执行后续的Action列表。4.2 配置一个复位仲裁规则BswM的配置模型有三块核心内容ModeRequestPort请求端口、ModeCondition条件和ModeAction动作。复位相关的配置大致长这样在BswMModeRequestPort里添加一个请求端口名字可以叫DcmResetRequest关联到Dcm模块。这样Dcm就可以往这个端口发复位模式请求。在BswMModeCondition里创建复位条件比如ResetConditionAccepted绑定到AUTOSAR_EcuMResetRequest这个请求状态。条件里还可以加上NvM写状态、ComM通信状态等判断。在BswMModeAction里创建复位动作比如ResetActionExecute动作里调用EcuM_SetResetMode接口。如果你需要在复位前延时可以在动作里加一个TimerAction延时结束再触发下一步动作。创建一条仲裁规则BswMModeArbitrationRule把请求端口、条件和动作串起来。规则里要指定仲裁方式是“同时满足所有条件”还是“任意一个条件满足即触发”。把这条仲裁规则激活。在BswM里规则受BswMModeControlPriority控制优先级高会先被评估建议复位规则不要设太高避免影响其他正常模式切换。4.3 一个常见但容易翻车的配置条件判断的时序BswM的仲裁是周期性的它不会在你发请求的瞬间立刻做出判断而是等下一个仲裁周期。如果Dcm那边设置了很短的ResetTimeBswM还没来得及把模式请求发出去Dcm已经认为超时了此时就会出问题。解决办法有两个方向一是把Dcm的ResetTime调大比如从10ms调到50ms给BswM仲裁留出时间二是在BswM的条件里直接用“收到请求”本身作为触发条件不额外等待其他模块状态。前者适合系统状态本来就很稳定的场景后者适合对复位响应速度有严格要求的场景。我个人习惯是先加一个“延时50ms再请求EcuM”的Action这样Dcm的ResetTime只需要覆盖到“Dcm发出模式请求”这一步即可剩下的时间由BswM来兜底。你可以根据自己项目的实际情况选。5. EcuM侧配置真正干活的模块5.1 EcuM的复位模型EcuM是ECU State Manager负责ECU的启动、关闭和复位状态机。在AutoSar架构里EcuM才是真正执行复位动作的模块。BswM通过EcuM_SetResetMode之类的接口把复位请求传给它EcuM根据请求的复位模式走完一套标准流程后触发硬件复位。EcuM的复位流程包含这些阶段调用NvM服务把尚未写入非易失存储器的数据写下去。通知通信模块ComM/CanSM进入通信关闭流程。关闭OS调度停止继续执行应用任务。根据复位模式决定是跳转到Bootloader还是直接软复位。调用Mcu模块的复位接口真正让芯片复位。这些阶段在EcuM里对应一个个ActionList你可以把整个复位过程拆成多个步骤每个步骤里干不同的事。比较典型的配置有先写NvM等它写完之后再关通信最后才复位。5.2 配置EcuM复位模式的实操打开EcuM配置先找到EcuMResetMode这一项。你需要针对每个复位类型定义对应的复位模式。比如ResetMode_Hard、ResetMode_KeyOffOn、ResetMode_Soft这些名称可以自定义但要和Dcm里定义的ResetType一一对应。接着在EcuMResetAction里配置具体的执行动作。常见的动作有NvM_WriteAll把所有挂起的NvM数据块写完。ComM_GoToNoCom把通信模式切到No Communication停止总线收发。EcuM_GoToStandby / EcuM_GoToSleep进入休眠或待机适合keyOffOnReset。Mcu_PerformReset触发芯片硬件复位。每个Action可以设置激活条件比如NvM_WriteAll只在上一次有写请求时才执行避免每次复位都白等一遍写NvM的时间。这里有一个“隐藏福利”要注意如果你不想花时间在NvM写数据上可以配置NvM不参与复位流程但代价是RAM里所有诊断相关数据、扩展会话状态全丢。某些项目对“复位后恢复扩展会话”有要求就需要额外把会话状态存到NvM里再恢复那就必须在复位前把NvM写了。5.3 复位后跳到Boot还是App0x11服务最常见的业务场景是产线刷写完成后诊断仪发一个0x11 01让ECU重新上电跑App。另一种场景是OTA下载完Boot之后通过0x11 03跳到Bootloader继续刷App。这两种场景对EcuM复位后跳转目标的要求不一样。跳Bootloader通常需要设置一个标志位比如在非易失存储区写一个标志或者设置一个RAM标志如果复位后RAM还能保留的话EcuM在启动初始化阶段检测到这个标志就跳到Boot。跳App则相对简单正常复位即可。这里要注意如果EcuM复位后走的是软件复位不经过硬件下电RAM标志是可以保留的但可靠性不如存储在Flash里。你需要在复位可靠性要求与擦写寿命之间做个取舍。我见过一个项目复位标志存在内部Flash每次复位前写一次结果产线连续刷了几个小时Flash写寿命被耗掉不少。后来改成软件复位RAM标志才解决。所以说这个设计要结合硬件特性来定不能照抄。5.4 多核ECU的复位要小心如果你在做的是多核MCU比如AURIX TC3xx或者S32K3这类芯片EcuM的复位配置还有一个额外关注点复位是所有核一起复位还是只复位当前核在某些安全相关的应用场景可能希望Core0负责复位其他核优雅退出。这需要在EcuM的复位动作里增加核间通知逻辑确保所有核都进入安全状态后再统一复位。这块内容比较依赖具体芯片的MCAL实现我的建议是在设计阶段就和底层驱动负责的同事确认好别等集成测试的时候才发现复位动作把另一个核正在跑的电机控制任务打断了那就麻烦大了。6. 从Dcm到EcuM的时序全景上面把三个模块分别讲了一遍现在把它们串起来看。一次完整的0x11服务流程在时间轴上是这样的诊断仪发来0x11 01请求CanIf收到报文通过PduR转发给Dcm。Dcm检查当前会话模式是否允许该服务检查安全等级是否解锁检查子功能是否支持。Dcm通过BswM_DcmResetRequest把复位请求发给BswM。BswM收到请求后在下一个仲裁周期评估条件NvM是否忙、ComM是否允许断开通信、有没有更高级的模式在占用系统。条件满足BswM执行Action列表。可选地先延时几十毫秒再调用EcuM_SetResetMode。Dcm在DcmDspResetType::ResetTime计时结束后发送正响应0x51 01。EcuM收到复位请求执行复位前流程写NvM、关通信、停OS。调用Mcu复位接口系统重启。这个时间线里第5步和第6步的顺序很关键。如果你的架构是Dcm先发正响应再进行BswM动作那么ResetTime必须足够长确保正响应完全发出去了EcuM才开始复位。如果是BswM先延时再发请求Dcm的正响应可以早一点发出去不用等EcuM复位完成。我在实际调试时通常会用CANoe抓一下总线报文数一下从0x11请求到0x51响应之间的时间间隔再对比EcuM复位的实际时刻。如果发现复位发生在正响应之前就把Dcm的ResetTime调大或者把BswM的延时调大。这个调试过程看着很基础但特别能帮助理解整条链路的时序关系。7. 常见问题与排查技巧实录7.1 请求发出去了但ECU没有任何反应先检查Dcm有没有收到报文。在CANoe里看是否可以抓到0x11的请求。如果抓到了继续看Dcm有没有发出正响应。如果连正响应都没有问题多半在Dcm的配置上比如0x11服务没有使能或者当前会话不匹配或者安全等级没有解锁。如果Dcm的正响应已经发了但ECU没有复位那问题在Dcm到BswM到EcuM这条链路上。先用调试工具看BswM有没有进入复位相关的仲裁规则条件是否满足Action有没有执行。再看EcuM的复位动作有没有被触发。一层层排查不要跳步。7.2 NvM数据在复位后丢了这是最常见的问题。复位前如果NvM有挂起的写请求而EcuM的复位流程没有执行NvM_WriteAllRAM里的数据就会丢。检查EcuMResetAction里是否配置了NvM_WriteAll并且确认它的优先级在所有会修改NvM的应用任务停止之前。还要注意NvM_WriteAll本身需要时间如果复位流程没有设置一个合理的等待机制写操作没完成就复位了照样丢数据。一般做法是在EcuM的复位状态机里设置一个“等待NvM写完成”的状态NvM写完回调再触发下一步。7.3 正响应发不出去正响应发不出去常见于Dcm的ResetTime设置太短BswM的动作链太长或者CanIf的发送队列拥堵。解决思路是先把Dcm的ResetTime调大比如从默认的10ms调到100ms再试。如果恢复正常再逐步缩小找到临界值。另外一个容易忽略的点如果你在BswM的Action里把PDU通信关了比如调用了CanSM的通信关闭而Dcm的正响应还没发完那也会导致正响应发不出去。这种情况需要把通信关闭动作放在正响应发送完成之后再执行或者让Dcm的正响应不被通信关闭动作中断。7.4 复位之后进入不了Bootloader跳转Boot失败通常有三个原因。一是复位模式配置不对EcuM里填的复位模式和Bootloader跳转条件对不上。二是跳转标志没有写成功比如Flash地址错误或者写入时机不对。三是复位后Bootloader启动条件不满足比如还处于编程会话的依赖条件被清掉了。排查时先确认Bootloader能不能通过外部工具正常进入。能进入的话就查跳转标志不能进入的话查EcuM复位后初始化流程。7.5 BswM仲裁条件一直不满足这种情况多见于复位条件绑定了ComM状态或者NvM状态而当前状态是“忙”或者“不匹配”的。可以用诊断工具查看BswM内部的模式状态逐一核对哪个条件没满足。某些工具链支持在线监控BswM的状态机调试效率会高很多。如果暂时查不到具体哪个条件不满足可以先在BswM里加一个临时的“无条件复位”规则把原来的复位规则屏蔽掉看看复位链路本身是否通。链路通了之后再逐个恢复条件定位是哪个条件导致的问题。7.6 多帧请求处理问题0x11服务的请求一般只有两个字节不存在多帧问题。但如果Dcm的接收缓冲区配置有误比如PDU长度配错了也可能出现请求被当成多帧协议处理而导致整个服务不可用。检查CanIf和PduR的PDU配置确保0x11请求的接收长度大于等于2个字节即可。7.7 复位请求和网络管理打架整车网络里ECU复位会导致网络上的通信中断如果此时网络管理处于Bus-Sleep Release状态可能引发网络风暴其他节点连续发NM报文。所以BswM的复位条件建议加上“当前网络管理允许断开通信”的判断。这个判断可以从ComM的当前模式拿如果ComM还处于COMM_FULL_COMMUNICATION复位动作会直接导致通信中断最好是先请求ComM进入NoCom模式等切换完成再复位。有的项目图省事复位条件里根本没加网络管理判断结果测试时发现ECU复位瞬间总线上出现一堆错误帧或者NM重复报文。这不是偶发问题是模式切换顺序不对导致的系统性风险建议在量产前把这条链路调通。8. 一些值得反复检查的设计细节8.1 应答与复位执行的先后顺序不同整车厂和ECU供应商对这个顺序的要求不完全一样。有的要求先回正响应再复位有的允许复位和正响应同时进行只要正响应确实在总线关闭前发出去。这个需要在需求阶段就跟整车厂确认清楚否则到集成测试阶段再改就很被动了。我建议的实现是Dcm的正响应逻辑保持默认复位执行前在BswM里加一个至少50ms的TimerAction这样正响应有充足的物理时间上总线。如果你所在的平台对复位响应时间有硬性指标比如小于100ms那这个延时不能太长需要精调。8.2 防止诊断仪误触发复位0x11服务是最高“杀伤力”的几类诊断服务之一一旦被误触发ECU直接重启。所以安全设计上除了安全等级校验还要注意3个问题CanIf和PduR的报文过滤是否只放行诊断物理请求Dcm是否只允许在特定物理寻址下接收0x11BswM的条件判断是否足够严格能否拒绝异常时刻的复位请求。曾有人踩过坑ECU同时支持功能寻址和物理寻址功能寻址下0x11也能被Dcm接收结果总线上一台设备发功能寻址复位整条总线上所有ECU全重启了。出现这个问题的原因就是Dcm里0x11服务没有限制寻址方式。解决办法是在DcmDspReset的配置里把它绑定到特定的物理地址请求或者在PduR层做地址过滤。8.3 与诊断仪的超时等待配合诊断仪发送0x11之后通常会设置一个比较长的响应超时因为ECU复位本身需要时间。如果诊断仪端的超时设太短ECU还在执行复位诊断仪就报超时错误了。这种情况不是ECU的bug要调整的是诊断仪侧的P2*或者S3Server配置。另外一种情况是ECU复位完重新启动后Dcm还没有初始化完毕诊断仪又紧接着发了第二条诊断请求结果被丢掉。应对策略是在诊断仪侧增加一个“ECU重启等待时间”等EcuM初始化完成、CanIf进入正常收发状态后再继续后续诊断流程。8.4 日志和数据记录调试0x11服务时建议在Dcm、BswM、EcuM三个模块分别加日志输出如果用的是DevOps流程Trace工具或者DEM事件记录也可以。复位动作执行太快靠人肉盯总线报文盯不过来有日志就能事后复盘。我现在习惯在Dcm发出复位请求、BswM判断通过、EcuM真正复位前三处各打一条记录比对时间戳就能快速定位是哪个模块卡住了。有些项目为了方便量产阶段追溯还会把复位请求次数、复位原因写进NvM重启后通过诊断服务读出来。这样如果用户反馈“我的车自己重启了”就能判断是诊断仪触发的主动复位还是系统异常导致的复位。这个功能虽然小但在售后问题定位时用处很大。9. 一个完整的配置项检查清单为避免你搭的时候漏配置我整理了一份我每次做0x11相关项目都会过一遍的检查清单你可以直接拿去做评审或者自测。检查项配置位置关键内容0x11服务使能DcmDspReset确认Reset服务处于使能状态对应的Dsp列表已生成子功能定义DcmDspResetType0x01-0x05与需求一一对应不能有重复或遗漏会话绑定DcmDspResetSubFunction确认扩展会话、编程会话都覆盖到了安全等级绑定DcmDspResetSubFunction确认需要解锁的具体Level并且Dcm能正确校验ResetTimeDcmDspResetType给正响应发送留足够时间建议初期放宽到100ms调试BswM请求端口BswMModeRequestPortDcm复位请求端口已创建并与Dcm关联BswM条件BswMModeCondition确认复位条件绑定了NvM、ComM等必要状态BswM动作BswMModeAction确认延时动作和EcuM请求动作都配置了顺序正确BswM仲裁规则BswMModeArbitrationRule规则已激活优先级合理没有被其他规则抢占EcuM复位模式EcuMResetMode复位模式的定义与Dcm的ResetType能对应上EcuM复位动作EcuMResetAction确认NvM写操作、通信关闭、最终复位调用都在列表里正响应时序联调验证抓总线确认0x51在复位前发出复位后跳转EcuM启动流程确认复位后是回App还是跳Boot标志位可靠每条检查项后面对应的模块都要安排专人确认尤其是BswM和EcuM的配置它们的字段名在不同工具链里差异比较大不能只看名字想当然必须逐条核对生成的代码。10. 最后说点自己的体会做了好几个项目的0x11服务配置之后我最大的感受是诊断开发最难的往往不是某个模块本身而是模块与模块之间的交接。Dcm觉得“我已经把请求发给BswM了后面不管我的事”BswM觉得“我仲裁完了指示EcuM去复位就行”EcuM觉得“我按状态机执行复位但前面的人什么时候给我发信号不归我管”。三方都没有错但拼起来就可能翻车。我个人的建议是拿到新项目第一步先别急着点配置工具先把“0x11请求从进到出经过哪些接口、哪些回调、哪些模式请求”这条链画出来和团队里的人对一遍再动手。配置本身花不了多少时间真正花时间的是对链路和时序的理解。如果你正在调0x11相关的问题不妨先检查一下Dcm的ResetTime、BswM的延时Action和EcuM的NvM_WriteAll这三处大部分这类问题最后都能在这三个地方找到答案。
返回列表