6.6 动态执行架构

6.6.1 概述

本章内容开始之前,我们先来了解一个工业嵌入式产品中很重要的一个基本概念:强实时和弱实时。

我们平时接触到的大多数嵌入式系统都是弱实时系统,如家用洗衣机。从我们按下开始按钮到洗衣机开始洗衣服,间隔可以是100ms,也可以是1s,甚至可能出现异响要求在按一次。因为响应慢顶多体验差或引来两句骂声,但没有其他损失,这类系统就是弱实时系统。

不同于弱实时系统,强实时系统在规定时间之前如不能正确工作,就会导致非常严重的后果。本书中提及的微机保护装置就是强实时系统,发生电力系统故障时,必须在特定时间内切断故障线路,否则就会导致昂贵的电气设备烧毁,甚至造成大面积停电等极端恶劣事故。在微机保护领域,这个特定时间是在ms级别,真所谓养兵千日用兵一瞬,这类系统被称为强实时系统。

强实时和弱实时,虽然仅有一字之差,但程序设计理念有着巨大的差异,本节课程会揭开这种差异,并带领大家体验一把我这些年的探索之旅。

额外,需要强调一点,即使是强实时系统,强实时也大多体现在一个或几个点上,而其他模块都是弱实时系统的。如微机保护装置中的通讯模块,假如正常情况下要求接收到请求后50ms之内发送确认帧,但即使偶然100ms才发送,顶多导致通讯延迟或重发,损失并不大。这些特征导致在构建微机保护的程序执行框架时,需要充分识别并分别对待,强实时必须确保特定时间内执行完毕,弱实时模块则要尽可能快,尽可能稳的执行。

与普通的强实时系统不同,微机保护装置还有一个特点,需要进行大量复杂的计算,这进一步提升了软件设计的复杂度。如本书提及的变压器保护,不仅需要计算变压器四侧的三相电流、三相差流和三相制动电流,甚至需要计算二次和五次等闭锁谐波电气量,每一路都需要复杂的付式滤波计算。如何在满足这些计算量的前提下保证实时性,是变压器保护首当其冲需要考虑的重要因素。

每一路电气量计算付式公式如下,转换为计算机算法是一系列的乘加运算。付式变换经常被称为最美数学公式之一,是很多工科的基础。如下所示:

在这里插入图片描述

◇◇◇

关于程序动态组织架构,还容易出现另外一个误解。

在网络上交流时,经常听到很多人埋怨自己公司的产品太low,连OS都不使用。好像用了OS就是高级产品,不用就是低级产品似的。也有很有大学生一说学习嵌入式感觉好像就是在学习linux,最后折腾一番,仅仅是调用了几个接口函数,学了个一知半解而已。

学习工业嵌入式系统,我个人建议从裸系统和简单cpu起步,尝试着构建开发环境,设置一些寄存器,简单的写几个驱动,接触中断和互斥,让程序运行起来,先体会体会软硬件协调运行的味道。总之,先有全局认知,后期才能事倍功半。

本章我会带着大家去体味多种嵌入式程序组织模式,这也是我这些年的探索之旅。

6.6.2 起点:前后台系统

嵌入式系统中最简单的程序结构被称为前后台系统,如下图所示:
在这里插入图片描述
在这种程序结构中,主程序部分是一个无线循环,循环中依次调用各个任务模块,我们习惯将主循环程序称之为后台。中断服务例程处理异步事件,一般是设置标志后留待后台任务处理,称之为前台。(注:目前随着互联网程序盛行,前台程序经常特指和用户打交道的界面程序,后台程序指各种业务流程数据库存储等,注意不要概念混淆。)

这种程序结构很low,但生命力很顽强,几乎是简单且非强实时嵌入式设备的通用结构,有很多优点:

  1. 程序结构简单小巧,程序空间和ram空间都比较小,可以使用一些很低成本的cpu;
  2. 各任务之间以串行方式执行,几乎不需要考虑同步互斥问题;
  3. 一般情况下是在中断中设置标志,在后台任务中检索标志,并进行相应的处理。基于这种机制,中断和主循环互斥很容易处理,主要防止主程序中标志被中断修改等问题,策略也很简单,每次使用标志前增加缓存即可;
  4. 特别适合于要求省电的嵌入式应用,如家中的各种遥控器,平时处理器处于停机(halt)状态,通过中断快速唤醒;

