天天看点

嵌入式操作系统VxWorks简介(转贴)

VxWorks操作系统是美国WindRiver公司于1983年开发的一种嵌入式实时操作系统(RTOS),是嵌入式开发环境的关键组成部分。

实时操作系统和分时操作系统的区别

        从操作系统能否满足实时性要求来区分,可以把操作系统分成分时操作系统和实时操作系统。

        分时操作系统按照相等的时间片调度进程轮流运行,分时操作系统由调度程序自动计算进程的优先级,而 不是由用户控制进程的优先级。这样的系统无法实时响应外部异步事件。

        实时操作系统能够在限定的时间内执行完所规定的功能,并能在限定的时间内对外部的异步事件作出响应。分时系统主要应用于科学计算和一般实时性要求不高的场合。实时性系统主要应用于过程控制,数据采集,通信,多媒体信息处理等对时间敏感的场合。

VxWorks的特点

-可靠性

-实时性

实时性是指能够在限定时间内执行完规定的功能并对外部的异步事件作出响应的能力。实时性的强弱是以完成规定功能和作出响应时间的长短来衡量的。

 VxWorks的实时性做的非常好,其系统本身的开销很小,进程调度,进程间通信,中断处理等系统公用程序精练而有效,它们造成的延迟很短。VxWorks提供的多任务机制中对任务的控制采用了优先级抢占(Preemptive Priority Scheduling)和轮转调度(Round-Robin Scheduling)机制,也充分保证了可靠的实时性。

-可裁减性

用户在使用操作系统时,并不是操作系统中的每一个部件都要用到。例如图形显示,文件系统以及一些设备驱动在某些嵌入式系统中往往并不使用。

VxWorks由一个体积很小的内核及一些可以根据需要进行定制的系统模块组成。VxWorks内核最小为8KB。

对一个实时内核的要求

一个实时操作系统内核需要满足许多特定的实时环境所提出的基本要求,这些包括:

-多任务:由于真实世界的事件的异步性,能够运行许多并发进程或任务是很重要的。

-抢占调度:真实世界的事件具有继承的优先级,在分配CPU的时候要注意这些优先级。当一个高优先级的任务变成可执行态,它会立即抢占当前正在运行的较低优先级的任务。

任务间的通讯与同步:在一个实时系统中,可能有许多任务作为一个应用的一部分执行。系统必须提供这些任务间的快速且功能强大的通信机制。内核也要提供为了有效地共享不可抢占的资源或临界区所需的同步机制。

任务与中断之间的通信:尽管真实世界的事件通常作为中断方式到来,但是为了有效的排队,优先化和减少中断延时,我们通常希望在任务级处理响应的工作。所以需要复杂任务级和中断级之间存在通信。

二、系统编程方法

实时系统主要包括:多任务调度(采用优先级抢占方式),任务间的同步和进程间的通信机制。

一个多任务环境允许实时应用程序以一套独立任务的方式构筑,每个任务拥有独立的进程和它自己的一套系统资源。进程间通信机制使得这些任务的行为同步、协调。wind使用中断驱动和优先级的方式。它缩短了上下文转换的时间开销和中断时延。在VxWorks中,任何例程都可以被启动成为一个单独的任务,拥有它自己的上下文和堆栈。还有一些其他任务机制可以使任务挂起、继续、删除、延时或改变优先级。

另一个重要内容是:硬件中断处理。硬件产生中断,统治系统调用相应的中断历程(ISR),为了系统得到尽快的响应,ISR在它自己独立的上下文和堆栈中运行。它的优先级高于任何任务优先级。

中断延迟(Interupt Latency) 中断延迟是指从硬件中断发生到开始执行中断处理程序第一条指令之间的这段时间。

优先级驱动(Priority-Driver)优先级驱动是指多任务系统中,当前运行的任务总是具有最高优先级的就绪任务。

多任务调度

两种方式:优先抢占和轮转调度(Preemptive Prioriy, Round-Robin Scheduling)

优先抢占(Preemptive Priority):每一个任务都有一个优先级,系统核心保证优先级最高的任务运行于CPU。如果由任务优先级高于当前的任务优先级,系统立刻保存当前任务的上下文,切换到优先级高的上下文。

抢占(Preemptive):抢占是指系统处于核心态运行时,允许任务的重新调度。换句话说就是指正在执行的任务可以被打断,让另一个任务运行。抢占提高了运用对异步事件的响应性能力。操作系统内核可抢占,并不是说任务调度在任何时候都可以发生。例如当一个任务正在通过一个系统调用访问共享数据时,重新调度和中断都被禁止。

