
AF框架第十二章主要解决的是设备层通信这件事。我把这一章从头到尾过了一遍也顺手在几个实际项目里做了验证。先说结论这一章的内容比前面很多章节都实用如果你正在做S7-1500和第三方设备变频器、机器人、仪表、上位机的通信对接这一章基本能把设计思路和关键代码逻辑讲清楚。整章看下来核心不是教你怎么写某一条通信指令而是告诉你如何在AF框架的架构下把通信这件事标准化、模块化、可复用。这一章适合三类人一是已经在用AF框架做项目的工程师想看看官方推荐的通信层怎么搭二是被各种设备通信协议搞得头疼的PLC程序员想知道怎么用一套统一思路去应对Modbus、TCP/IP、OPC UA这些常见方式三是刚接触AF框架、读到这一章有点懵的新手我会把里面的概念用大白话重新捋一遍顺便补充一些原文档里不会写的实操细节。1. 这一章在AF框架中的定位1.1 从框架全局看第十二章的承上启下AF框架Application Framework的全称是西门子面向SIMATIC S7-1200/1500平台的一套应用开发框架。它不是一套现成的程序而是一套约定包含数据结构约定、功能块划分约定、状态机模板和命名规范。框架前面章节解决的问题分别是第一章到第五章讲基础数据类型和通用功能块中间章节讲报警管理、配方管理、数据记录这些都是PLC内部的功能组织。到了第十二章视角从内部转向外部。这一章讲的是如何让一个基于AF框架的PLC程序和PLC之外的设备、系统对话。放到整个框架里看这一章更像是一个收口章节——把前面建立的标准化数据结构通过标准化的通信通道送到外部世界去。同时它又向下兼容了老项目的迁移需求很多用户是从S7-300/400的老程序迁移过来的老程序里各功能块直接调用通信指令到了新框架下必须改写这一章就给出了改写的标准路径。翻译这一章的时候我的一个明显感受是作者对通信的定位非常克制。全章从头到尾没有试图把所有通信协议都讲一遍而是盯着几个核心场景周期性数据交换、非周期性命令传递、故障状态上报。围绕这三个场景去设计功能块才是这章真正有价值的思路。1.2 第十二章为什么聚焦通信集成AF框架前面章节处理的数据比如报警消息、配方参数、生产统计如果只存在PLC内部价值就少了一大半。这些数据最终要交给HMI显示、交给MES系统记录、交给变频器去执行、交给机器人去联动。所以第十二章必须回答一个问题在AF框架的体系下PLC如何高效、可靠地完成这一层数据交换。这一章的翻译难点也在这里西门子官方文档中大量使用了英文缩写和面向对象编程的概念比如instance、interface、callback这类词直接翻译成实例接口回调很容易让习惯了传统LD梯形图编程的工程师犯迷糊。我在翻译时做了不少本土化处理把interface拆成功能块的对外参数把callback解释成通信完成后的通知机制这样读起来顺很多。另外这一章有一个隐藏前提它假设你已经在用AF框架的数据结构了。比如框架里定义了统一的设备状态数据类型这一章的通信功能块就直接跟这种类型对接。如果你没有用AF框架直接看这一章会觉得很多地方多此一举——比如为什么通信功能块非要带一个请求队列而不是直接来什么处理什么。理解了框架的全局设计才能理解这些看似多余的结构。2. 通信架构设计思路拆解2.1 为什么AF框架坚持集中式通信管理传统的PLC通信程序最常见的写法是每个功能块里面各自调用通信指令。比如控制变频器的功能块里直接写Modbus通信指令读取仪表的功能块里直接写串口通信指令。这种方式调试前期很快但项目一复杂就出问题通信指令在同一个CPU里争抢通信资源一个功能块卡住其他功能块的通信全部超时第三方设备做了更换你得把所有相关功能块翻出来改。AF框架第十二章的设计思路是反过来的它把通信这件事从业务功能块里抽离出来形成一层独立的通信管理层。业务功能块需要发送数据不是直接调用通信指令而是把数据放到一个统一的发送缓冲区然后由通信管理功能块统一调度发送。反过来接收到的数据也由通信管理功能块统一接收解析后放入接收缓冲区业务功能块自己去取。我用做饭来打个比方传统写法是每个菜馆自己雇人去进货各买各的堵在路上谁也进不来集中式通信管理是整个园区建了一个中央采购中心各菜馆把需求报上去采购中心统一派车。单个菜馆业务功能块不再关心菜是哪儿买来的、怎么运来的只管接货读接收缓冲区。这种设计的第一个好处是通信通道可以被多个业务功能块复用。AF框架里一条通信连接比如一个TCP连接可以被多个功能块轮流使用通过请求队列仲裁谁优先级高谁先发。第二个好处是通信故障的隔离。通信层单独检测连接状态、重试次数、超时时间业务功能块只需要读取通信层的状态字就知道当前通信是否正常而不用自己处理底层错误。2.2 三种典型通信方式的选型逻辑第十二章围绕三种通信方式展开Modbus TCP、TCP/IP自定义协议、OPC UA。章节里没有直接说什么情况下用哪种我从它的功能块结构上读出了选型逻辑这里整理一下。Modbus TCP适合标准第三方设备的场景。只要设备支持Modbus TCP不管是ABB变频器、森兰变频器还是各种仪表都可以用同一个功能块接入。选它的理由是极低的对接成本——Modbus协议是公开的、结构简单的、几乎所有工业设备都支持。但Modbus的缺点是数据结构弱只能读寄存器、线圈没有复杂的类型系统也没有设备自动识别机制。TCP/IP自定义协议适合非标设备或系统的场景比如和库卡机器人的交互、和上位机的私有协议通信。这种情况Modbus覆盖不了需要按对方协议文档逐字节组帧、解析。AF框架在这一块提供的价值是把帧的组装和拆解标准化把协议部分留给工程师自己按需填写。OPC UA适合信息化系统对接比如MES、SCADA、以及过程仿真软件的通讯热搜词里提到的Process Simulate通过OPC UA与西门子PLC通讯就是典型场景。OPC UA的强项是信息建模数据带语义不用像Modbus那样每个地址都要查表对应。代价是通信栈和配置都更重对PLC资源占用也更高。从章节的行文看作者对三种方式的态度不是哪个先进用哪个而是哪把钥匙开哪把锁。这正好也是我在实际项目中的选型原则。2.3 心跳、超时与数据一致性的处理翻译第十二章的时候心跳这个词反复出现。AF框架的通信功能块里内置了心跳机制PLC周期性向外发送一个递增的计数值对方设备收到后若能正常回复说明链路活着如果连续几次收不到对方的正常回复通信层自动标记连接异常而不是继续闷头等数据。这个设计对工程现场的意义很大。很多现场通信问题不是一次性完全断掉而是间歇性丢包、超时、停顿。如果没有心跳机制业务层接收到一半数据很可能拿着不完整的数据去执行动作这是非常危险的。有了心跳检测就算通信抖动PLC至少知道当前数据不可信可以切换到安全状态。超时处理是另一个关键点。AF框架把所有通信请求都拆成请求—响应配对每个请求发出后启动一个超时定时器。定时器到点未收到响应该请求被标记为失败并进入重试队列重试超过设定次数通信功能块向业务功能块上报通信失败状态。这个模式保证了通信问题永远不会无限期地阻塞业务逻辑——任何一次数据请求都必须在有限时间内给出结论哪怕结论是失败了。数据一致性这一块章节里强调了一个容易被人忽略的点PLC和外部设备之间的数据交换不要直接把原始数据暴露给业务逻辑。AF框架的做法是设置一套镜像区通信层把收到的数据先写入镜像区校验完整后再整体更新到业务可见的数据区。这样做的好处是业务逻辑任何时候读取到的数据都是完整的一帧数据不会出现前一半是新的后一半是旧的这类错位问题。3. 核心功能块详解与实操要点3.1 通信功能块的标准结构第十二章给出的通信功能块对外接口并不复杂大致分四类参数控制参数、状态参数、数据区指针、协议相关参数。控制参数管什么时候通信和和谁通信状态参数管当前通信状态和最近一次错误码数据区指针指向实际要发送或接收的数据缓冲协议相关参数是地址、端口、从站号这类信息。我在翻译这一章时对照过其他框架的通信功能块设计AF的处理有一个明显特征它不允许功能块直接持有数据存储区而是用指针引用外部数据区。很多新手第一次读到这个设计会不理解觉得直接把数据放在功能块内部多省事。但从工程角度想指针引用的方式让一个功能块可以服务于多组数据。比如你和十个变频器通信不需要建十个通信功能块实例只要建一个通信功能块实例然后在不同的时间点让它分别指向不同变频器的数据区。这个设计的另外一个好处是状态机可以独立运行。AF框架的通信功能块内部是一个典型的状态机空闲→建立连接→发送请求→等待响应→解析数据→回到空闲。每一个状态之间的切换条件都写在表中现场出问题的时候通过状态字就能定位到卡在哪里排查效率高很多。注意使用指针引用数据区时务必保证数据区在PLC的保持性存储区中并避免在功能块退出后仍然操作已失效的数据区引用。AF框架对此有专门的数据区有效性检查功能块翻译时我发现很多项目没用上其实是个大隐患。3.2 参数传递与数据类型映射这一章有大量篇幅在讲数据类型映射我一开始觉得有点枯燥后来仔细看才发现这是实操中最容易出错的部分。AF框架的统一通信数据载体是字节数组。也就是说不管外部设备是Modbus寄存器还是自定义协议最终进入PLC后都被拆成字节放进一个数组里。框架再提供一套映射规则把这些字节按照项目定义的数据结构解释回来。比如一个双字实数REAL在Modbus TCP上占了两个寄存器映射进字节数组后高字节在前还是低字节在前是靠字节顺序参数控制的。我看到很多工程师在这里踩坑明明通信通了数据也能收到但数值不对或者小数点位置不对原因往往就是字节顺序和数据类型的长度没配对。AF框架在功能块里明确暴露了这两个参数不允许开发者猜。翻译时我把这部分单独拎出来做了注释一定要先确认对方设备的字节序西门子和大部分欧洲设备默认高字节在前但有些仪表和第三方控制器默认低字节在前这个不对齐后面全是糊涂账。数据类型的长度映射也值得注意。同样是整数有BYTE8位、WORD16位、DWORD32位三种长度。外部设备和PLC之间交换数据最稳妥的做法是统一约定为WORD或DWORD长度避免因对方发了16位PLC按32位解释导致的高位填充垃圾数据。数据类型在字节数组中的占用常见来源易错点BOOL1 bit按位打包设备状态位位偏移计算和文档不一致BYTE1 字节小数值、命令码无符号与有符号混乱WORD2 字节整数寄存器字节序颠倒REAL4 字节模拟量、温度等数据来自两个寄存器时高低字顺序STRING变长设备名称、报警文本长度前缀和终止符的差异3.3 与第三方设备对接的典型案例第十二章给了几个对接案例我挑两个和当前项目最贴近的展开讲。第一个是西门子S7-1500与变频器的Modbus TCP通信。AF框架的通信功能块面向Modbus时把功能码FC03读保持寄存器、FC06写单个寄存器、FC16写多个寄存器作为显式参数开放。实际调试中遇到ABB变频器时最典型的坑是AB变频器的Modbus寄存器地址文档里写的是40xxx这是PLC习惯的地址表示法报文里实际地址要减一40001对应地址0。S7-1500配合AF框架在组态时直接把地址做减一处理就能避免运行时一次又一次的地址错误提示。而森兰变频器SB200系列的Modbus地址是纯16进制寄存器地址不用减一两者混用时特别容易搞混。AF框架的功能块里把地址偏移量单独做成了一个参数这一个参数解决了我在现场最头疼的兼容问题。第二个是西门子S7-1500与库卡机器人的交互。这种场景一般走TCP/IP或ProfinetAF框架的处理方式是定义一套标准的命令—应答数据包结构。PLC发送命令数据包例如请求机器人移动到位置A机器人执行后回传应答数据包例如已到达位置A。章节里强调了指令去重的问题——网络通信会重发机器人可能收到两条相同的移动指令如果没做去重机器人会重复执行。AF框架里在数据包里加入命令序号字段PLC发出的第N条命令序号为N机器人只需记下最后一次执行的序号序号重复则忽略。4. 移植到实际项目的完整过程4.1 从章节示例到项目代码的翻译路径我拿自己最近做的一个实例说明这一章怎么落地。项目要求一台S7-1500作为主站和两台ABB变频器Modbus TCP通信、一台库卡机器人TCP/IP通信、一套上位机系统OPC UA通信同时对接。第一步先按AF框架的结构梳理谁和谁说话。列一张通信矩阵图每一条通信连接、对应的设备类型、通信协议、数据交换周期、数据量大小、优先级。这一步做完我心里就有底了——这相当于第十二章里说的通信需求分析是整个设计的地基。第二步确定通信功能块的实例化数量。AF框架的官方建议是一条物理连接对应一个通信功能块实例。我的项目里两台ABB变频器可以共用一条Modbus TCP连接所以用一个通信实例库卡机器人单独一条TCP连接单开一个实例OPC UA走的是PLC内置的OPC UA服务器功能AF框架主要配合做服务器侧的地址空间映射不需要传统意义上的通信实例。第三步配置数据区。把每一台设备的发送数据区和接收数据区在PLC的数据块里声明好。数据区的大小要留足余量比如变频器的运行参数我预计只用到10个寄存器但我会配置20个寄存器的数据区多出来的部分是给以后扩展用的。翻译章节时看到一个观点很认可数据区一旦定好尽量在一个项目周期内保持不变换地址的代价远大于多占几个字节的存储。第四步逐条填写协议参数。Modbus TCP这边填从站地址、寄存器起始地址、寄存器数量、字节顺序TCP/IP这边填机器人的IP和端口号、帧格式定义OPC UA这边主要是建立服务器配置把需要开放给上位机的变量挂到地址空间。第五步写业务侧的调用逻辑。业务功能块在需要通信的时刻把数据写入发送数据区然后把发送请求标志置位。通信功能块检测到请求标志从发送数据区取数发出报文收到应答后把数据写入接收数据区并清除请求标志。整个过程业务代码完全不用关心底层协议这和第二章说的接口与实现分离一脉相承。4.2 与热搜场景的交叉验证看那些经典问题怎么解热搜词里有一类高频问题我今天翻译完第十二章突然感觉它们其实都是同一个答案的不同变体。这类问题是西门子PLC与三菱变频器RS485通讯S7-200 SMART与森兰变频器通信Modscan能读串口数据但西门子组态软件不能读。先说S7-200 SMART与三菱变频器RS485通信。三菱变频器的RS485协议和标准的Modbus RTU不完全一致特别是命令帧的CRC校验和站号定义都有厂商的特殊处理。AF框架虽然以S7-1500为主但它对通信功能块的抽象思路是通用的把协议差异封装进功能块内部对外暴露的永远是请求数据、响应数据、错误码这三样。你要是用这个思路去看S7-200 SMART的通信就会很自然地想到把三菱特有的协议写成一个独立的协议转换子程序和主通信逻辑分开。这个思路比试图在梯形图里一行行调CRC要省心太多。另外一类问题是第三方上位机读不到PLC数据。比如KepServer连接S7-1500时读不到数据或者Process Simulate通过OPC UA与西门子PLC无法交互。这类问题十有八九不是通信层断了而是数据模型没有对上。AF框架在第十二章前面几节强推的统一数据块结构恰好能帮你把给上位机看的数据单独整理出来结构清晰、变量名规范KepServer或OPC UA客户端在浏览时一目了然。很多人不用框架的套路直接裸奔用DB块地址乱七八糟上位机工程师连变量都找不到自然读不到数据。我遇到过一次类似情况最后发现是DB块没有做非优化访问外部OPC UA无法识别优化访问的DB块。这个细节AF框架相关的章节里反复提到过属于典型的框架避免的坑。关于Modscan能读串口数据但西门子组态软件不能读这一类问题则是另一个维度。Modscan是通用的Modbus调试工具它读数据不分地址区只要链路通就一定能读到。但西门子组态软件有明确的地址区划分I区、Q区、M区、DB区如果你把仪表数据放到了一个和组态配置不一致的地址区Modscan照常能读组态软件却找不到。解决思路还是那一套先建标准的数据映射表再配置组态软件而不是配错了之后到处发帖问。4.3 从S7-1200/1500延伸到S7-200 SMART的迁移技巧AF框架虽然主要针对S7-1200/1500但我发现很多人想把其中思路用到S7-200 SMART上因为200 SMART做小型设备项目量很大。第十二章里的通信架构思想可以用但具体实现要降级。S7-200 SMART的分区存储和指令集都比1500简单没法完全照搬AF的通信管理层镜像数据区指针引用。我在几个小项目里是这么做的把AF框架的集中式思路简化成两个全局数据块一个通信子程序。全局数据块A存所有发送数据全局数据块B存所有接收数据通信子程序统一处理RS485或TCP外部设备的数据分发靠子程序里的索引表完成。这个简化版虽然少了AF的完整状态机和自动重试但在资源有限的CPU上跑得很稳定而且保留了最核心的收益——业务代码和通信代码分离。翻译章节时发现AF框架作者在开篇其实说过一句话大意是框架是为了让代码更清晰、更可维护而不是为了框架而框架。这句话放在S7-200 SMART的简化方案上非常合适我们借鉴的是思想不必纠结形似。5. 常见问题与排查实录5.1 通信失败的三类典型原因这一章最后一部分结合我自己的调试经历把通信失败的常见原因归成三类。第一类是参数配置错误。IP地址、端口、从站号、寄存器地址这一类看似简单的错误反而最难查因为报错不明显。我的习惯是任何通信功能块接入新设备第一件事不是看功能块是否通信成功而是用一个通用调试工具比如Modscan直接对设备发报文先确认设备本身没问题再回过来查PLC侧的配置。这样可以快速把问题范围缩小一半。第二类是通信资源冲突。PLC的通信资源是有限的S7-1500同时建立的TCP连接、Modbus连接、OPC UA会话数都有上限。项目里多个设备同时对接时如果连接数超过了CPU的许可范围新连接会建立失败但旧连接的异常未必会立刻暴露。这里要用第十二章通信状态里的连接计数实时监控已用连接数。第三类是PLC程序扫描周期与通信周期不匹配。如果CPU扫描周期远大于通信超时时间功能块还没来得及处理收到的数据超时标志就先置位了。这种情况下通信本身是通的但逻辑处理不过来。AF框架提供的解决办法是调整通信功能块的调用位置——把它放到一个高优先级的中断OB里保证通信数据的处理不被普通的IO刷新延误。症状可能原因快速验证方法解决动作通信完全不通物理链路断开或参数配置错误检查网线指示灯、用调试工具直连设备排除物理问题后逐项核对IP/端口/站号时通时断通信资源冲突或电磁干扰查看通信状态里的连接计数和错误码减少并发连接或加装网络隔离器数据能通但数值不对字节序或数据类型映射错误用固定值回环测试发已知数看收到什么修正字节顺序或调整数据类型长度偶发超时扫描周期与通信周期不匹配比较CPU扫描周期和通信超时时间将通信功能块移到中断OB中执行上位机读不到数据数据块优化访问或地址区不匹配在上位机地址空间直接浏览PLC数据关闭优化访问按模块标准地址区重新映射5.2 通信数据错乱的深坑不是你错了是它自带偏移这是我翻译第十二章时最有共鸣的部分也是很多现场排查到深夜才发现的问题。Modbus协议本身的寄存器地址是零基的但不同厂商的文档习惯于用一基甚至四万系列的表示法。比如变频器参数P100文档里标注Modbus地址为40100实际报文中的地址却是99。这种偏移量问题一个项目里有三四种变频器每一种的偏移规则都略有不同光靠人工配对必然出错。AF框架的方案是在通信功能块里内置一个地址解码规则从设备型号到实际地址的映射全部自动化。换句话说你以后在这个框架下接新设备只需要添加一条地址映射记录剩下的计算全部由代码完成。话虽如此我要提醒一句这类映射规则一定要在项目文档里留痕否则过几个月回来看代码你会想不通为什么当初40001被减了一。团队项目里这类魔法数字最容易引起交接时的互相伤害。5.3 功能块实例化的内存分配问题第十二章末段讨论了多个通信实例时的内存规划。这里有一个反直觉的点通信功能块实例的数量不是越多越好因为每一个实例都会占用独立的背景数据块背景DB这块内存在CPU里是固定占用的哪怕该实例没有建立通信。我在一个早期项目里就吃过亏S7-1500的CPU看着内存很大觉得多开几个通信实例没问题结果程序做到后段发现MEM存储区不够用了还得回头删减实例数量重构通信逻辑。后来学聪明了按AF框架文档的建议一个通信实例覆盖多条逻辑链路靠路由表区分不同设备而不是简单粗暴地一台设备一个实例。这个操作在第十一章的后半段也提过第十二章算是把应用细节补齐了。注意通信功能块的实例化除了内存开销还有扫描时间开销。每一个实例在每次扫描中都要执行状态机处理实例过多会拉长程序扫描周期。评估实例数量时既要看内存也要看CPU的循环时间预算。6. 翻译笔记之外的实战心得6.1 这一章最值得反复读的三处如果把这一章压缩成必须记住的三句话我的选择是第一通信层必须和业务层解耦。哪怕你不是用AF框架也建议把通信的收发逻辑封装成独立的功能块不要散落到各个业务块里。我在后续项目中改变最大的地方就是这一条——程序结构清晰了排查问题的难度会降一个量级。第二通信状态必须暴露给HMI。AF框架的通信功能块带有完整的状态字未初始化、建立中、运行正常、重试中、通信失败这些状态不要只在程序里用把它传到HMI画面上操作工能第一时间看到通信闪断而不是等到数据异常了才发现。我自己被操作工问过无数次为什么数据不动了后来加上通信状态显示这类问题几乎绝迹。第三统一的数据区胜过散乱的通信指令。哪怕你只是做一个很小的设备只有一台变频器一条通信也建议单独建一个数据块存放通信数据而不是随手放在某几个M位和M字里。数据区统一调试时能少花一半时间交接时也能少挨一半骂。6.2 用AF框架第十二章避免改了上位机又忘了PLC的尴尬做通信项目的过程中最容易出现的问题是上位机侧改了地址但PLC侧忘了同步。热搜词里反复出现KepServer、OPC UA、组态软件连不上PLC等问题大多和这种两侧不同步有关。AF框架在第十二章给出的解法是把PLC对外通信的数据全部集中到一个通信映射区上位机和PLC约定只通过这个映射区交换数据PLC侧其他任何变量都不直接对上位机开放。这样改地址时只需要改映射区里的一条记录PLC程序和上位机配置同步修改即可大大减少只改了一头的概率。我在实际项目中用这个方法做得比较彻底映射区里连注释都写上此地址与上位机绑定修改必须通知上位机工程师。这样做看起来有点机械但在多人协作的项目里这是最不依赖个人记忆的可靠方案。6.3 翻译视角的补充英德原始文档里那些翻译不出来的坑这一章的英文原版和中文习惯很大的一个差异是英文里的configuration和parameter在原文里经常混用。翻译到中文时如果都翻成参数会掩盖两者的区别。我在处理时做了区分configuration翻成组态指设备级的参数集合如通信初始化配置parameter翻成参数指运行过程中可以动态调整的量。这个区分很重要因为AF框架里通信功能块的组态是在下载程序前定好的而参数可以在HMI上运行时修改。如果概念混在一起新手很容易在运行时盲目修改组态项导致功能块重新初始化通信闪断。另外德军原版里很多动词的语义比英文复杂一层翻译成英文本身就已经丢信息。比如überwachen这个词英文翻成monitor中文再翻成监视三重翻译下来原文里带有主动干预含义的监视这个信息就丢了。AF框架里的很多监视功能块其实不只是监视还包含超限自动修正的含义。我在翻译时会在这类词后面加括号备注原文提醒自己也是提醒读者不要小看这些词义差异它直接决定你理解功能块的行为边界。6.4 关于深入学习这一章的建议如果你决定啃下AF框架第十二章并实际用起来我给三条学习路径的建议。第一先搭硬件环境再读文档。没有实际PLC和第三方设备或者仿真软件读这一章很容易觉得抽象。哪怕你只有一台S7-1500本机也可以先建两个通信功能块做回环测试PLC自己发自己收把整个通信流程跑通再去看书本里的扩展内容。回环通了后面的事情都顺理成章。第二带着什么时候用得上去读。这一章每一节解决一类问题。你在做上位机对接就重点看OPC UA那几节你在做变频器通信就重点看Modbus那几节在做机器人联动重点看TCP/IP那几节。工作驱动型的阅读比从头到尾通读效率高很多。第三把这一章和AF框架里的报警管理、配方管理章节联动起来看。你会慢慢发现通信只是手段标准化的数据在系统里流动才是目的。第十二章在整本书里不孤立它和前后章节一起构成了西门子这套框架的完整闭环。这就是从会用一个功能块到会搭一个可维护系统的关键一步。