任务间通信机制是多任务间相互同步和通信以协调各自活动的主要手段。VxWorks提供的任务间通信手段按其速度由快到慢包括信号量、消息队列、管道到网络透明的套接字。
任务间通信机制
  • 共享内存,数据的简单共享
  • 信号量,基本的互斥和同步
  • 消息队列和管道,同一CPU内多任务间消息传递
  • Sockets,远程调用,任务间透明的网络通信
  • Signals,用于异常处理
共享数据结构

任务共存于单一的线性地址空间。任一程序中定义的全局变量,都可以被所用任务直接访问。为了方便编程,自身定义几种数据类型:线性缓冲、环形缓冲、连接链等。可以被运行在不同上下文的代码引用。

连接链是一种双向连接的数据结构,定义在\target\h\lstLib.h中。

环形缓冲定义在\target\h\rngLib.h中。环形缓冲在用于任务和中断服务程序间传送字符非常有用。环形缓冲大小固定,以先进先出方式工作。

需要考虑互斥问题。仅仅服务于一个读任务和写任务时,不需要用信号量来控制对环形缓冲的访问。当读取任务读取下一个节点时,该操作将使该节点变为空,使得节点对新数据可用。如果读任务不能跟上写任务的速度,缓冲区将会溢出,应用数据可能丢失。这种情形需要较大的环形缓冲。

互斥

当一个共享地址空间简单地用于交换数据时,为避免竞争,需要对该内存的访问上锁。互斥方法包括:禁止中断,禁止抢占,和使用信号量对资源上锁。

中断上锁:intLock(); intUnlock(lock); 之间是不能被中断的临界区。

抢占上锁:taskLock(); taskUnlock(); 不能被中断的临界区;

中断上锁时间与控制抢占禁止时间尽可能短;

一种更好的机制是信号量。

VxWorks信号量是提供任务间通信、同步和互斥的最优选择,它提供任务间的最快通信,也是提供任务间同步和互斥的主要手段。

对于互斥,信号量可以上锁对共享资源的访问,并且比禁止中断或禁止抢占提供更精确的互斥粒度。

三种类型的信号量:

  1. 二进制:最快的最常用的信号量,可用于同步或互斥
  2. 互斥:为解决具有内在的互斥问题、优先级继承、删除安全和递归等情况而最优化的特殊的二进制信号量
  3. 计数器:类似于二进制信号量,但是随信号量释放的次数改变而改变。适合于一个资源的多个实例需要保护的情形

不仅提供主要为vxworks设计的wind信号量,同时为了使应用程序具有可移植性,也提供POSIX信号量。

信号量控制:wind信号量提供一套单一的接口用于控制信号量,类型仅仅由创建函数确定,其他接口根据需要处理的信号量类型自动完成相应的操作。

对一个需要互斥访问资源的操作由semTake()和semGive()对括起。

二进制信号量

二进制信号量用于互斥和同步情况下不同的状态顺序:

1、用于互斥时,信号量最初是满的、可用的,每个任务首先是取,然后放回;

2、用于同步时,信号量最初是空的、不可用的,一个任务首先是等待由其他任务释放的信号量。

互斥信号量

是一种特殊的二进制信号量,主要解决具有内在的互斥问题:优先级继承、删除安全和对资源的递归访问等。

基本行为与二进制信号量一致,不同之处:

  • 仅用于互斥
  • 仅能由取(semTake())它的任务释放
  • 不能在ISR中释放(semGive())
  • semFlush()操作非法
1、优先级倒置

互斥信号量有个选项:SEM_INVERSION_SAFE使用这个选项将使能优先级继承算法。必须与优先级队列SEM_Q_PRIORITY一齐使用。semId=semCreate(SEM_Q_PRIORITY|SEM_INVERSION_SAFE);

