恭喜,你发布的帖子
发布于 2023-07-04 07:24:57
63楼
分层结构在Trace中的运用
关于分层结构的运用,用一个Trace场景实例做启发。
这个开源项目的温控器FB,总体上分为功能调度和通信IO执行两大部分。
功能调度是个例化的,就不作为例子。Modbus通信,大家都熟悉,就以通信IO执行部分的分层结构作例子。
这个Modbus通信执行部分,作为温控器这个设备FB的IO,是个7层的构造。
每一层通过一个IF判断来进入和限定该层的内容范围。代码执行可以在各层之间来回跳转。
如何在程序运转中,在各种真实场景下,实际观察各层的跳转执行细节,以分析每个层的执行是否正常,进而洞察错误的蛛丝马迹呢?需要Trace。
为了Trace的运用,需要在每一个层中增加一个Bool变量,来观察该层的代码在当前的扫描周期中,是否被执行了。
首先,在FB的static区域中,增加一个bool数组如下
在每一层中的If判断的最后,增加一句类似如下的代码。
这句代码的目的:每次该层代码被进入执行,这个bool变量会产生一个上升或者下降沿。在Trace中观察每层的这个Bool变量的上升或者下降沿,就知道它是否被执行了。通过各层沿变化的时序关系,分析代码是否按照设计预期执行,以及定位各层的故障。
Trace截图如下
在这个Trace截图中,两条竖虚线之间的区域,描述了如下场景:
在温控器3的0号任务执行期间,温控器0获得了优先权,并等待。
在温控器3的0号任务执行完毕后,温控器0获得了执行权。
默认的温控器4的所有modbus任务被跳过去了,本轮未执行。
温控器0的优先任务序号是11,所以首先执行11号任务。之后又执行了0号任务和14号任务。总共3 个任务。
温控器0的这三个任务,总共耗费了13个扫描周期。
通信层一:执行了1次。这层是负责通信的启动,只执行一次。在第1扫描周期
通信层二:执行了1次。这层是负责通信的通路初始化。由于只是常规执行,不需要切换波特率,所以只执行一次,在第1扫描周期。如果是执行自动匹配波特率,那就会执行很多次。
通信层三:执行了3次。这层是负责通信的执行前调度。有3个任务执行,就执行3次。在第1、第4、第11扫描周期。
通信层四:执行了13次。这层是负责通信的执行。其中11号任务执行了3次(1-3扫描周期)。0号任务执行了7次(4-10扫描周期)。14号任务执行了3次(10-13扫描周期)。
通信层五:执行了3次。这层是负责通信的执行后解析。有3个任务执行,执行3次,在第3、第10、第13扫描周期。此处可以优化,因为11号任务是个写任务,不需要通信任务完成后解析数据。我是懒就没改。如果改的话,就在通信层四的末尾,加一个判断,如果是读任务就进入通信层五,否则直接越过进入通信层六。
通信层六:执行了3次。这层是负责通信的执行后调度。有3个任务执行,执行3次,在第3、第10、第13扫描周期。
通信层七:执行了1次。这层是负责通信的结束,当然只执行一次,在第13扫描周期。
如果理解了这个FB的分层设计,这个截图中还有非常多的信息可以被解读出来,都隐藏在每个周期的各个变量的时序关系中。尤其是很多差一个周期的事件。
调试就是分析时序关系的所有细节全集。Trace是最好的时序反馈工具。
除了以设备直接功能为目标的变量,最好额外构造一些用于诊断的数据结构,来调试和迭代。用完删掉。
下图Trace是执行自动匹配波特率。通信初始化层在温控器0获得执行权期间执行了多次。
-------------------------------------------------------------------------
Trace需要经常用。我自己早已直觉化。几乎不用翻代码,就看Trace。
眼睛看Trace细节,脑子立刻定位到特定代码层的特点元素关联,非常高效。
这种方式特别能培养,程序员从单个变量的解耦视角,以元素的方式观察过程细节。时间长了习惯后,会形成对数据结构的不同思考方式。
代码,不论怎么编辑注释和优化排版,它都只是事物之间因果关系的静态描述。
只靠代码,你无法同步同时看到动态关联的结果全貌。而我们恰恰是需要通过每一个变量在全局画像中的任意场景下的瞬间结果,来反推相关的因果路径描述是否正确。
请填写推广理由:
分享
只看
楼主