听飞哥聊聊软件架构仿真那些事儿(1)

前言

过去的几十年里,越来越多的软件控制的功能出现在嵌入式系统中。在汽车、航天、铁路及工业相关领域,相当一部分功能是与人身安全相关的。而且目前看来,这些功能的数量和复杂程度仍在不断增加,如自动驾驶功能,其对功能安全提出了最高等级的要求。为应对这些新的挑战,需要引入新的软件开发方法论。在功能安全标准ISO 26262:2018中,针对软件架构的表达方式和验证方式都提出了明确的要求,如针对满足ASIL C/D的产品,其软件架构要采用半形式化的方法来表达,对于满足ASIL D的产品,要对其软件架构的动态行为进行仿真验证。

软件架构的仿真可以解决我们在开发过程中,可能遇到的如下问题:
第一,我们有多大的信心,对于产品中的各种需求,如功能需求、性能需求、功能安全需求及信息安全需求,对于其中存在的依赖关系,能够全部达成?
第二,我们有多大的信心,对于产品中提供的各种服务,其中涉及到的各种协议、时序,在运行时,不会出现死锁等异常?可以发现系统中存在的瓶颈?
第三,我们有多大的信心,对于产品中的实时性要求,能够全部达成?比如,功能安全需求中的FTTI,需要将这些时间分配到实现需求的各个组件上,那这些时间分配的是否合理,在各种可能的(随机)场景中,是否能够达成?

这个系列文章想从软件架构仿真的一种:离散事件仿真(Discrete-Event Simulation)出发,介绍下离散事件仿真的原理,工具及其在软件架构验证中的应用。

1 系统模型分类

生活中常见的系统可以大致概括为下图所示的树状分类:
在这里插入图片描述
其中,确定的(Deterministic)系统可以理解为通过数学表达式,给出确定的输入则系统给出确定的输入,随机的(Stochastic)系统则需要基于统计上的方法进行研究,因为这类系统中,至少有一个影响系统状态的变量的随机的。静态的(Static)和动态的(Dynamic)指系统在运行过程中,时间的变化是否影响系统的输出结果。最后,对于连续的(Continuous)是指在连续的时间点,系统状态没有明显的改变,而离散的(Discrete)则是指即使是在时间上连续,系统的状态也出现的明显可察觉的变化。具几个例子说明一下图中的6个分支的代表:

  • 确定的静态系统,比如车辆仪表中的LED显示,其亮灭状态取决于其输入电压的高低,在时间上,并不会电压的持续而改变其状态;
  • 确定的动态连续系统,如在加油站给汽车加油,油箱中的油量取决于加油枪的加油速度,而加油速度是固定值,油箱中的油量可由加油速度乘以时间来计算;
  • 确定的动态离散系统,如打印机的打印队列或者加油站排队的队列,每个任务的都有固定的处理时间,而系统在不同的时间点处于空闲或者繁忙的状态;
  • 随机的静态系统有个特殊的名字:蒙特卡罗(Monte Carlo)系统,这个系统主要是指以一种统计方法研究一个静态的系统,如历史上曾经出现过的通过向一块带有圆的正方形的区域掷飞标,通过落在圆内和圆外的飞标数量计算圆周率;
  • 随机的动态连续系统,人工智能中的神经网络是一个例子;
  • 随机的动态离散系统,如一天中到某加油站加油的车辆,其排队的时间、队列的长度符合概率分布;

我们要介绍的软件架构仿真的方法离散事件仿真属于随机动态离散系统的例子,其包括三个显著的属性:

  1. 随机性,软件架构中,至少有一个输入是随机的,比如,功能安全系统中,要处理的硬件失效
  2. 动态性,时间因素在软件架构中有重要的影响,如不同的软件组件的执行时间,周期,资源锁等等
  3. 离散事件,软件的输入事件是离散的,如用户的输入,网络的数据及硬件失效的事件等

2 一个概念模型

