数据结构论坛

首页 » 分类 » 定义 » 简单模块仿真概念
TUhjnbcbe - 2023/6/8 20:56:00
白癜风资深专家 https://wapjbk.39.net/yiyuanfengcai/ys_bjzkbdfyy/791/

为了介绍OMNeT++的概念和实现时的一些术语,这里首先简要地介绍离散事件仿真(DES)是如何工作的。

1离散事件仿真

离散事件是指事件的状态改变在时间上是离散的,并且认为事件的发生在瞬间完成。假定相邻发生的两个事件之间没有其他事件,即其他事件的状态均不发生改变(连续系统的状态改变也是连续的)。可以被视为离散事件的系统才能用离散事件仿真系统建立模型(其他系统可以使用连续仿真系统建立模型)。

例如,计算机网络可以认为是离散事件系统。其中发生的事件可以包括如下几项。

包传输开始。

包传输结束。

超时重传。

这说明两个事件之间,比如包传输开始和包传输结束时,没有其他“兴趣”事件发生,即包的状态是正在传输。“兴趣”事件的定义和状态通常依赖于建模的动机和目的。如果对传输的单个比特感兴趣,我们将把比特传输开始和比特传输结束列为“兴趣”事件。

事件发生的时间通常称为事件时间戳,在OMNeT++中称为到达时间(因为在类库中,“timestamp”为保留字,用于设置事件类中的用户属性)。模型内的时间(模型时间或虚拟时间)称为仿真时间,不同于真实的时间或CPU周期所指代的仿真运行的时间以及CPU运行消耗的时间。

2事件循环

在离散事件仿真中,特征事件集的维护被定义在一个叫FES(FutureEventSet)或FEL(FutureEventList)的数据结构中。这些数据结构通常根据以下伪代码工作。

初始化–包括建立模型和插入初始事件到FES中

while(FES不为空且仿真没有结束)

{

从FES中返回第一个事件

t=该事件的时间戳

处理事件

(处理过程可能会在FES中增加新的事件或删除已存在的事件)

}

结束仿真(记录数据结构等)

首先,初始化过程要先建立能够反映仿真模型的数据结构,调用所有用户定义的初始化代码,在FES中插入初始事件以保证仿真能够开始。不同仿真之间的初始化策略可能差异很大。

随后的循环从FES中读取事件并处理它们。所有事件均严格按照时间戳顺序处理以保证先后关系,即当前事件不会对之前的事件造成影响。

处理事件包括调用用户提供的代码。例如,对计算机网络仿真时,处理“超时”事件可能会包括重传网络的包副本,更新重传计数器,调度其他“超时”事件等。用户代码还可能会从FES中去除事件,如取消超时。

当FES中没有事件剩余(实际中很少发生),或仿真时间超过设定的时间阈值且没有必要继续进行,或数据达到要求的精度时,仿真将停止。此时,在程序退出前,用户一般会将统计数据记录到输出文件中。

OMNeT++中的简单模块

在OMNeT++中,事件发生在简单模块内部。简单模块封装了C++代码产生的事件,并与其他事件相互作用,换句话说,就是实现模块的行为。

用户通过cSimpleModule类的子类创建简单模块类型,该类是OMNeT++类库的一部分,和cCompoundModule类一样,都是从公共基类cModule派生而来的。

cCompoundModule类尽管打包了仿真相关的函数,但其本身并不做任何工作,需要重新定义一些虚拟的成员函数来使其工作。

这些成员函数如下所示。

voidinitialize()。

voidhandleMessage(cMessage*msg)。

voidactivity()。

voidfinish()。

在初始化阶段,OMNeT++构建了网络,创建了必要的简单模块和复合模块,并根据NED定义连接了这些模块。OMNeT++也调用所有模块的初始化函数initialize()。

事件处理期间调用handleMessage()和activity()函数,用户在这些函数中实现模块行为。handleMessage()和activity()分别实现不同的事件处理策略:对于每个简单模块,用户需要重新精确定义其中某个函数。