当然,这种程序结构也有诸多缺点,其中最主要的缺点是后台程序运行一圈的时间是不确定的,导致其最坏响应时间是主程序执行一圈的最大时间。很显然,这种程序结构不符合微机保护强实时性需求。

6.6.3 探索1:双环前后台系统

早期的嵌入式cpu都是8位单片机,性能极其有限,为了保证保护的强实时性,只能甩弱实时任务了。老一代微机保护研发人员创新性的提出了一种双循环程序结构,如下图所示:
在这里插入图片描述
整个程序分为两个环,弱实时环运行通讯、液晶等弱实时任务,强实时环运行保护电气量计算,逻辑判断,动作出口等强实时任务。

平时运行弱实时任务,在采样中断中进行快速启动逻辑判断,如果发现有异常的苗头,快速将程序切换到强实时任务运行。当整个保护逻辑处理完毕并整组复归后,再切换回弱实时任务运行。

这种程序结构在早期cpu计算能力非常匮乏的情况下保证了保护模块的强实时性,但也有诸多弊端:

  1. 想在中断中直接返回保护程序,必须采取修改程序堆栈的策略,类似于现代OS任务切换策略。不过我们早期做法比较野蛮,没有寄存器保存堆栈切换等策略,而是直接破坏了原弱实时任务的运行环境。在整组复归返回时,需要对原弱实时任务进行重新初始化,这种程序结构要求增加了程序的复杂度。
  2. 过度依赖保护启动元件。启动元件是一种计算量小但灵敏的判断程序,但因为算法差异,可能存在保护元件动作但相应的启动元件未动作的情况,此时就会发生误动作。
  3. 在一些耗时比较长的保护元件动作过程中,通讯液晶等会长时间无响应,在早期单独微机保护运行时还影响不大,但目前随着微机保护都逐渐组网运行后,这一缺点已经无法忍受了。

记得初接触这类程序时,我刚大学毕业,这种程序技巧对我个人触动还是蛮大的。有很重要的两点感悟:

  1. 中断返回强实时任务策略是很好的一次OS启蒙,这让我后期学习OS时有种水到渠成的感觉。因为这段经历,后来我在指导新人学习强实时OS时,经常会让大家先体验这种从中断返回其他程序的技巧。
  2. 为了能从异常中恢复,弱实时任务需要被打断后依然可重新恢复运行。为了做到这一点,需要用到一些程序技巧,也有很多注意事项。后来,我才发现这是一种很朴素的提升程序运行可靠性的策略。为了提升可靠性,嵌入式系统时时刻刻处于自检状态,发现某任务异常后,以前只能采取粗暴的策略复位整台设备,现在有了很巧妙的策略,仅复位某一任务。

6.6.4 探索2:限时前后台系统

2000年前后,单一的微机保护设备开始逐渐的构建成综自系统,组网成为最基本需求,原有的双环前后台系统已经难以满足需求了。而且,此时开始出现各种32位嵌入式cpu,如经典的MC68332单片机,相比早期的51单片机,速度可以说是天翻地覆的提升。在这样的大背景下,我们采用了一种新的程序调度策略。这种策略因为会严格限定后台程序中每个任务的执行时间,因此被称为限时前后台系统。程序结构示意如下:
在这里插入图片描述

该程序结构的最基本特点就是保证无论在什么情况下,最大响应时间内必须执行保护强实时程序模块。记得当初约定最大响应时间为5ms,因此要求所有弱实时任务必须在5ms内执行完毕。基于该理念,程序主循环每次选择一个弱实时任务和保护强实时任务,弱实时任务依次切换执行。

在该产品的研发过程中,我们被迫养成了一个习惯,会习惯性去测试所有程序的执行时间。测试结果很让人意外,也让我真正体会到什么叫二八定律,80%的cpu时间用在20%的程序上,尤其是算法程序上。这段经历告诉我们,花大力气去优化普通流程代码是没意义的,而算法程序中仅省一条汇编指令可能都是有价值的。

为了保证各种弱实时任务可以在5ms内执行完毕,我们被迫采用了大量的程序技巧,增加了很多中间状态。举例说明,如flash写操作需要10ms时间,因此,此时的flash写操作就不能发送完写命令后死等了,需要记录中间状态后快速退出,等待下次执行时继续完成一次完整写过程。