我们先设想一个简单的场景来表示离散事件仿真的基本概念:一家有150台机器的工厂,每台机器单独工作,生产产品用于销售。机器会随机的出现故障,这时需要有技工对机器进行维护。由于成本问题,技工的数量不能太多,否则就会出现大量空闲的情况。故障的机器沿特定的路径运送到技工处排队(如果没有空闲的技工);维修完成的机器再沿固定的路径送回到厂房。系统的模型如下图所示:
在这里插入图片描述
其中,顶部的矩形表示厂房,内部白色的圆圈表示正常工作的机器。出现故障的机器沿左侧箭头运输到技工处排队,故障的机器由黑色的圆点表示 。模型中的技工有4个,可以同时最多维修4台机器。修好的机器由右侧箭头运回厂房。在当前这个场景中,有6台机器出现故障,其中4台处于正在维修状态,由于技工数量的限制,2台处于排队状态。
我们假设每台机器每天工作8小时,每年工作250天,除非出现故障,每台机器每小时产出为20元。每个技工的年薪为52000元,由于休假的原因,每个技工每年工作230天,每天8小时,但休假时间预先协调好,以保证在岗的技工数量最大化。问,应该雇佣多少个技工?

整理一下这个模型中,需要关注的要点:

  • 模型中,要解决的问题和目标已经清晰定义,即多少个技工可以满足利润最大化的需求。极端情况是为每一个机器雇佣一个技工,这样会导致大量的人力成本,同时机器的产出由于故障能够最短时间修复也最大化了;另一个极端情况是,只雇佣一个技工,这样人力成本最小化,但机器潜在的故障时间被延长导致了利润的损失。
  • 模型是一个易于理解的概念模型。机器的状态有2种:工作状态和故障状态。每个技工的状态有2种:忙碌状态和空闲状态。这些状态可以描述整个系统在任意时间的状态。
  • 可以将这个概念模型规范化,但需要补充一些信息。如,机器的故障是随机产生的,那故障产生的时间间隔是如何分布的?机器的维修时间也是随机的,维修所用的时间是如何分布的?如何才能用一些系统化的方法来描述影响模型状态的随机事件需要在规范化的过程中定义。
  • 对这个模型进行仿真需要包括跟踪系统的时间、故障机器队列的长度和可用的技工数量,以及一些其它的重要统计信息,如系统在一定时间内总的利润输出。
  • 仿真相当于对模型的计算和验证过程,但这个过程通常需要大量的测试。由于输入是随机的,所以对于输入数据要考虑各种可能性。
  • 确认模型和实际工厂的场景是否接近。模型无法做到是现实场景完全一致,但模型能提供给我们现实场景中需要大量时间和成本才能得到的数据,因此确认模型和现实的接近程度就非常重要。在这个例子中,如果技工的数量增加,在现实中故障的平均维修时间是否下降?如果平均的维修时间增加,那平均的故障机器的数量是否也有增加?

通常来说,离散事件仿真的模型要尽可能的简单。模型的目标是尽量抓住系统的本质问题而忽略无关的特性。比如在这个工厂模型中,机器的运输方式不是核心问题,在模型中就被简化掉,最多只会考虑从厂房到技工处,在运输过程中,可以有一个固定或者随机的延时(所花的时间是核心问题,时间代表成本和利润)。

3 离散事件仿真的基本原理

在这一节,我们把概念模型中的一些基本元素抽取出来进行进一步的解析,并进行规范化。

3.1 概念模型

下图给出一个概念上的单服务节点的模型:
在这里插入图片描述
单服务节点包括一个用于处理任务的服务器,以及其处于繁忙状态下的任务等待队列。任务在随机时间到达服务节点,服务器对于任务的处理的时间也是随机的。一旦任务处理完毕,任务则从服务节点移出。服务器有2种状态:繁忙与空闲,队列有2种状态:空与非空。如果服务器处于空闲状态,则队列一定处于空的状态;如果队列处于非空状态,则服务器一定处于繁忙状态。对于上节的概念模型,如果只雇佣一名技工,则系统属于单服务节点模型:技工就是服务器,故障的机器就是任务。
对于队列来说,可以有多种模式,如:

  • 先入先出,即FIFO队列
  • 后入先出,即堆栈模型
  • 随机模式,服务的顺序是随机的
  • 优先级模型,服务的顺序按照排序