任务上下文(Task Context):任务上下文是指任务运行的环境。例如,针对x86的CPU,任务上下文可包括程序计数器,堆栈指针,通用寄存器的内容。

上下文切换(Context Switching):多任务系统中,上下文切换是指CPU的控制权由运行的任务转移到另外一个就绪任务时所发生的事件,当前运行任务转为就绪(或者挂起,删除)状态,另一个被选定的就绪任务成为当前任务。上下文切换包括当前任务的运行环境,恢复将要运行任务的运行环境。上下文的内容依赖于具体的CPU.

轮换调度(Round-Robin Scheduling):使所用相同优先级,状态为ready的任务公平分享CPU(分配一定的时间间隔,每个任务轮流享有CPU)。

系统由256个优先级,从0到255,0为最高,255为最低。任务在被创建时设定了优先级。也可以用taskPrioritySet()来改变任务的优先级。

任务的主要状态为:READY, PEND, DELAY, SUSPEND

轮转调度(Round-Robin):轮转调度可以扩充到优先抢占方式中,当多个任务优先级相同的情况下 ,轮换调度算法使任务按平等的时间片运行于CPU,共享CPU。避免一个任务长时间占用CPU,而导致其他任务不能运行。可以用kernelTimeSlice()来定义时间长度。

taskLock()和taskUnlock()用来取消优先抢占方式,和恢复优先抢占方式。

注意:一个任务可以调用taskDelete()删除另一个任务,但是如果一个当前正在运行的任务被删除后,该任务的内存没有释放,而其他任务不知道,依然在等待,结果导致系统stop。用taskSafe()和taskUnsafe()来保证正在运行的任务不被删除。

用法如下:

taskSafe();

semTake(semId,WAIT_FOREVER);

.....critical region

semGive(semId);

semGive(semId);

taskUnsafe();

任务间的同步和进程间协调

 信号量作为任务间同步和互斥的机制。在wind核中有几种类型的信号量,它们分别针对不同的应有需求;二进制信号,计数信号量,互斥信号量和POSIX信号量。所有的这些信号量是快速和高效的,它们除了被运用在开发设计进程中外,还被广泛应有在VxWorks高层应有系统中。对于进程间通信,wind核也提供了如消息队列,管道,套接字和信号等机制。

任务间的同步和进程间协调的几种方式:

1. 内存共享(shared memory),对简单的数据共享而言

2. 信号量(semaphore),基本的互斥和同步

3. 消息队列(message queues)和管道(Pipe),单个CPU中,任务间的消息传递

4. 套接字(Socket)和远程调用(Remote procedure calls),相对于网络任务间的通信。

5. 信号(Signals),出错处理(Exception handling)

内存共享(Shared Memory)

任务间通信最通常的方式是通过共享的数据结构进行通信。因为所有VxWorks的任务存在于一个单一的 线性地址空间,任务间共享数据。全局变量,线性队列,环形队列,链表,指针都可以被运行在不同上下文的代码所指。

互斥(Mutual Exclusion)

互斥是用来控制多任务对共享数据数据进行串行访问的同步机制。在多任务应用中,当两个或多个任务同时访问共享数据时,可能会造成数据破坏。互斥使它们串行地访问数据,从而达到保护数据的目的。

解决互斥的几种方法:

1. 关部中断的方法(intLock):能解决任务和中断ISR之间产生的互斥。

funcA()

{

           int lock =intLock();

           ...critical region that cannot be interrupted

            intUnlock(lock);

}

但在实时系统中采取这个方法会影响系统对外部中断及时响应和处理能力。

2. 关闭系统优先级(taskLock):关闭系统优先级,这样在当前任务执行时,除了中断外,不会有其他优先级高的任务来抢占CPU,影响当前程序的运行。

    funcA()

   {

           taskLock();

           ....critial region that cannot be interrupted

           taskUnlock();

    }

这种方法阻止了高优先级的任务抢先运行,在实时环境中也是不适合的,除非关闭优先级的时间特别短。

信号量(Semaphore):信号量是解决互斥和同步协调进程的最好的方法

VxWorks信号量提供最快速的任务间通信机制,它主要用于解决任务间的互斥和同步。针对不同类型的问题,有以下三种信号量:

二进制信号量(binary)使用最快捷、最广泛,主要用于同步或互斥