程序技巧是一把双刃剑,可以提升项目组成员的编程水平,但也会增加了代码的复杂度,不仅容易出错,也给后期代码维护阅读带来了困难。特别是随着非保护功能的持续增加,限时前后台这这种程序组织方式的弊端也日益突出,我们被迫要再一次上路了。

6.6.5 探索3:中断虚拟任务

现在一提起微机保护装置,习惯性说微机保护是集保护、测量、控制、监视、录波、通讯等多种功能于一体的智能设备。实际上在早期并非如此,受限嵌入式CPU计算能力和软硬件资源,保护装置和测控装置是分家的。后来随着嵌入式CPU的快速发展,为了节约成本,才慢慢的整合到了一起。

新的趋势下,保护装置中的弱实时软件模块越来越多,保护程序的占比逐渐下降,原有的限时前后台系统就变得非常尴尬了。新的发展趋势,要求新的程序组织方式相适应,该如何破解这个问题呢?

在限时前后台系统中,为了保证保护模块的强实时特性,对弱实时模块提出了很高的要求。现在弱实时模块代码占比大了,能否调换一下呢?思维的转变带来了一种新的程序组织方式,如下图所示:
在这里插入图片描述
不同于以往组织模式,仅有一层采样滤波中断前台程序,此时,多了一层前台中断,用于执行保护强实时模块,我们习惯称之为保护中断。为了保证强实时性,保护中断为5ms定时中断,但此时也带来了一个额外约束条件:保护中断程序必须在5ms内执行完毕。

在新的架构下,时间优化策略聚焦到了强实时模块,弱实时模块可以用传统方式编写了,同时随着CPU的计算能力快速提升,强实时模块也比较容易压缩到5ms时间内执行完毕,两全其美。甚至,如果在采样中断中发现有保护启动的苗头,我们可以调节保护中断定时器以提前启动,进一步优化了保护动作时间。

遗憾的是,新策略总会碰到新问题,这种策略的最大弊端是中断占用cpu时间过多,当年我们的实测数据显示,变压器差动保护装置中保护和采样中断最大占cpu近40%负荷。又因为保护中断和采样中断优先级比较高,给其他模块带来了影响,最典型如串口通讯。
在这里插入图片描述
因为中断密集且占用cpu负荷重,串口中断经常丢失,导致通讯异常。碰到这种情况,需要在中断任务中插入串口查询程序。想当初,借助uart模块内缓冲区,好不容易做到9600波特率,但想继续往上提,就会导致保护任务时间性能下降,权衡的颇为艰难。

6.6.6 探索4:初次引入强实时OS

用中断方式虚拟任务,已经很接近OS调度机制了,而且还带来一系列新的问题,要不干脆在进一步,直接选择强实时OS系统。团队内部经过“认真”权衡,外加我刚学完ucos,正处于兴头上,我们终于迈出关键一步,然而,没想到迎接我们的是当头一棒。

依据OS使用原则,中断中代码应该尽可能的少,我们将原采样中断中的滤波计算提出,将保护中断任务化,整个程序结构如下图所示:
在这里插入图片描述
第一当头棒就是发现保护响应时间离散型增大,给人最直观的感觉是保护动作时间不稳定,有时快有时慢。经测试后才发现,密集的串口或CAN中断会影响到强实时任务响应时间。

第二当头棒是发现基于OS的程序不好写。以前大家习惯于前后台系统,所有的软件模块都是一个个的查询程序。现在基于任务调度,我们想当然的给以前查询程序增加了一个sleep,并放到循环中,最后遗憾的发现虽然cpu快了,但程序执行速度还不如以前。而且,还要为每个任务预留足够大的堆栈空间,ram资源占用也比以前大幅度增加了。

第三当头棒是关于程序稳定性的,程序总会出现一些莫名其妙的问题,要知道大部分代码都是经过千锤百炼积累下来的,后来深入分析才发现源头出自“同步互斥”。以前的调度机制无论如何调整,本质上是中断和主程序之间的互斥,相对容易处理,目前存在多个任务后,同步互斥点开始激增,而我们一开始根本没有认识到这一点。

一番痛苦的折腾,换来了一套占用资源多,运行速度慢,而且不稳定的程序版本,大家怨声载道,最后不得已重新退回上一个版本。这次经历,让我第一次认识到,好的技术是一把双刃剑,驾驭不了时会被反噬的。

