软件设计中模块化的思想已日益普遍,模块化的设计能够使程序结构清晰,便于维护,开发起来也更加高效。大型软件通常由多个功能模块构成,模块的功能实现又是由多个线程所支撑的。诸如windows,Linux类型的操作系统自身能够对线程间的通信进行较好的管理,不需要过多的关心底层。而VxWorks 是一个多任务系统,任务是系统最基本的执行单元。功能模块间的通信也就是任务间的通信,VxWorks 对任务间通信的管理远没有windows的完善。在模块数量多,通信业务大的情况下VxWorks 提供的任务间通信机制不能很好的满足实时性与资源利用的要求。本文提出了一种任务间的通信模型,将用于网络通信的UDP方式引进到任务间的通信中,使通信更加灵活和便于管理,改善了整个系统的性能。
多任务实时操作系统VxWorks 简介
VxWorks 操作系统是一种嵌入式实时操作系统(RTOS),是嵌入式开发环境的关键组成部分,具有可靠高、实时性强、可裁减性的特点。VxWorks 为程序员提供了高效的实时任务调度、中断管理、实时的系统资源以及任务间通信。应用程序员可以将精力放在应用程序本身,而不必关心系统资源的管理。VxWorks 可以支持多达256个任务,支持二进制信号量、互斥信号量、消息邮箱等资源共享方式。高效实时的多任务内核使得VxWorks 能同时面对多个系列的MPU、MCU、DSP提供类同的API接口,其良好的移植性在跨处理器平台上只需要修改1%~5%的代码。
良好的持续发展能力、高性能的内核以及友好的用户开发环境,使VxWorks 在嵌入式实时操作系统领域占据一席之地。它以良好的可靠性和卓越的实时性被广泛地应用在通信、军事、航空、航天等高精尖技术及实时性要求极高的领域中,如卫星通信、军事演习、弹道制导、飞机导航等。
传统模块间通信方式存在的问题
嵌入式操作系统大多用于对实时性要求较高的场合,由于体积和成本的限制,嵌入式系统的资源和运行速度都不能和PC机相比。因此在此类系统上开发的应用程序的效率和减少资源消耗是十分重要的。VxWorks 是一个多任务操作系统,传统的任务间通信模型有以下几种:共享内存、信号量和消息队列三种,这几种方式都有其不足的地方。
共享内存方式使用全局变量或缓存,对于大型而复杂的程序,多个任务同时对一个变量进行读写操作会引起冲突或缓从区的溢出。信号量的通信模型虽然可以很好的起到互斥作用,但在多个任务同时与某一任务通信时就会产生对信号量的竞争,引起通信发起端的排队,降低系统的效率。消息队列可以通过异步的消息传送模型避免了由于信号量引起的排队问题,但任务数量较多时所需要的队列数量过大,会耗费大量系统资源。模块数量多,通信量大引起的任务排队和资源耗费会对VxWorks 的实时性和系统性能产生影响。下面介绍的通信模型在克服以上问题方面有着一定的优势。
通信模型的原理与性能分析
整个通信模型由业务模块、UDP插口、虚拟设备控制器(DEV)和统一定时器四部分组成。模块间采用基于UDP的通信方式进行信息交互,通过一种虚拟设备控制器(DEV)的概念将UDP插口与业务模块绑定,两者之间呈现一种松耦合的关系。每个模块拥有属于自己的DEV,负责管理UDP插口与其他模块进行交互。统一定时器负责协调和控制业务模块发送消息的时机。统一定时器与DEV相结合共同完成模块间的通信。
UDP本来是一种面向无连接的网络通信方式,把它引入到一个程序的内部作为通信手段需要考察其可靠性。UDP通信虽然是面向无连接的,在网络情况不好时有可能产生丢包,但由于程序是在一台主机上运行,程序内各个模块之间的交互也只限于本机上,并不经过网络,所以UDP包丢失的概率很小,通信可靠性是完全能够得到保证的。通信模型的原理图如下:
图1 通信模型原理图
1 虚拟设备控制器(DEV)的概念
虚拟设备控制器(DEV)本质上是一种数据结构,每个模块通过声明这样一个数据结构来获得属于自己的DEV。DEV中记录了有关UDP插口的信息,模块号或模块名称。DEV中还包括两个环形缓冲区用于和模块进行数据交互。UDP插口是与DEV紧密结合在一起的,但并不与模块直接联系,也就是说一个UDP并不固定属于某一模块,他们之间是一种松耦合的关系。DEV概念的引入将UDP套接字抽象为一种虚拟设备,供模块使用。使通信功能与模块的业务功能相互分离,更加独立,提高了效率。模块与UDP插口间也可以通过DEV进行灵活的配置与释放。DEV的结构如下:
Struct DEV
{
int Module_ID; //记录模块号;
int SocketKind; //记录插口类型;
int socket_ID; //记录socket句柄
struct RINGBUF_t m_Buf[2]; //两个环形缓冲区:[0]用于输入[1]用于输出
}
软件初始化时可以申请若干个空闲DEV设备,当某一业务模块启动时可以申请一个空闲的DEV供自己使用。将DEV结构体变量中的Module_ID填上该业务模块的号码就完成了模块与DEV的绑定。再申请一个UDP插口,将插口句柄存放在DEV结构体的socket_ID变量中就完成了UDP与DEV的结合,这样业务模块就通过DEV与UDP建立了联系,模块可以通过DEV来控制和使用该UDP进行通信。当模块不再需要使用UDP时,或者UDP插口出现故障时,可以将其拥有的DEV中的Module_ID和socket_ID置为0,取消模块与DEV的连接,完成对UDP的释放。当模块再次需要使用UDP时,可以重新申请DEV和UDP。这种送耦合的方式使得模块的正常运行不受UDP的影响,提高了程序的可靠性。
2 统一定时器的作用
统一定时器负责协调所有的UDP发送。程序初始化后启动统一定时器,定时器的时间一到就会执行特定的动作。它将轮询所有的DEV,将DEV发送缓存中的内容通过UDP发送出去。
3 通信的实现过程
①UDP消息的发送
当模块A的某个任务需要与其他模块通信时,它将把信息打包通过UDP方式发送给其他模块。模块A的任务(线程)将需要发送的消息包放入DEV的1号缓存中,统一定时器每到一定的时间就轮询所有的DEV,如果有信息等待发送就通过该DEV所控制的UDP插口将信息发往目的地。
② UDP消息的接收
如图2所示,每个模块有一个监视任务(线程),负责定时监视该模块对应的DEV所控制的UDP端口,一旦UDP接收到消息,该线程负责将收到的消息从UDP的缓存中读出存放到DEV的0号缓存中。模块的其他工作任务需要获得消息时可以从0号缓存中读取并解析消息。
图2 UDP消息的接收
4 通信模型的性能分析
上述通信模型采用准异步的方式,发送消息时各模块同步,接收消息时异步。能够避免多模块通信时的冲突,并且能够节省资源,在效率上优于传统的通信模型。松耦合的映射关系使得模块与通信端口之间能够灵活的结合与释放,给软件结构的设计带来了较大的自由。
①效率:采用了定时器轮询的消息发送方式,避免任务通信的冲突。模块向其他模块发送UDP消息时只需要将消息打包放入DEV的发送缓存中,包的发送工作由统一定时器完成。模块本身并不关心数据包的发送,将包放入DEV后就可以进行其他的工作。在多个模块同时与某一模块通信的情况下这种方式不会出现信号量模型中排队等待信号量的现象,提升了系统的效率,实时性得到了保证。由于所有模块的发送都由定时器完成,程序代码得到了精简,增强了代码的共用性。
②资源的利用:由于采用了UDP这种较为灵活的通信方式,模块间需要通信时才发送消息,而不必在各个模块间建立多条消息队列。当模块数较大时,这种方式在资源的节约上体现得更加明显。例如:有n个模块,两两间建立一对消息队列就需要(n-1)!对队列,程序要维护大量的消息队列,资源消耗严重。采用本文的通信模型将会节约有限的系统资源,充分适应嵌入式系统的开发。
③消息堆积问题的解决:传统通信模型中,模块发送消息是不加限制的。只要有消息需要发送就立刻调用发送函数,如果多个模块在一段时间内同时向一个模块发送消息,而接收消息的模块又来不及处理,消息不断的堆积就会引起缓冲区的益处。采用统一定时器的方法在一定程度上使消息的发送有序和受限,在定时器两次轮询某一模块的间歇期模块就能够处理前一次收到的消息,为接收新的消息作好准备。只要定时器的定时时间设置合适,就能够避免缓冲区的溢出。
通信模型的实现
Vxworks是一个多任务的操作系统,模型的各个组成部分可以通过任务来实现,VxWorks 的网络编程接口和定时器使用起来十分方便,对用户提供了开放的API。只需要使用socket(),send(),receive(),CreateTimer(),SetTimer()等函数就能够对套接字和定时器进行操作。在程序的初始化过程中可以先开启定时器任务,然后产生足够数量的DEV,接下来顺序启动各个模块。启动每个模块的过程中,首先申请一个空闲的DEV,然后产生一个UDP套接字,将套接字与DEV建立映射关系。完成了模块与DEV的绑定后,启动一个监视任务(Moniter_Task)监视UDP端口。到此,通信模型建立完毕。最后启动模块的其他工作任务开始正常的业务流程。
结束语
本文提出了一种不同于传统通信机制的模块间通信模型。将用于网络通信的UDP方式引入到程序内的模块间通信中,避免了传统方式可能引起的降低效率与资源耗费过多问题。通过送耦合的连接方式增强了程序的灵活性。在实验中,这种基于UDP方式的虚拟设备绑定的通信模型取得了较好效果,在对实时性要求较高的嵌入式系统开发中有着较高的价值。