VxWorks简介
学习Vxworks之前希望读者已经具备计算机网络、操作系统、C/C++、网络通信等基础知识。建议阅读CSAPP,打好基础。 Vxworks为美国风河公司推出的高性能实时操作系统,现已广泛应用于各类大型项目,同时Tplink等公司也应用Vxworks作为某些路由器的操作系统。 实时性在计算机中分为强实时、软实时,vx所支持的为强实时,Linux普遍为软实时。 在一个实时操作系统之中,最关注的是每个任务在多长时间内可以完成。简单地说,实时和分时操作系统最大的不同在于 **时限(deadline)**这个概念。它不允许任何超出时限的错误。超时错误会带来损害甚至导致系统失败、或者导致系统不能实现它的预期目标。软实时系统的时限是一个柔性灵活的,它可以容忍偶然的超时错误。失败造成的后果并不严重,例如在网络中仅仅是轻微地降低了系统的吞吐量。
1. 多任务
现代实时系统是在多任务和任务间通信的基础上建立起来的。一个多任务的环境允许将实时应用构造成一组独立的任务,每个任务拥有各自的线程和一套系统资源。为了协调任务间的行为,任务间的通信设备允许这些任务通过同步和通信操作协调各自的活动。在VxWorks 操作系统中,任务间通信设备包括信号量、消息队列、管道以及网络套接字等设备。 在实时系统中,处理中断是另一个主要功能,这是因为中断是将外部事件通知系统的重要方式。为了能得到较快的中断响应,VxWorks操作系统里中断服务程序(ISR)在一个专门的上下文中执行,是处于任务的上下文之外。 Vxworks实时内核Wind提供了基本的多任务环境,并提供了相应的调度算法。每个任务均具备自己的上下文,上下文均保存在任务控制块TCB中。其中TCB包括以下内容:
注意: VxWorks系统是支持虚拟内存的。
1.1 任务状态
VxWorks任务状态表如下:
1.2 Wind任务调度
Wind内核的默认算法为基于优先级的抢占式调度算法,同时也支持RR调度算法,两种算法均依赖任务优先级。Wind内核中有256种优先级,0-255,0为最高级。函数如下:
1.2.1 基于优先级的抢占式任务调度
字面理解,优先级高的任务会掠夺CPU资源进行执行,低的任务将被掠夺。
优点:紧急任务可以立刻执行。
缺点:当多个优先级相同的任务共享一个CPU时,若某个任务永不阻塞,它将独占CPU,其他任务无法执行。任务调度会产生上下文切换,频繁掠夺会导致上下文切换造成的资源浪费。
由此引出轮转调度算法。
1.2.2 轮转调度算法(RR)
平均使用CPU,相同优先级任务获得相同CPU处理时间,当一个时间片用完,自愿放弃对CPU的使用,进行上下文切换。
注意:若在轮转调度中,某个任务被高优先级任务抢占,则保留该任务的时间片剩余时间,在高优先级任务执行完毕后,恢复低优先级任务执行并用完用于时间片。
1.2.3.抢占上锁
taskLock()、taskUnlock(),可以禁止内核调度。若某个任务开启了禁止调度,却在执行中被阻塞和挂起,内核有资格执行调度,选择高优先级任务执行,当该任务解除阻塞或挂起时,禁止抢占将再次生效。
注意: 应用程序的优先级应设置在 100-250之间,驱动程序优先级位于 51-99。
1.3 任务控制
1.3.1 任务创建函数:
id = taskSpawn(name, priority, options, stacksize, main, arg1, arg10);
taskInit();
taskActive();
//id 为4个字节 int
//name: 任务名
//stacksize:任务堆栈
//options:任务选项
//main:入口函数地址
//arg10:传给入口函数的启动参数
//示例:
tid = taskSpawn("tMyTask", 90, VX_FP_TASK, 20000, myFunc, 2387, 0, 0 0, 0, 0, 0, 0, 0, 0);
1.3.2 任务删除
exit();//终止任务调用,释放内存
taskDelete();//终止指定任务,释放内存
taskSafe();//保护调用任务免于删除
taskUnsafe();//解除任务删除保护
注意删除任务之前,应先释放该任务所占有的共享资源。也需要注意,若某个任务需要访问临界区,若该任务释放时,同时对其持有的信号量进行删除,这将导致其他任务无法对该临界区进行访问。此时应调用taskSafe()保护该任务被删除。
1.3.3 任务控制
1.3.4 任务拓展函数
1.3.4 任务错误errno
操作系统中的潜在的全局变量errno是已被定义的,可直接与操作系统相连接的应用代码所应用,上下文切换时,errno同时被保存。
1.3.5 任务异常处理
对于操作系统来说,CSAPP讲到异常分为中断、陷阱、异常,同时中断也分为硬件中断与软件中断,通常硬件中断是异步的,即不可预料的;软件中断是同步的,即是可预料的,Debug就是通过陷阱实现的。
1.3.6 共享代码和重入
- 代码共享必须是可重入的,多个任务同时调用一个函数不发生冲突;
- Vx中的I/O与驱动程序为可重入的;
VxWorks函数使用下列重入技术:
- 动态堆栈变量:多任务调用一个函数时,每个任务都有自己堆栈;
- 被信号保护的全局和静态变量:使用semMLib提供的mutex或互斥信号量;
- 任务变量:多任务调用一个程序,但每个任务调用使用不同的全局变量或静态变量。
2. 任务间通信
- 共享内存:可以说是最快的通信方式,也简单
- 信号量:同步与互斥
- Mutexe和条件变量
- 消息队列和管道:用于同一个CPU内任务间消息传递
- Sockets和远程程序调用:网络编程
- 信号:异常处理
2.1 互斥办法
中断上锁:
int lock = intLock();
//禁止中断的代码临界区
intUnlock(lock);
抢占上锁:
taskLock();
//禁止中断的代码临界区
taskUnlock();
2.2 信号量
#include <vxWorks.h>
#include <semLib.h>
SEM_ID semMutex;
semMutex = semBCreate(SEM_Q_PRIORITY, SEM_FULL);
semTake(semMutex, WAIT_FOREVER);
//临界区
semGive(semMutex);
信号量同时可用于同步。具体为任务1等待某信号量,任务2完成某个任务后,释放该信号量,此时任务1捕捉到该信号,解除阻塞,开始执行任务。
一致性
和信号量有关的一致性问题主要包括:
- 删除信号量时不会使阻塞其上的任务陷入无限等待
- 持有信号量的任务不会因为意外停止运行而使其他任务陷入无限等待
一致性问题可能会使部分任务无法运行,严重时使整个系统得不到期望的结果甚至崩溃。其中,第(1)点由系统保证,系统在删除信号量时自动解除信号量阻塞队列上所有阻塞的任务。
2.2.1 二进制信号量
可进行同步和互斥
2.2.2 互斥信号量
2.2.3 计数信号量
2.3 优先级倒置
基于优先级调度的操作系统会存在一个问题。
解决办法:优先级继承。
由此提出互斥信号量
semID = semMCreate(SEM_Q_PRIORITY | SEM_INVERSION_SAFE);
Wind标准信号量接口包括两个特定选项,不兼容POSIX。
- 超时: NO_WAIT, WAIT_FOREVER;
- 队列:
2.4 消息队列
单个CPU里任务间主要通信方式。
2.4.1 全双工通信需要两个消息队列
2.4.2 可选择任务优先级顺序与FIFO顺序
2.4.3 接受超时与紧急消息选项
推荐模型:
为什么使用消息队列
“信号量+共享缓冲区”不需要在用户缓冲区(即共享缓冲区)和系统内核缓冲区之间复制数据,因此效率很高,适合数据量非常大的场合;而使用消息队列时通信双方需要经过系统内核缓冲区交换数据,因此效率比“信号量+共享缓冲区”低。
优势在于:程序简单。
数据量不大的情况下,程序简化效果明显。
信号量的优先级问题只考虑一个阻塞任务队列的优先级排序。和信号量优先级不同,消息队列优先级问题从下面两个方面考虑。
- 消息自身的优先级:决定消息队列中多条消息提交给接收任务的顺序;
- 阻塞任务队列优先级:决定阻塞任务队列中多个发送者任务或者多个接收者任务谁先被执行。
消息优先级被分成两类,normal和urgent,urgent插在队头,normal队尾。
信号量与消息队列实验
/* msgorder.c: test message processing order */
#include <vxWorks.h>
#include <msgQLib.h>
#include <semLib.h>
#define MAXMSG 3
#define MAX_MSG_LEN 50
#define TEST_NUM 8
MSG_Q_ID msgQueue = NULL;
SEM_ID semMutex = NULL;
SEM_ID semCounter = NULL;
char logBuf[ TEST_NUM*3 ][MAX_MSG_LEN+20];
int nLog = 0;
void sendMsg( int );
void rcvMsg ( void ) ;
void msgOrder(){
/*定义:发送任务优先级消息自身优先级接收任务优先级*/
const int senderPri[TEST_NUM] = {50, 51, 52, 49, 53, 56, 55, 54};
const int msgPri[TEST_NUM] = {0, 1, 1, 1, 0, 1, 0, 1};
const int rcvPri = 60;
int i, j;
if( msgQueue || semMutex || semcounter ) {
printf ("last running not exit properly ! \n" );
exit (-1);
}
/* 创建:消息队列互斥信号量计数信号量 */
msgQueue = msgQCreate(MAX_MSG, MAX_MSG_LEN, MSG_Q_FIFO);
semMutex= semMCreate(SEM_Q_FIFO);
semCounter = semccreate(SEM__Q_FIFO,0);
if( !msgQueue || !semMutex li || !semCounter )
exit (-1);
/* 生成发送任务(发送消息)*/
for( i=0; i<TEST_NUM; i++ )
taskSpawn( 0, senderPri(i, 0,2000,sendMsg, msgPri[i], 0,0,0,0,0,0,0,0,0 );
/* 生成接收任务(接收消息) */
taskspawn(0, rcvPri, 0, 2000, rcvMsg, 0,0,0,0,0,0,0,0, 0,0 ) ;
/* 等待所有任务退出*/
i = 0 ;
while( i++ < TEST_NUM+1 )
semTake(semCounter, WAIT_FOREVER);
printf ( " \n**Logged result : ln" ); /*输出结果*/
for(i=0; i<nLog; i++)
printf("<%2d> %s\n", i, logBuf[i]);
msgQDelete(msgQueue);
msgQueue = 0;/*正常退出清理*/
semDelete(semMutex);
semMutex=0;
semDelete(semcounter);
semCounter= 0;
}
void sendMsg( int msgPri ) /*发送消息msgPri表示消息优先级*/
{
int taskPri ;
char msgBuf[MAX_MSG_LEN];
/*生成消息内容msgBuf:任务优先级+消息优先级记录*/
taskPriorityget(taskIdself(), &taskPri ) ;
sprintf(msgBuf,"task pri : %d,msg pri: %s",taskPri, msgPri == MSG_PRI_NORMAL ? "LO" : "HI") ;
semTake(semMutex, WAIT_FOREVER) ;
sprintf( logBuf[nLog], "---s:[ %s ] ready" , msgBuf ) ;
nLog++;
semGive( semMutex);
/*发送消息―记录*/
msgQSend(msgQueue,msgBuf,MAX_MSG_LEN,WAIT_FOREVER,msgPri );
semTake(semMutex,WAIT_FOREVER);
sprintf(logBuf[nLog] , "---s: [ %s ] sent" , msgBuf );
nLog++;
semGive(semMutex);
semGive( semcounter);
}
void rcvMsg(void) /*接收消息*/
{
int i=0, j=0;
char msgBuf[MAX_MSG_LEN];
for( i=0; i<TEST_NUM; i++)
{
j = msgQReceive(msgQueue, msgBuf, MAX_MSG_LEN, WAIT_FOREVER);
semTake(semMutex, WAIT_FOREVER);
if(j > 0)
{
sprintf(logBuf[nLog], "---R:[ %s ] received" , msgBuf);
}
else
{
sprintf(logBuf[nLog], "---R: 0 bytes read");
}
nLog++;
semGive(seMutex);
}
semGive(semCounter);
}
2.5 管道
#include <pipeDrv.h>
status = pipeDevCreate("/pipe/name", max_msgs, max_length);
status = pipeDevDelete(char *name, BOOL force);
#include <ioLib.h>
int open(const char *name, int flags, int mode);
管道提供了一个消息队列不具备的重要特性,可使用select(),属于I/O复用范畴,请读者自行检索。和直接的VxWorks消息队列相比,VxWorks管道具有如下一致的地方:
- 当任务试图从一个空的管道中读取数据,或向一个满的管道中写入数据时,任务被阻塞:
- 支持中断服务程序向管道中写入信息,驱动程序判断调用环境属于中断则不阻塞,即使没有可用的缓冲区,但是不能从管道中读取,如同中断服务程序不能对消息队列做接收操作一样;
- 往管道中写入数据时,一次写入的数据不能超出消息队列最大允许消息长度
- 由于管道具有上述消息队列的本质内容,本书有时称管道中的数据为消另外,VxWorks管道相比消息队列有如下不足:
- 管道的效率不如消息队列的高。需要将应用程序中标准的IIO调用映射到pipeDrv驱动程序函数,并最终由消息队列函数实现;
- 管道会稍微多占用一些内存资源和IO系统资源;
- 不支持消息的优先级控制,消息队列支持两级消息优先级和阻塞任务队列优先级
- 管道不支持超时控制;
- 较老版本可能不支持管道删除,消息队列不再使用时可以删除以释放资源。
2.6 任务间网络通信
套接字
TCP/UDP
可以去看看图解TCP/IP,通俗易懂。
远程程序调用(RPC)(笔者:总看见RPC框架什么的,没有深入了解过,JAVA用户应该比较了解)
2.7 信号
“信号可以异步改变任务的控制流程”
信号也称为 软中断。
Wind内核支持:UNIX BSD风格信号与POSIX兼容信号。