6.6.7 探索5:增强型中断虚拟任务

虽然第一次尝试OS铩羽而归,但所有的经历都是有价值的。初次接触OS带给我们很多思维上的转变。如OS中的同步机制,会极大简化程序结构。以前需要通过一个状态机才能实现的程序,现在仅需要等待一同步事件即可。那么,有没有可能适度引入这些策略呢。

基于中断虚拟任务基础上,我们做了一些尝试,在主程序中引入一个弱实时调度机制,给各种弱实时任务设定优先级,不再是单纯的切换,而是基于优先级的调度。当然因为弱实时的原因,各任务执行完毕之前不允许抢占。程序结构如下图示意:

在这里插入图片描述
类同OS工作原理,我们也构建的任务就绪表。为了模拟同步信号量,允许在sleep或等待信号量是递归弱实时调度,额外优化了系统定时器模块。整个弱实时调度机制如下图示意:
在这里插入图片描述

6.6.8 我们目前的位置:动态执行框架

OS不仅仅是一套调度机制,更是一套软件中间件,如文件系统、tcp/ip、USB协议栈等。随着微机保护设备的快速迭代,网络协议,通过U盘升级程序或导出扰动数据等功能成为用户日常维护的常见操作。我们没必要重复制造轮子,因此我们没有选择,必须使用OS。

幸好,此次我们有备而来。密集的串口中断会影响保护任务响应时间,那就选一款支持DMA机制的芯片;同步互斥会影响程序稳定性,如同前面已经多次提及的一般,就将限定在api接口函数内部,专人负责;……

而且,不仅如此,基于需求,我们额外还构建出一个动态执行框架。

◇◇◇

基于OS的任务是一个个死循环,基于前后台系统的任务是一个个可调用有返回的函数。为了统一这两类程序,我们对应用任务进行了重新提炼,将所有的应用模块有优化为一系列消息响应函数。当然,如果还记得分布式模型,会理解这种策略也便于程序进一步分布到多个cpu上。

在这里插入图片描述

◇◇◇

使用嵌入式OS系统,还会面临一个问题:OS种类比较多,每换一次就需要折腾一次。最早期我们使用的是pSos系统,pSos被收购后只好换用vxWorks系统。再后来在简单cpu上用过ucos,为了构建虚拟设备用过windows仿真,最近因为中兴华为事件,大家又开始慢慢切换RT-thread系统。

不同的系统有不同的特点,也有着各自的api接口,早期我们是直接使用OS的系统接口,可能年轻气盛的原因吧,还特喜欢充分挖掘使用各种OS的特性,遗憾的是,这几乎成为后来程序移植的噩梦,大块大块的代码需要废弃重写。

为了解决这个问题,我们尝试构建了OS抽象层,并精心优选了一个较小的功能子集,以约束应用层边界。这种做法给后期系统移植带来了方便,最典型的就是我们以前构建的弱实时调度策略,竟然也可以完美移植,很容易完成了早期代码的充分复用。
在这里插入图片描述

◇◇◇

任务是OS中的基本概念,是OS的调度机制,同时也是OS中的资源载体。基于OS的程序,需要为每个任务设置堆栈,但嵌入式设备中内存一般是紧缺资源。过多的任务也会增加cpu负荷,在微机保护领域,cpu计算能力也是紧缺资源。如何缓解这些矛盾呢?

在实践过程中,我们发现,将一个OS任务和一个应用模块对应起来,总是有很多不太舒服的地方,最典型的如TCP通讯协议,每一个客户端连接后最经典的策略是新建一个任务,但这种策略会导致系统中任务过多,浪费资源,影响CPU效率。

为了解决这些问题,我们分离了OS任务和应用,并对应用进行了概念性抽象,主要包括一系列消息函数和定时器等。为了描述方便,我们称OS任务为active对象,主要负责资源调度,应用模块称为reactive对象,主要负责应用抽象。reactive对象需要指定到active对象才可以执行,多个reactive对象可以指定同一个active对象上。

基于active和reactive机制,应用模块可以按照自己最舒服的模式构建,而任务调度也可以充分考虑硬件资源、CPU算力、实时性要求等约束条件。

在这里插入图片描述
◇◇◇