优先级继承协议确保拥有资源的任务以阻塞在该资源上的所有任务中优先级最高的任务有限执行。一旦这个任务的优先级被抬高,它将以这个高优先级运行,直至它持有的所有互斥信号量全部释放。然后,该任务返回正常状态。

2、删除安全:

在一个受信号量保护的临界区,经常需要保护在临界区执行的任务不会被以外地删除。删除一个在临界区执行的任务可能引起保护资源的信号量不可用,可能导致资源处于破坏状态,也就导致了其他要访问该资源的所有任务无法满足。

原语taskSafe()和taskUnsafe()提供了解决任务避免被意外删除的一种方法。同时,互斥信号量提供了选项SEM_DELETE_SAFE,该选项,每次调用semTake()时隐含使能taskSafe(),每次调用semGive()时隐含使能taskUnsafe().semId=semCreate(SEM_FIFO|SEM_DELETE_SAFE);

3、递归资源访问

在被释放之前,一个被递归获取N次的互斥信号量必须被释放与获取相同的n次。这由系统中的一个计数器来跟踪,每次semTake()调用计数器将加一,每次semGive()调用计数器将减一。

计数器信号量

除了像二进制信号量那样工作外,还保持对信号量释放次数的跟踪。信号量每次释放,计数器加一,每次获取,计数器减一,当计数器减到0,试图获取该信号量的任务被阻塞。

wind信号量提供在阻塞状态判断超时的能力,由semTake()特定的参数控制。参数是一定量的系统ticks数目,表示任务将要在阻塞态等待的时间。

如果任务在规定时间内成功地取得了信号量,semTake()返回OK,当规定的超时值已过,semTake()未能成功取得信号量而返回ERROR,系统设置errno的值。

NO_WAIT(=0)不要等待,如果调用的信号量不可用,将直接返回,系统将errno设置为S_objLib_OBJ_UNAVAILABLE。

一个以正整数n作为参数的semTake()调用,如果等待n个tick后,请求的信号量仍不可用,系统将errno设置为S_objLib_OBJ_TIMEOUT,调用返回。

WAIT_FOREVER(=-1)无限期等待,一直等到请求的信号量可用。

二进制信号量可以用于任务间同步,但是如果事件发生的足够快,可能导致数据丢失。即如果事件产生的速度超过任务处理该事件的速度,就可能会发生数据丢失。使用计数器信号量可以解决这一问题。

队列
先进先出FIFO

创建时确定队列类型,使用优先级继承的信号量必须选择使用基于优先级的队列。

消息队列

信号量提供高速的任务间同步和互斥机制,但常常需要一种较高级的允许合作任务之间相互通信的机制。VxWorks中,单CPU任务间主要的通信机制是消息队列。允许长度可变、数目可变的消息排队。任何任务或ISR可以发送消息到消息队列。任何任务可以从消息队列中接收消息。两个任务间全双工通信一般需要两个消息队列,每一个提供一个流通方向。

VxWorks提供两个消息队列函数库:

一个是msgQLib,提供Wind消息队列,专门为vxworks设计;

另一个是mqPxLib,提供与POSIX实时扩展标准(1003.1b)兼容。

一、Wind消息队列

该库按照FIFO排队的消息队列。但有一个例外,Wind消息队列有两个优先级,高优先级的消息将放在队列的头部。

消息队列由msgQCreate()创建,以它能够排队的最大的消息数目以及每个消息的最大字节长度作为参数。预先分配足够的缓冲空间。

超时
msgQReceive()和msgQSend()均可有超时作为参数。NO_WAIT(=0)意味着立即返回;WAIT_FOREVER(=-1)意味着程序永不超时。
紧急消息
msgQSend()使用一个参数来指定消息的优先级,正常MSG_PRI_NORMAL或紧急MSG_PRI_URGENT.正常优先级消息追加到消息队列的尾部,紧急优先级任务添加到消息队列的首部。
二、POSIX消息队列