处于服务状态的任务数量取决于服务器数量和队列的长度,队列长度可以设置为有限或无限。有限的长度如果达到最大值,则后续的服务请求则会被拒绝或者阻塞。最常见的队列模式可能是先入先出模型,这与现实生活中一致;在嵌入式实时系统中,操作系统基于优先级的调度策略则属于最后一种模型。另外,如果存在抢占的情况(即,已经进入了服务器的任务被移出,服务器用于处理更高优先级的任务),需要一些进一步的处理手段,这里暂时不讨论。

3.2 规范化模型

我们在这节定义几个基础的变量,用于规范化模型。假设系统的运行时间从0开始,下标 i i i 表示系统中的第 i i i 任务:

  • 任务 i i i 的到达时间表示为 a i a_i ai
  • 任务 i i i 与上个任务到达时间的间隔为 r i r_i ri,明显 r i = a i − a i − 1 r_i=a_i - a_{i-1} ri=aiai1,而 a i = r 0 + r 1 + r 2 + . . . + r i , i = 1 , 2 , 3 , . . . a_i=r_0+r_1+r_2+...+r_i, i=1,2,3,... ai=r0+r1+r2+...+ri,i=1,2,3,...
  • 任务 i i i 在队列中的延迟定义为 d i , d i ≥ 0 d_i, d_i\ge0 di,di0
  • 任务 i i i 开始服务的时间定义为 b i = a i + d i b_i=a_i+d_i bi=ai+di
  • 任务 i i i 的服务时间定义为 s i , s i > 0 s_i, s_i\gt0 si,si>0
  • 任务 i i i 在服务节点中的时间定义为 w i = d i + s i w_i=d_i+s_i wi=di+si
  • 任务 i i i 完成服务的时间定义为 c i = w i + a i c_i=w_i+a_i ci=wi+ai

下图以图形的形式表达这些概念之间的关系:
在这里插入图片描述
考虑下面2情况:

  • 如果 a i < c i − 1 a_i<c_{i-1} ai<ci1,则说明任务 i i i 在上个任务完成前到达,则 d i = c i − 1 − a i d_i=c_{i-1}-a_i di=ci1ai
  • 如果 a i > c i − 1 a_i>c_{i-1} ai>ci1,则说明任务 i i i 在上个任务完成后到达,则该任务可以立即进行服务状态, d i = 0 d_i=0 di=0

3.3 统计数据