互斥信号量(mutual exclusion)特殊的二进制信号量,主要用于优先级继承,安全删除和回溯;

计数器信号量(counting)和二进制信号量类似,保持信号量被释放(gaven)的次数,主要用于保护一个资源的多个例程(multiple instance of a resource)

信号量控制,函数介绍:

semBCreate()分配并初始化一个二进制信号量

semMCreate()分配并初始化一个互斥信号量

semCCreate()分配并初始化一个计数器信号量

semDelete()终止一个自由的信号量

semTake()占有一个信号量

semGive()释放一个信号量

semFlush()解锁所有等待信号量的任务

semBCreate(),semMCreate(),and semCCreate()返回一个信号量ID作为其它后续任务使用该信号量的句柄。当一个信号量被创建,它的队列(queue)类型就被确定。等待信号量的任务队列以优先级的高低排列(SEM_Q_PRIORITY),或者一个先到先得的方式排列(SEM_Q_FIFO)。

当一个Semaphore创建时,指定了任务队列的种类。

A. semBCreate(SEM_Q_PRIORIY, SEM_FULL), SEM_Q_PRIORITY指明处于等待状态的任务在等待队列中以优先级的顺序排列

B. semBCreate(SEM_Q_FIFO, SEM_FULL), SEM_Q_FIFO指明处于等待状态的任务在等待队列中以先进先出的顺序排列。

互斥进程(Mutual Exclusion)

互斥信号量有效的内锁对共享资源的进入,与屏蔽中断(disabling interrupt)和优先级锁定(preemptive locks)相比,二进制信号量将互斥的范围限制在仅与其有关的资源上。从技术上说,创建一个信号量来保护(guarding)资源。信号量初始化位可用的(FULL)。

当一个Semaphore创建时,指定了这个semaphore是用在解决互斥还是用于同步任务

A. semBCreate(SEM_Q_FIFO, SEM_FULL), SEM_FULL指明用于任务间互斥

     SEM_ID semMutex;

     semMutex = semBCreate(SEM_Q_PRIORITY, SEM_FULL);

    当一个任务要进入资源,首先要得到一个信号量(take that semaphore),只要有任务在使用这个信号量,其它的要进入资源的任务要停止执行(blocked from execution),当这个任务完成了对资源的使用,它会释放信号量,允许另一个任务要使用资源。

semTake(semMutex, WAIT_FOREVER);

...critical region, only accessible by a single task at a time.

semGive(semMutex);

同步协调进程(Synchronization)

B. semBCreate(SME_Q_FIFO, SEM_EMPTY), SEM_EMPTY指明用于任务间同步

#include "vxWorks.h"

#include "semLib.h"

SEM_IDsyncSem;

init(int someIntNum);

{

   intConnect(INUM_TO_IVEC(someIntNum), eventInterruptSvcRout, 0);

   syncSem = semBCreate(SEM_Q_FIFO, SEM_EMPTY);

  taskSpawn("sample", 100, 0, 20000, task1, 0, 0, 0, 0, 0, 0, 0, 0, 0,0);

}

task1(void)

{

   ...

   semTake(syncSem, WAIT_FOREVER);

      printf("task 1 got eht semaphoere/n");

}

eventInteruptSvcRout(void)

{

   ...

   semGive(syncSem);

   ...

}

semTake(semID, time out)-----有Semaphore空闲,就Take,如果没有,由time out定,超时则向下执行。

2. 互斥信号量

互斥信号量是一个特殊的二进制信号量,设计用于优先级继承,安全删除和回归。

互斥信号量的使用基本和二进制信号量类似的。但是以下不同:

.仅仅被用做互斥

.只能被使用它的任务释放。(It  can be  given only by the task that took it.)

.ISR不能释放它

.不能使用函数semFlush().

优先级反转(Priority Inversion)

优先级反转是指一个任务等待比它优先级低的任务释放资源而被阻塞,如果这时有中等优先级的就绪 任务,阻塞会进一步恶化。优先级继承技术可用于解决优先级反转问题。

Priority inversion arises when a higher-priority task is forced to wait an indefinite period of time for

a lower-priority task to complete.

优先级继承(priority inheritance)

优先级继承可用来解决优先级反转问题。当优先级反转发生时,优先级较低的任务被暂时地提高它的优先级,使得该任务能尽快执行,释放出优先级较高的任务所需要的资源。

 The mutual-exclusion semaphore has the option SEM_INVERSION_SAFE,which enables a priority-inheritance algoithm. The priority-inheritance protocol assures that a task that owns a resource executes at the priority of the highest-priority task blocked on that resource. Once the task priority