当模块收到信息后会通过仿真内核调用handleMessage()函数,activity()函数则是基于协同操作的解决方法,是实现交互处理的途径(协同操作是非抢先线程,如合作)。通常,我们更推荐使用handleMessage()而不是activity(),因为activity()不能很好地度量。

包含activity()和handleMessage()的模块能够在仿真模型内自由复合。

当仿真成功终止时调用finish()函数。通常使用finish()会记录仿真过程中收集的数据。

4OMNeT++中的事件

OMNeT++使用消息代替事件,每个事件都是cMessage或其子类的实例表示,没有单独的事件类。消息从一个模块发送至另一个模块,这表明“事件发生地”是消息的目的模块,事件发生的模型时间是消息的到达时间。像“超时”等事件则通过模块给自身发送消息来实现。

FES内的事件读取是按照到达的时间顺序进行,以保证先后关系。更详细的,若给定两个消息,则遵循下述规则。

先到达的消息首先执行。

若到达时间相同,优先级权小的先执行。

若优先级相同,预定义的或发送早的先执行。

优先级是用户指定的整型消息属性。

5仿真时间

当前仿真时间可以由SimTime()函数得到。在OMNeT++中,仿真时间用C++类型simTime_t表示,默认为SimTime类的typedef。SimTime类可以存储的仿真时间为64bit整型数,以十进制定点数的形式表示。精度由度量指数全局结构变量控制,即SimTime实例拥有相同的精度。指数范围可以在18(0秒量级精度)到0(秒量级精度)。表1介绍了各类指数及其范围。

表1SimTime类的指数、精度及范围

尽管仿真时间不能为负值,表示为负数时也是有意义的,因为它们常常由算术表达式评估得出。

SimTime类执行64bit整型的加减运算时,整数溢出会导致仿真终止并伴随着一个错误信息。执行其他运算(乘、除等)时则使用双精度型,结果再转换成整型。

SimTime转成双精度型并不是默认的,因为这会与重载的SimTime算术运算相冲突,一般使用SimTime类中的dbl()进行转换。为了减少dbl()的使用,一些函数自带重载变量,从而可以直接使用SimTime,例如fabs()、fmod()、ceil()、floor()、uniform()、exponential()和normal()等。

注意

从SimTime转换为双精度的过程中可能会丢失精度,因为双精度只有52bit的尾数。

SimTime的其他有用方法还有:str()以字符串形式返回值;parse()把字符串转换为SimTime;raw()返回包含潜在位数的64bit整型值;getScale-Exp()返回全局度量指数;getMaxTime()返回当前度量指数能够表示的最大仿真时间。

兼容性:

OMNeT++早期的版本中,仿真时间使用双精度。为了方便OMNeT++4.0及后续版本的端口存在模型,OMNeT++可以用双精度对simtime_t编译。若允许这个模式,需要在编译OMNeT++和仿真模型时定义USE_DOUBLE_SIMTIME预处理程序宏。

在仿真模型中,有些宏可以同时使用双精度型和SimTime仿真时间编译:SIMTIME_STR()把仿真时间转换为字符型常量(可以用在printf变元表中);SIMTIME_DBL(t)把仿真时间转换为双精度型;SIMTIME_RAW(t)返回包含潜在的64bit整型和双精度型;STR_SIMTIME(s)把字符串型转换为仿真时间;SIMTIME_TTOA(buf,t)把仿真时间转换为字符串型,并把结果存在给定缓存器中。MAXTIME()对于两种simtime_t类型均能正确定义。

注意

为什么OMNeT++要使用基于64bit整型的仿真时间呢?因为双精度比特数只有52bit,在长时间仿真而又需要细密时间分割的情况下会出现问题,比如MAC协议。其他问题有:近似误差的累积、非结合性(通常(x+y)+z≠x+(y+z),即两个双精度型仿真时间不能保证算术上的等值性)等。

1
查看完整版本: 简单模块仿真概念