初始化函数mqPxLibInit(),使得POSIX消息队列函数可用,在使用其他消息队列函数之前,系统初始化代码必须调用这个函数。在任务集合使用一个消息队列通信之前,其中之一必须以O_CREAT作为参数调用mq_open()创建这个消息队列,一旦消息队列已经创建,其他的任务可以根据其名字打开这个消息队列,然后可以收发消息。仅仅需要第一个打开这个队列的任务使用O_CREAT参数,后续的发送任务使用O_WRONLY参数打开该队列,接收任务使用O_RDONLY参数打开该队列,既收又发的任务使用O_RDWR作为打开函数的参数。

mq_send()将消息发送到消息队列。如果消息队列已满,如果一个任务试图向该队列发送消息,这个任务将被阻塞,直到另外的任务从这个队列中读走一个消息,使得空间可用为止。为避免该函数阻塞,可以使用O_NONBLOCK作为参数打开消息队列。消息队列 满,返回-1,并设置errno为EAGAIN,而任务不会阻塞,允许程序再试一次或采取其他合适的操作。

mq_receice()同上。

mq_send()一个参数用来指定优先级,从0(最低)~31(最高)。

mq_close()关闭队列,但不会摧毁,仅仅是保证调用任务不再使用它。

mq_unlink()不会立即摧毁队列,阻止以后的任务打开该队列,并将该队列名字从名字表中移出,当最后关闭了该队列,队列就摧毁了。

mq_notify()要求系统当有一个消息进入一个空的消息队列时通知它,这样可以避免任务阻塞或轮询等待一个消息。

该函数以消息进入空队列时系统发给任务的信号(signal)作为参数。

mq_notify()机制只对当前状态为空的消息队列有效,当该队列有新的消息可用时,提醒任务。如果消息队列已有可用消息,当有更多消息到达时,通知不会发生。如果有其他任务因调用mq_receive()阻塞在该队列上,也不会通知这个使用mq_notify()注册的任务。

每个队列任意时刻只能有一个注册等待通知的任务,一旦队列已经有一个任务等待通知,不会接受其他任务调用mq_notify()的注册,直到这个通知请求已经满足或者取消。

用NULL代替通知信号调用mq_notify()函数,删除一个请求通知,仅仅当前注册的任务才能删除它的通知信号。

POSIX和Wind消息队列比较
特征 Wind消息 POSIX消息队列(可移植性)
消息优先级 1 32
阻塞的任务队列 FIFO或基于优先级 基于优先级
不带超时的接收 可选 不可用
任务通知 Not available 可选(一个任务)
关闭/解链语法
管道

管道使用VxWorks I/O系统,可提供能与消息队列互换的功能。

是一种由pipeDrv驱动程序管理的虚拟I/O设备。函数pipeDevCreate()创建一个管道设备以及与该管道相联系的底层消息队列。

调用指定了要创建管道的名字、能够排队的消息的最大数目、每个消息的最大长度等信息。

status=pipeDevCreate("/pipe/name",max_msgs,max_length);

任务能够使用标准I/O程序来打开、读、写管道,也可以调用ioctl函数设置控制属性。当从一个没有数据可用的空管道读数据时,任务会堵塞。像对消息队列的操作一样,ISRs能够向管道写,但不能读,像I/O设备。

管道能提供消息队列不能提供的一个重要特征,可以使用select()函数。它允许任务等待一个I/O设备集合之一的数据可用。select()函数也能够与其他的异步I/O设备一起工作,包括网络套接字和串行设备。因此,使用select()函数,任务能够同时等待几个管道、套接字和串行设备集合上的数据。

vxworks应用中的管道是一个先进/先出的缓冲。

一个任务可以创建一个管道,一旦管道创建,任务可以直接调用read()和write()语句对之进行读写访问。如果任务试图向一个已满的管道执行写操作,该任务将等待(挂起)。

管道强调文件描述符,消息队列缺乏这种能力。可以提供select和其他基本I/O操作。