1引言
VxWorks是美国Wind River Systems公司开发的一种嵌入式实时操作系统,它以高性能可剪裁的微内核以及友好的用户开发环境在嵌入式实时操作系统领域占有重要的地位。在很多实时应用场合(如嵌入式地理信息系统) ,需要在VxWorks环境下设计图形用户界面(GUI) 。VxWorks提供了两种开发图形用户界面的组件:WindML (Wind多媒体库)和Zinc。WindML为图形、图象和声音应用程序的开发提供了基本技术,并且提供了为定制设备开发标准化驱动程序的框架。Zinc是以WindML为基础的, Zinc是为应用创建图形用户界面提供框架的可扩展的面向对象的类库。Zinc为实现复杂的图形用户界面提供了更多的支持。
为了提高性能和简化源代码,大多数情况下在一个实时环境中的应用程序是以多任务的方式实现的, Zinc自身通常也是多任务的。一般不在外部任务里直接处理GUI ,而是在一个专用的GUI任务里执行所有的GUI处理。在多任务环境下,非GUI任务将需要在不同的时期和GUI任务通信, GUI也可能需要与外部任务通信。GUI任务与非GUI任务之间的通信方式将对整个实时系统的实时性、可靠性和稳定性产生重大的影响。因此, 深入研究VxWorks环境下Zinc所支持的GUI任务与非GUI任务之间的通信方式并确定在特定应用场合选择通信方式的原则是非常重要的。
2GUI( Zinc)任务和外部任务之间的通信方法
GUI任务和非GUI任务之间的通信大致可以分为两种:同步通信和异步通信。Zinc主要提供了如下几种通信机制供程序员选择使用:
Zinc入口点
共享内存
OS消息队列
派生设备
a)Zinc入口点
可以利用Zinc入口点实现GUI任务与非GUI任务之间的通信,主要有如下两种方法:
与Zinc通信的基本入口点是ZafEventManager::Put()程序。利用该函数可以在Zinc事件队列中放置一个事件。这是一个异步方法,因此只能适用于可接受异步通信的场合。
ZafApplication::BeginSynchronize() 和ZafApplication::EndSynchronize() 。这两个函数自身并不是通信程序,但能确保直接通信方式访问Zinc是安全的。直接通信通常由一个对象的Event()函数来实现,也可能采用其它的方式,比如数据对象更新。间接通信可以使用ZafEventManager::Unblock()函数来实现。使用一个派生设备或其它方法进行通信的时候,使用该函数可以使正在等待事件的Zinc任务解除阻塞。
b)共享内存
在VxWorks中很容易实现共享内存。为了安全地共享内存,最好让一个信号量和共享内存关联。这样可以避免任务之间出现资源冲突。
使用共享内存的时候,通常不需要使用Zinc 提供的保证线程安全的入口点。但是当一个窗口对象的某个成员指向共享内存的时候是一个例外情况。例如,如果一个ZafButton的bitmapData成员正指向共享内存,除非已经确保Zinc任务目前没有使用该共享内存,才能安全地更新该共享内存。可以使用ZafApplication::BeginSynchronize() 和ZafApplication::EndSynchronize() 函数来保证在某一时刻该任务是唯一使用该共享内存的任务。
c)OS消息队列
在VxWorks环境下使用Zinc的时候,有两种不同类型的消息队列可供选用:事件管理器消息队列和VxWorks操作系统提供的消息队列。
可以通过ZafEventManager::Put()和ZafEventManager::Get()函数访问事件管理器消息队列。事件管理器队列只提供从非GUI任务到Zinc任务的通信,利用事件管理器队列进行从GUI任务到非GUI任务的通信是不实用的。
VxWorks消息队列可以实现从GUI任务到非GUI任务的通信,也可以实现从非GUI任务到GUI任务的通信,但是不允许同时在两个方向上进行通信。
d)派生设备
选用共享内存或消息队列作为通信方法时,Zinc需要与该通信方法进行交互,这可以通过派生设备实现。使用派生设备的目的是为了检查是否有来自另一个任务的通信。每当ZafEventManager::Get() 函数被调用,事件管理器轮询该设备,看看是否有新消息。这个派生设备仅仅需要检查共享内存或消息队列。如果有新信息可用,派生设备可以直接调用对象的Event() 函数在队列上面放置一个新事件,也可以自己处理这个消息。
派生设备还可用于实现从GUI任务到非GUI任务的通信。ZafEventManager::UnBlock()函数对这种通信方法是非常有用的。在正常的情况下,如果没有需要处理的事件, Zinc会阻塞自己。如果采用一个派生设备监听一个VxWorks消息队列,向该队列发送一个消息后解除事件管理器的阻塞可以更及时地轮询该派生设备。派生设备自身不会阻塞,也不会导致Zinc暂停。
3选择通信方式的原则
上述关于GUI任务的通信方法各有其优缺点。在选择通信方法的时候,应该以具体的应用场合为依据,一般应遵循如下的原则:
a) 应该尽可能选用简单的通信方式。
在大多数情况下, Zinc入口点足够用。Zinc入口点是最简单的关于GUI任务的通信方式,因为它们不需要Zinc 任务内部的任何专门代码。可用的最简单入口点是ZafEventManager::Put()函数。然而,它有下列缺点:第一,它只允许从非GUI任务到GUI任务的通信;第二,它是异步的;第三,因为要防止ZafEventManager::Get() 和ZafEventManager::Put()函数同时访问Zinc事件队列以对其进行保护, ZafEventManager::Put() 可能会阻塞。
如果异步通信是可接受的,但是不能接受阻塞,可以采用下列两种方法:第一,使用ZafEventManager::Put ( )函数,并且另外有一个可被阻塞的任务向Zinc队列中放置事件。这个任务可以监听一个OS消息队列,而原先产生消息的任务正是使用OS消息队列来发送消息; 第二,创建一个设备以监听OS消息队列,产生消息的任务发送一个消息给OS消息队列,然后由派生设备接收并解释。派生设备可以放置一个事件在Zinc队列中,或者自己处理这个事件。只是这两种方法都给应用程序增加了一点复杂性。
b) 如果需要进行同步通信,必须使用函数对ZafApplication::BeginSynchronize() 和ZafApplication::EndSynchronize() 。
调用ZafApplication::BeginSynchronize()之后,可以保证对Zinc对象的任何访问是安全的。该方法很简单,且不需要在GUI任务中添加专门的代码。使用ZafApplication::BeginSynchronize() 的缺点是该函数会阻塞,使用该方法时必须采取预防措施。
c) 采用共享内存进行通信时必须创建保护和同步机制
共享内存是从GUI任务到非GUI任务的两种通信方法之一,其优点是对数据的访问简单而直接。共享内存没有对数据访问进行保护的内在支持,所以必须创建一个对访问进行保护及同步的机制,并且访问共享内存的所有任务都应该使用该机制。采取这种方案的缺点是容易发生阻塞。
d) 在不能接受阻塞的应用场合,最好使用OS消息队列。
OS消息队列是从GUI任务到非GUI任务和从非GUI任务到GUI任务进行通信的另一种方法。使用OS消息队列进行通信的时候,需要在GUI任务和非GUI任务中编写访问消息队列的代码。在正确进行设置的情况下,消息队列不会引起阻塞的问题。创建消息队列时,必须保证消息队列有足够的消息容量或者建立处理消息队列溢出的机制。
4Zinc的事件模型
Zinc中的GUI任务与非GUI任务的多种通信方式都与Zinc的事件模型有关,因此在设计和实现GUI任务与非GUI任务之间的通信时,需要对Zinc的事件模型有深入的理解。Zinc具有一个事件驱动的体系结构。输入设备与应用程序之间的交互是通过事件完成的。由于VxWorks本身不是事件驱动的实时操作系统,在VxWorks运行平台中, Zinc主要从输入设备和应用任务获取事件。
然后Zinc以标准的方式将这些事件打包,并且将它们路由给适当的对象以进行进一步的处理。在EGIS中, GSM通讯任务使用了自定义的事件与GUI任务进行异步通信。基于Zinc的EGIS事件模型如图1所示。
图1基于Zinc的EGIS事件路由示意图
从图1中,在VxWorks中Zinc事件的主要来源是输入设备和应用程序(比如GSM通信程序) ,由于VxWorks不支持事件驱动的系统,事件管理器周期性地查询或接收来自输入设备的数据并以Zinc定义的事件结构包装成事件。一旦事件管理器获得事件, Zinc主控进程重新获得对应用程序的控制,该进程从时间管理器中得到事件并传送给窗口管理器。窗口管理器决定事件的最终目的地和合适的路由并将其发送。最终窗口对象的Event方法收到每个事件并对其进行处理。在EGIS中,通过重载Event函数实现对自定义事件的处理。
5EGIS 系统中GUI任务与非GUI任务之间通信的实现
嵌入式地理信息系统包括了两个部分,跑车分系统和中心显示分系统。跑车上包括一台PC机、一台GPS接收机和一台短信收发设备。中心包括一台VxWorks目标机、一台PC机(用于开发和调试)和一台短信收发设备。其中,VxWorks目标机上运行的是EGIS各功能模块。
为了提高系统性能和简化代码,将目标机上的EGIS软件划分为两个任务: GUI任务和GSM通信任务。GUI任务的主要功能是:负责界面和菜单的实现,并且需要根据GSM实时接收到的经纬度数据在地图上画出跑车的运行轨迹。GSM通讯任务的主要功能是:实时接收以短消息方式传输的GPS经纬度数据,存放在环形缓冲区中,并通知GUI任务。
EGIS系统需要满足两方面的实时性:第一, GSM通信任务接收经纬度数据的实时性;第二, GUI任务响应菜单操作的实时性和特定情况下画出跑车轨迹的实时性。为此,在该系统中采取两种通信方式: Zinc入口点和共享内存。GUI任务与非GUI任务之间通信的实现主要包括如下几个方面:
a)用户事件的定义
Zinc中的事件共分为七类,其中包括用户事件。用户事件的取值范围为10,000到32,767。在EGIS中定义的用户事件为:
const ZafEventType TRACKING = 10028;
其中, ZafEventType是Zinc事件类型。
b)用户事件的发送
用户事件的发送通过下面的调用完成:
zafApplication-> EventManager() -> Put(TRACKING)
其中, zafApplication是Zinc全局变量,也是GUI任务的任务变量。某些程序可能同时被多个任务调用,这些程序可能要求全局变量或静态变量对于每个调用该程序的任务具有一个不同的值。为了适应这种情况, VxWorks提供了一种所谓任务变量的机制。一个4字节的变量可以以任务变量的方式被添加到一个任务的上下文中,这样每当任务切换的时候同时切换该变量的值。由于在GUI任务和GSM通信任务中的zafApplication具有不同的值,因此,可以在创建GSM通信任务的时候通过参数传递使GSM通信任务获得GUI任务的任务变量zafApplication:
if (GSMTaskID = taskSpawn("SERIAL_PORT",90, VX_FP_TASK,
ZAF_VXW_STACK_SIZE, (FUNCPTR)Trace,
(int)zafApplication,0,0,0,0,0,0,0,0,0) != ERROR)
{
taskVarAdd(GSMTaskID,(int*)
&zafApplication);
/*将zafApplication添加为任务变量*/
} /*通过参数传递GUI任务的任务变量zafApplication*/
在GSM通信任务中:
int Trace(CGIS_Window * pWindow,ZafApplication *application)
{
……
taskVarAdd(0,(int*)&zafApplication);
/*将zafApplication添加为任务变量*/
zafApplication = application; /*GSM通信任务变量zafApplication赋值*/
zafApplication->EventManager() -> Put(TRACKING) /*发送用户事件*/
……
}
c)用户事件的处理
ZafEventType CGIS_Window::Event(const ZafEventStruct& event)
{
ZafEventType code;
if ( event_type == TRACKING) /*判断是否用户定义事件TRACKING*/
{
……
m_pMainController->ZoomToPosition ( Lat_Long[0],Lat_Long[1]); /*事件处理函数*/
code = TrackCode;
return (code);
}
else
{
……/*处理其他事件*/
}
d)任务之间的数据共享
共享数据是通过下面的全局变量数据实现的。GSM任务收到数据之后,首先将经纬度数据存入数组Lat_Long中,然后将变量PntNumber加1,而GUI任务首先读取PntNumber,然后处理Lat_Long中的数据。因此不会产生共享冲突。
float Lat_Long [ 2000 ]; /*存放通过GSM终端所收到的经纬度数据*/
int PntNumber; /*存放通过GSM终端所收到的经纬度数据的个数*/
6结论
本文介绍了VxWorks支持的任务之间进行通信的各种方式,给出了在特定应用场合选择任务之间通信方式的原则,介绍了Zinc的事件模型,并详细地给出了嵌入式地理信息系统中非GUI任务与GUI任务进行通信的实现方式。实验证明,该通信方式能够满足嵌入式地理信息系统各方面的实时性要求。