计数信号量(Counting Semaphores)

计数信号量是任务同步和互斥的另一种实现形似。计数信号量除了保留信号量被释放的次数以外和二进制信号量是一样的。每次信号量被释放(gaven)一次,计数增加;每次信号量被占用(taken)一次,计数减少;当计数减少到0时,要求得到信号量的任务被阻塞(blocked)。二进制信号量是如果一个信号量被释放,有一个任务阻塞等待,则这个任务被unblock。而计数信号量如果一个信号量被释放,没有任务阻塞等待,则计数增加。这说明一个被释放两次的计数信号量可以被占用(taken)两次,没有阻塞。

消息队列(Message queues)

现实的实时应用由一系列互相独立又协同工作的任务组成。信号量为任务间同步和联锁提供了高效机制。

在VxWorks中,用于单一CPU任务之间通信主要(primary)的机制是消息队列。

消息队列允许一定数量不同长度的消息进行排列。任何任务或中断服务程序(ISR)能够发送消息给消息队列。任何任务可以从消息队列接收消息。多任务可以从同一消息队列发送和接收消息。两个任务之间的全双工(Full-duplex)通信需要针对不同方向的两个消息队列。

消息队列函数介绍

msgQCreate()创建并初始化一个消息队列

msgQDelete()终止并释放一个消息队列

msgQSend()发送一个消息到消息队列

msgQReceive()从消息队列接收一个消息

消息队列由函数msgQCreate(MAX_MSGS, MAX_MSG_LEN, MSG_Q_PRIORITY)创建。它的参数MAX_MSGS指定了消息队列中可允许最多可以排列的消息数和每个消息允许的最大字节数MAX_MSG_LEN。

一个任务或中断服务程序(ISR)用函数msgQSend()发送一个消息到消息队列。如果没有任务等待消息队列的消息,这个消息被添加到消息缓存的队列里。如果某些任务已经在等待消息队列中的消息,消息立刻被传递到第一个等待消息的任务。

一个任务用函数msgQReceive()从消息队列得到一个消息。如果消息队列缓存中有消息存在,第一个消息立刻出列并回到调用处(caller)。如果没有消息存在,则任务(calling task)停止(blocks)并被添加到等待消息的任务队列中。这个等待的任务队列按照优先级或先进先出(FIFO)规则排列,这个规则有消息队列创建时所指定的。

等待时间限制(time out)

msgQSend()和msgQReceive()都有时间限制参数。当发送一个消息,如果消息队列缓冲这时没有空间,这个 参数指定允许等待的时间(ticks),直到队列缓存有空间来接收消息。当接收消息时,如果消息队列没有消息,这个参数指定允许等待的时间(ticks),直到消息队列有消息。

#include "vxWorks.h"

#include "msgQLib.h"

#define MAX_MSGS (10)

#define MAX_MSG_LEN (100)

MSG_Q_ID myMsgQId;

task2(void)

{

    char msgBuf[MAX_MSG_LEN];

    if(MsgQReceive(myMsgQId, msgBuf, MAX_MSG_LEN, WAIT_FOREVENR) == ERROR)

                  reutrn (ERROR);

      printf("Message form task 1:/n %s/n", msgBuf);

}

#define MESSAGE "Greeting from Task 1"

task1(void)

{

  if(myMsgQId = msgQCreate(MAX_MSGS, MAX_MSG_LEN, MSG_Q_PRIORIYT)) == NULL)

                  return (ERROR);

    if(msgSend(myMsgQId, MESSAGE, sizeof(MESSAGE), WAIT_FOREVER, MSG_PRI_NORMAL) == ERROR)

    return (ERROR);

}

管道(Pipes)

   管道对消息队列提供了一个可供选择的接口,VxWorks的I/O系统。管道是虚拟的I/O设备,由驱动pipeDrv管理。函数pipeDevCreate()创建一个管道设备,这个调用指定管道的名字,能被排列的最多消息数,和每个消息允许的长度。

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

被创建的管道是一个通常命名(named)的I/O设备,任务能用标准的I/O函数打开,读,写管道,并能调用ioctl例程。当任务试图从一个空的管道中读取数据,或向一个满的管道中写入数据时,任务被阻塞。和消息队列一样,ISR可以向管道写入,但是不能从管道读取。

作为I/O设备,管道提供了消息队列没有的重要特性,调用select()