基于嵌入式操作系统VxWorks的多任务并发程序设计(2)
――任务控制
作者:宋宝华 e-mail:21cnbao@21cn.com 出处:软件报
4 任务与任务状态
VxWorks实时内核Wind提供了基本的多任务环境。对用户而言,宏观上看起来,多个任务同时在执行。而本质而言,在微观上,系统内核中的任务调度器总是在根据特定的调度策略让它们交替运行。系统调度器需要使用任务控制块(TCB)数据结构来管理任务调度功能,TCB被用来描述一个任务。TCB中存放了任务的上下文(context)信息,主要包括程序计数器PC、CPU内部寄存器、浮点寄存器、堆栈指针SP、任务信息等。每一任务都与一个TCB关联,当执行中的任务被停止时,任务的上下文信息需要被写入TCB;而当任务被重新执行时,必须要恢复这些上下文信息。
VxWorks的一个任务可能处于如下几种状态:
Ready:就绪状态(不是运行状态),其他资源已经就绪,仅等待CPU,当获得CPU后,就进入Running状态;
Pended:阻塞状态,由于等待某些资源(CPU除外)而阻塞;
Suspended:挂起状态,这种状态需要用taskResume才能恢复,主要用于调试。不会约束状态的转换,仅仅约束任务的执行;
Delayed:睡眠状态,任务以taskDelay主动要求等待一段时间再执行;
这些状态之间的转换关系如下:
任务状态转换 |
完成方式 |
Ready->pended |
通过semTake()/msgQReceive()调用 |
Ready->delayed |
通过taskDelay() |
ready->suspended |
通过taskSuspend() |
pended->ready |
通过其它任务对semaGive()/msgQSend()的调用 |
pended->suspended |
通过其它任务对taskSuspend()调用 |
delayed->ready |
延迟期满 |
delayed->suspended |
通过taskSuspend()调用 |
suspended->ready |
通过taskResume()/taskActivate()调用 |
suspended->pended |
通过其它任务的taskResume()调用 |
suspended->delayed |
通过其它任务的taskResume()调用 |
5 任务控制
5.1创建任务
VxWorks程序员创建任务需使用如下API:
taskSpawn (char *name, int priority, int options, int stackSize,
FUNCPTR entryPt, int arg1, int arg2, int arg3,
int arg4, int arg5, int arg6, int arg7,
int arg8, int arg9, int arg10);
该API的参数定义如下:
name:任务名;
priority:任务优先级;
options:任务选项,下表给出了各种option及其含义:
选项 |
16进制值 |
含义 |
VX_FP_TASK |
0x0008 |
执行浮点协处理 |
VX_NO_STACK_FILL |
0x0100 |
不对任务堆栈填充0xee |
VX_PRIVATE_ENV |
0x0080 |
执行一个环境私有的任务 |
VX_UNBREAKABLE |
0x0002 |
使任务不能断点 |
VX_DSP_TASK |
0x0200 |
1 = DSP协处理支持 |
VX_ALTIVEC_TASK |
0x0400 |
1 = ALTIVEC协处理支持 |
stacksize:任务堆栈大小;
main:任务入口函数;
arg1,…arg10:任务入口函数参数
下面来看一个具体的例子:
例1:创建任务
/* includes */
#include "vxWorks.h"
#include "taskLib.h"
#include "sysLib.h"
int tid;
/* task function */
void myFunc(void)
{
int i;
printf("Hello, I am task %d\n", taskIdSelf()); /* Print task Id */
for (i = 0; i < 10; i++)
{
printf("%d ", i);
taskDelay(sysClkRateGet ( ) / 2);
}
}
/* user entry */
void user_start()
{
printf("ready to begin a new task\n");
tid = taskSpawn("myTask", 90, VX_NO_STACK_FILL, 2000, (FUNCPTR) myFunc, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
}
程序运行,在VxSim上输出:
Hello, I am task 14870080
0 1 2 3 4 5 6 7 8 9
taskDelay(sysClkRateGet ( ) / 2)语句的含义为将任务延迟0.5S,因此,0、1~9的数字输出之间间隔0.5S。
要特别注意taskSpawn函数的options参数,在如下几种情况下我们都要将其它options与 VX_FP_TASK做“按位或”操作使得任务支持浮点运算(如果仅包含此选项,则不需进行或操作):
(1)执行浮点操作;
(2)调用返回任何浮点数的函数;
(3)调用参数为浮点数的函数。
例如下列程序启动任务的方式就不正确:
例2:创建浮点支持任务
/* task including float calculate */
void floatTask(void)
{
printf("%f", 100 / 30.0);
}
/* user entry */
void user_start()
{
taskSpawn("floatTask", 90, VX_NO_STACK_FILL, 2000, (FUNCPTR) floatTask, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
}
应该将对taskSpawn函数调用的代码改为:
taskSpawn("floatTask", 90, VX_NO_STACK_FILL | VX_FP_TASK, 2000, floatTask, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
5.2 终止任务
exit( ) :终止当前任务。这个函数是不安全的,任务终止后,其所占据的内存空间并未释放,请看下面的程序:
例3:任务退出
/* includes */
#include "vxWorks.h"
#include "taskLib.h"
#include "sysLib.h"
int tid;
/* task function */
void myFunc(void)
{
int i;
printf("Hello, I am task %d\n", taskIdSelf()); /* Print task Id */
for (i = 0; i < 5; i++)
{
printf("%d ", i);
taskDelay(sysClkRateGet() / 2);
}
exit(0);
for (i = 5; i < 10; i++)
{
printf("%d ", i);
taskDelay(sysClkRateGet() / 2);
}
}
/* user entry */
void user_start()
{
printf("ready to begin a new task\n");
tid = taskSpawn("myTask", 90, 0x100, 2000, (FUNCPTR) myFunc, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0);
}
这次程序仅仅输出:
Hello, I am task 14868640
0 1 2 3 4
这意味着exit(0)语句之后的循环for (i = 5; i < 10; i++)没有被执行。
taskDelete()函数:终止任务并释放任务占用的内存(堆栈和任务控制块空间),其原型为:
extern STATUS taskDelete (int tid);
参数tid为任务的ID。
请看下面的例子:
例4:删除任务
/* includes */
#include "vxWorks.h"
#include "taskLib.h"
#include "sysLib.h"
int tid;
/* task function */
void myFunc(void)
{
int i;
printf("Hello, I am task %d\n", taskIdSelf()); /* Print task Id */
for (i = 0; i < 10; i++)
{
printf("%d ", i);
taskDelay(sysClkRateGet() / 2);
}
}
/* another task function:delete my task */
void delMyTaskFunc(void)
{
taskDelay(sysClkRateGet() *4);
printf("ready to delete task\n");
taskDelete(tid);
}
/* user entry */
void user_start()
{
printf("ready to begin new tasks\n");
tid = taskSpawn("myTask", 90, 0x100, 2000, (FUNCPTR) myFunc, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
taskSpawn("delMyTask", 90, 0x100, 2000, (FUNCPTR)delMyTaskFunc, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
}
运行输出:
Hello, I am task 14868640
0 1 2 3 4 5 6 7 ready to begin a new task
程序为运行输出8、9,这是因为在此之前,myTask已经被另一个任务――delMyTask删除。
任务可能被taskDelete()调用删除掉,但这一行为也不一定是安全的。如果我们删除一个获得了某些资源(如二进制信号量等)的任务,则对应的资源将不被释放,到站其它正在等待该资源的任务永远不能获得资源,系统会挡掉。我们可以用 taskSafe()和 taskUnsafe ()来保护这种区域,例如:
taskSafe ();
semTake (semId, WAIT_FOREVER);
/* Block until semaphore available */
. . . . critical region .
semGive (semId); semGive (semId);
/* Release semaphore */
taskUnsafe ();
5.3 延迟任务
taskdelay()提供了一个简单的任务睡眠机制,常用于需要定时/延时机制的应用中。它的原型是:
STATUS taskDelay(int ticks /* number of ticks to delay task */);
可以看出使用该函数实现延时的单位为节拍(tick)。在VxWorks下通常以如下方式调用taskDelay()函数:
taskDelay(sysClkRateGet()*n);
其中的n是要延迟的时间,以秒为单位。其中的sysClkRateGet(int ticks /* number of ticks every second */)函数返回系统的时钟速率,单位是tick数/每秒。操作系统每秒的tick数可以利用sysClkRateSet()函数设置。
5.4 挂起/恢复/重启任务
我们可以使用taskSuspend()函数挂起一个任务的运行,这个任务只有获得对应的taskResume()后才能再次运行,这两个API的原型为:
extern STATUS taskSuspend (int tid);
extern STATUS taskResume (int tid);
例5:挂起/恢复任务
/* includes */
#include "vxWorks.h"
#include "taskLib.h"
#include "sysLib.h"
int tid;
/* task function */
void myFunc(void)
{
int i;
printf("Hello, I am task %d\n", taskIdSelf()); /* Print task Id */
for (i = 0; i < 10; i++)
{
printf("%d ", i);
taskDelay(sysClkRateGet() / 2);
}
}
/* suspend and resume task */
void suspendResumeMyTask(void)
{
taskDelay(sysClkRateGet() *3);
taskSuspend(tid);
printf("my task is suspended\n");
taskDelay(sysClkRateGet() *3);
taskResume(tid);
}
/* user entry */
void user_start()
{
printf("ready to begin new tasks\n");
tid = taskSpawn("myTask", 90, 0x100, 2000, (FUNCPTR) myFunc, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
taskSpawn("suspendResumeMyTask", 90, 0x100, 2000, (FUNCPTR) suspendResumeMyTask, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0);
}
运行输出:
Hello, I am task 17753664
0 1 2 3 4 5 my task is suspended
6 7 8 9
这个程序运行3秒后,suspendResumeMyTask任务挂起了myTask,输出“my task is suspended”。suspendResumeMyTask本身延迟3秒后恢复myTask,使得myTask再次输出“6 7 8 9”。显然,“6 7 8 9”与“0 1 2 3 4
如果我们将上述程序改为:
例6:重启任务
/* includes */
#include "vxWorks.h"
#include "taskLib.h"
#include "sysLib.h"
int tid;
/* task function */
void myFunc(void)
{
int i;
printf("Hello, I am task %d\n", taskIdSelf()); /* Print task Id */
for (i = 0; i < 10; i++)
{
printf("%d ", i);
taskDelay(sysClkRateGet() / 2);
}
}
/* reset task */
void resetMyTask(void)
{
taskDelay(sysClkRateGet() *3);
printf("my task will be reseted\n");
taskRestart(tid);
}
/* user entry */
void user_start()
{
printf("ready to begin new tasks\n");
tid = taskSpawn("myTask", 90, 0x100, 2000, (FUNCPTR) myFunc, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0);
taskSpawn("resetMyTask", 90, 0x100, 2000, (FUNCPTR)resetMyTask, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
}
运行输出:
Hello, I am task 17753664
0 1 2 3 4 5 my task will be reseted
Hello, I am task 17753664
0 1 2 3 4 5 6 7 8 9
我们可以使用taskRestart()函数重新启动一个任务,不管任务当前处于什么状态,它都会被重新开始。该API的原型是:
extern STATUS taskRestart (int tid);
在例6中,程序运行3秒后resetMyTask启动,它复位了myTask,因此myTask被重新执行,“Hello, I am task 17753664”以及“0 1 2 3 4
5.5任务钩子
有过Windows钩子(Hook)编程经验的读者应该对其概念并不陌生,Hook作为回调函数,当被挂接后。操作系统发生特定的事情时,将触发这个Hook回调函数的执行。VxWorks也有钩子的概念,不过比Windows要简单许多,主要有taskCreateHook、taskDeleteHook、taskSwitchHookAdd,可以通过如下6个API来添加和删除这三种Hook:
STATUS taskCreateHookAdd (FUNCPTR createHook /* routine to be called when a task is created */ );
STATUS taskCreateHookDelete (FUNCPTR createHook /* routine to be deleted from list */);
STATUS taskSwitchHookAdd (FUNCPTR switchHook /* routine to be called at every task switch */);
STATUS taskSwitchHookDelete (FUNCPTR switchHook /* routine to be deleted from list */);
STATUS taskDeleteHookAdd (FUNCPTR deleteHook /* routine to be called when a task is deleted */);
STATUS taskDeleteHookDelete (FUNCPTR deleteHook /* routine to be deleted from list */);
请看例程:
例7:任务钩子Hook
/* includes */
#include "vxWorks.h"
#include "taskLib.h"
#include "taskHookLib.h" //taskHook所对应的库
/* task function */
void myFunc(void)
{
int i;
printf("Hello, I am task %d\n", taskIdSelf()); /* Print task Id */
}
/* taskCreatHook */
void myTaskHook(void)
{
printf("task hook function called\n");
}
/* user entry */
void user_start()
{
taskCreateHookAdd( (FUNCPTR) myTaskHook);
taskSpawn("myTask", 90, 0x100, 2000, (FUNCPTR) myFunc, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
}
运行输出:
task hook function called
Hello, I am task 14868640
5.6 其它重要API
与任务控制相关的其它重要API还有:
//设置任务优先级
extern STATUS taskOptionsSet (int tid, int mask, int newOptions);
//获得任务优先级
extern STATUS taskOptionsGet (int tid, int *pOptions);
//从任务ID获得任务名
extern char * taskName (int tid);
//从任务名获得任务ID
extern int taskNameToId (char *name);
//确认ID为tid的任务是否存在
extern STATUS taskIdVerify (int tid);
//获得任务自身ID
extern int taskIdSelf (void);
//任务状态是否为ready
extern BOOL taskIsReady (int tid);
//任务状态是否为Suspended
extern BOOL taskIsSuspended (int tid);
//获得任务的TCB指针
extern WIND_TCB *taskTcb (int tid);
//获得任务的优先级
STATUS taskPrioritySet (int tid, /* task ID */ int newPriority /* new priority */ );
//任务锁定与解锁:一个任务调用taskLock()后,任务运行时将没有基于优先级的抢占发生;而taskUnlock()则用于恢复锁定。
extern STATUS taskLock (void);
extern STATUS taskUnlock (void);
Trackback: http://tb.donews.net/TrackBack.aspx?PostId=754236