类同于微机保护装置,很多工业嵌入式设备都需要长期稳定可靠运行。为了防止程序紊乱,在前后台系统中习惯构建一个看门狗模块,每隔一段时间必须执行一次喂狗操作,否则系统会复位。基于OS的调度机制,原有的看门狗策略不合适了,单独看门狗任务,可能很多任务已经死掉了,但看门狗任务依然运行良好。

为了解决这个问题,最常见的策略是引入keepAlive机制,每个任务都需要定时向系统汇报自己的运行状态,如果某个任务不汇报,那么有可能该任务已经挂掉了,需要复位该任务。当然,这也需要该任务容易被复位,最常见的策略是构建一异常初始化流程,仅拉回各种程序运行中间状态,但内存分配等接口就不能再调用了。

看门狗和keepAlive机制对比示意图如下:

在这里插入图片描述
◇◇◇

结合OS抽象层、active和reactive对象、消息队列机制等多种策略,整个动态执行框架如下图所示:
在这里插入图片描述

至此,我们已经构建出一套适合微机保护的动态执行框架,这也是我们团队目前正在使用的策略。不同的需求会采用不同的OS,简单设备会继续使用弱实时系统,复杂的弱实时应用使用linux,强实时可能使用ucos或其他os。

更为重要的是,因为都基于动态执行框架,不管选用何种os,上面的代码结构是一致的,因此也就有了跨系统的复用基础。

6.6.9 反思与总结

双环结构,限时系统,中断虚拟任务,弱实时调度,直到最终的动态执行架构,这一路走来,几乎是我这二十年工作经历的缩影。一路探索,一路采坑,经常是老的问题还没完全解决,就又冒出了新问题。一路走来,颇多感慨。

有时,我会忍不住闭卷沉思,是什么让我们折腾了一路,细细思量,有两个关键点:趋势和需求。首当其冲的是趋势,嵌入式CPU越来越快,资源越来越丰富,各种外设越来越智能,各种OS越来越友好,在这种趋势裹挟下,我们不能永远抱着老一套的东西不放,只能勇敢的迎接未来。

资源的丰富带来了无限可能,以前保护测控需要分别设备运行,现在早已融合为一体;以前都是单独设备运行,现在可以支撑复杂的面向对象协议。软件的丰富也带来了用户运维模式的变化,甚至影响到各种组织架构,然后会进一步推动需求快速迭代。

可以预见,趋势和需求这个环还会继续迭代下去,因此,我们的探索之旅也不会停止,会裹挟着大家要么拼命赶路,要么被历史摔下。

◇◇◇

另外一点很重要的感悟是重构了对技术的认知。

记得早期,我特别喜欢追逐新技术,经常在团队中埋怨,这都啥年代了,还在用汇编程序,还不上OS,还不……。现在,在我的思维中,技术仅仅是一个维度,我心目中首当其冲的是产品,是团队。技术本身没有好坏,更没有高低贵贱之别,只有基于产品和团队,选出最恰当的技术才是更好的策略。

我们使用的大多是应用科学,新技术学习起来是比较快的,即使完全没用过的技术,找服务团队,大家一起学习,边用边学,节奏一般都很快。实际上,重要的不是新技术,而是学习力,这也是我在团队培训时经常刻意强调的东东。

可能是因为受同学或者广告影响,总有小伙和我表达想学某项技术,我经常反问,目前产品中会用到吗?如果答案是否定的,我一般建议大家仅快速了解一下,不要深入学习,因为大概率你是坚持不下去的,反而会影响你的学习能力。

基于工作,外延式学习,每学习一项就巩固一项,快速入门并快速用起来,很快就会积累很多程序技能,也会有效的提升学习力。

◇◇◇

最后,想和大家分享一个观点:所有的经历都不白走,即使最开始双环结构中的任务异常恢复机制,都是很宝贵的程序技巧和人生积累。

我走过的路谨供大家参考。每个人因从事不同的行业,或因为不同的机遇,希望大家能蹚出一条属于自己的精彩成长路。

——————————————

返回目录

我是小马儿,一个渴望良知与灵魂的嵌入式软件工程师,欢迎您的陪伴与同行,如感兴趣可加个人微信号nzn_xiaomaer交流,需备注“异维”二字。

猜你喜欢

转载自blog.csdn.net/zhangmalong/article/details/106729593
6.6
今日推荐