为了说明离散数据仿真统计数据的过程,假设系统用下面的数据作为输入,等待队列为先入先出模式:
在这里插入图片描述
上表中可以看出,最后一个任务的到达时间为 a 10 = 320 a_{10}=320 a10=320,完成时间为 c 10 = 320 + 26 + 30 = 376 c_{10}=320+26+30=376 c10=320+26+30=376
我们定义系统中任务平均的到达时间 r ‾ \overline{r} r 和平均的服务时间 s ‾ \overline{s} s 为:
r ‾ = 1 n ∑ i = 1 n r i 和 s ‾ = 1 n ∑ i = 1 n s i \overline{r}=\frac{1}{n}\sum_{i=1}^{n}r_i 和 \overline{s}=\frac{1}{n}\sum_{i=1}^{n}s_i r=n1i=1nris=n1i=1nsi
在上面的例子中,平均的到达时间为 r ‾ = a n / n = 320 / 10 = 32 \overline{r}=a_n/n=320/10=32 r=an/n=320/10=32,平均的服务时间为 s ‾ = 34.7 \overline{s}=34.7 s=34.7。那么平均每秒到达的任务数量为 1 / r ‾ = 1 / 32 ≈ 0.031 1/\overline{r}=1/32\approx{0.031} 1/r=1/320.031,系统每秒处理的任务数量为 1 / s ‾ = 1 / 34.7 ≈ 0.029 1/\overline{s}=1/34.7\approx{0.029} 1/s=1/34.70.029。在这个例子中,系统的处理速度明显小于任务的到达速度。
下面的定义给出系统基于时间信息的统计数据。对于任意的时间 t > 0 t\gt0 t>0

  • l ( t ) = 0 , 1 , 2 , . . . l(t)=0,1,2,... l(t)=0,1,2,... 为在时间 t t t 处于服务节点内的任务数量
  • q ( t ) = 0 , 1 , 2... q(t)=0,1,2... q(t)=0,1,2... 为在时间 t t t 处于等待队列内的任务数量
  • x ( t ) = 0 , 1 x(t)=0,1 x(t)=0,1 为在时间 t t t 处于处理状态的任务数量
    对于任意的 t > 0 t\gt0 t>0来说, l ( t ) = q ( t ) + x ( t ) l(t)=q(t)+x(t) l(t)=q(t)+x(t)
    在时间范围 ( 0 , τ ) (0,\tau) (0,τ)内,服务节点内的任务平均数量 l ‾ \overline{l} l,平均等待队列长度 q ‾ \overline{q} q 和服务器的平均使用率 x ‾ \overline{x} x 定义为:
    l ‾ = 1 τ ∫ 0 τ l ( t ) d t , q ‾ = 1 τ ∫ 0 τ q ( t ) d t 和 x ‾ = 1 τ ∫ 0 τ x ( t ) d t \overline{l}=\frac{1}{\tau}\int_0^\tau{l(t)}dt, \overline{q}=\frac{1}{\tau}\int^\tau_0{q(t)}dt和\overline{x}=\frac{1}{\tau}\int_0^\tau{x(t)}dt l=τ10τl(t)dt,q=τ10τq(t)dtx=τ10τx(t)dt
    通过描画服务节点内的任务数量 l ( t ) l(t) l(t) 可以得出如下的图形。通过计算下面图形中蓝色线内的面积除以时间(376),可以得出 l ‾ ≈ 1.65 \overline{l}\approx1.65 l1.65。在376单位的时间内,只有26单位服务器内没有任务,因此 x ‾ ≈ 0.931 \overline{x}\approx0.931 x0.931。如果把服务器想像为处理器,则可以理解为处理器的使用率为93%。
    在这里插入图片描述
    本节中使用的数据是提前编辑并输入到离散仿真的算法中,可以认为数据来自于现实,而模型则根据现实的数据对系统的行为进行仿真和验证。在实际应用中,对于不同的场景,真实的数据不太可能采集得到(如硬件的随机失效),因此,进行输入数据的仿真则尤为重要。下篇文章讨论下对于随机输入数据的仿真。

4 架构仿真的过程总结

基于离散事件的系统架构仿真一般需要按照以下步骤执行:

  • 确定仿真的目标,如,是否使用队列缓存网络数据,是采用轮询机制还是事件触发机制,系统的负荷及对异常的响应时间如何,等等
  • 建立概念模型,对于复杂系统来说,建立概念模型的过程是对需求规范化和理解的过程,消除了歧义的系统概念模型有助于建立可计算模型。建立概念模型的过程可以理解为系统设计过程。
  • 规范化模型,在概念模型的基础上,收集数据(如,执行时间、延迟、输入模式等等)。通过这些数据,结合概念模型建立规范化模型。即使产品处于系统设计阶段,有些实际的数据暂时通过假设的方式输入,也对后续的实现过程有很大的指导意义。
  • 建立可计算模型,通过开发语言实现规范化模型到可计算模型的转化。开发语言的选择有多种,通用语言可以使用C/C++,其中基于C++的SystemC提供了全面的系统仿真能力;基于模型开发可以选择Simulink中的SimEvents组件,可利用MATLAB的强大数学支持及图形化的仿真环境;还可以使用专门的GPSS语言,用于仿真。
  • 验证,通过对设计过程的检查,确保正确的建立了模型。
  • 确认,通过对模型仿真过程与结果的分析,确保建立了正确的模型。如果出现问题,则需要对前面的过程进行迭代。

猜你喜欢

转载自blog.csdn.net/coroutines/article/details/107812167