全部博文(404)
分类: LINUX
2009-03-04 12:42:31
基于嵌入式操作系统VxWorks的多任务并发程序设计(3)
――任务调度
作者:宋宝华 e-mail:21cnbao@21cn.com 出处:软件报
VxWorks支持两种方式的任务调度:
(1)基于优先级的抢占调度(Preemptive Priority Based Scheduling)
抢占是指正在执行的任务可以被打断,让另一个任务运行,它可以提高应用程序对异步事件的响应能力。基于优先级的抢占调度是最常见的抢占机制,用户任务被分配一个优先级,操作系统内核总是调度优先级最高的就绪任务运行于CPU。当系统正在执行低优先级任务时,一旦有更高优先级的任务准备就绪,OS内核会立即进行任务的上下文切换。
VxWorks的Wind内核划分优先级为256 级(0~255)。优先级0为最高优先级,优先级255为最低。当任务被创建时,系统根据用户指定的值分配任务优先级。VxWorks的任务优先级也可以是动态的,它们能在系统运行时被用户使用系统调用taskPrioritySet()来加以改变。
(2)时间片轮转调度(Round-Robin Scheduling)
时间片轮转调度指的是操作系统分配一定的时间间隔(时间片),使每个任务轮流运行于CPU。在VxWorks中,对于优先级相同的多个任务,如果状态为ready,则其可以通过时间片轮转方式公平享有CPU资源。
轮转调度法给处于就绪态的每个同优先级的任务分配一个相同的执行时间片,时间片的大小可由系统调用KernelTimeSlice指定。为对轮转调度进行支持,系统给每个任务提供一个运行时间计数器,任务运行时每一时间滴答计数器加1。一个任务用完时间片之后,OS停止执行该任务,将它放入就绪队列尾部,并将其运行时间计数器置零。接着,OS执行就绪队列中的下一个任务。
我们来看一个具体的例子,在这个程序中,用户启动了三个优先级相同的任务,并通过对kernelTimeSlice(TIMESLICE)的调用启动了时间片轮转调度。
例1:时间片轮转调度
/* includes */
#include "vxWorks.h"
#include "taskLib.h"
#include "kernelLib.h"
#include "sysLib.h"
/* function prototypes */
void taskOne(void);
void taskTwo(void);
void taskThree(void);
/* globals */
#define ITER1 100
#define ITER2 10
#define PRIORITY 101
#define TIMESLICE sysClkRateGet()
#define LONG_TIME 0xFFFFFFL
void sched(void) /* function to create the three tasks */
{
int taskIdOne, taskIdTwo, taskIdThree;
if (kernelTimeSlice(TIMESLICE) == OK)
/* turn round-robin on */
printf("\n\n\n\n\t\t\tTIMESLICE = %d seconds\n\n\n", TIMESLICE / 60);
/* spawn the three tasks */
if ((taskIdOne = taskSpawn("task1", PRIORITY, 0x100, 20000, (FUNCPTR)taskOne,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) == ERROR)
printf("taskSpawn taskOne failed\n");
if ((taskIdTwo = taskSpawn("task2", PRIORITY, 0x100, 20000, (FUNCPTR)taskTwo,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) == ERROR)
printf("taskSpawn taskTwo failed\n");
if ((taskIdThree = taskSpawn("task3", PRIORITY, 0x100, 20000, (FUNCPTR)
taskThree, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) == ERROR)
printf("taskSpawn taskThree failed\n");
}
void taskOne(void)
{
unsigned int i, j;
for (i = 0; i < ITER1; i++)
{
for (j = 0; j < ITER2; j++)
printf("task1\n");
/* log messages */
for (j = 0; j < LONG_TIME; j++)
;
/* allow time for context switch */
}
}
void taskTwo(void)
{
unsigned int i, j;
for (i = 0; i < ITER1; i++)
{
for (j = 0; j < ITER2; j++)
printf("task2\n");
/* log messages */
for (j = 0; j < LONG_TIME; j++)
;
/* allow time for context switch */
}
}
void taskThree(void)
{
unsigned int i, j;
for (i = 0; i < ITER1; i++)
{
for (j = 0; j < ITER2; j++)
printf("task3\n");
/* log messages */
for (j = 0; j < LONG_TIME; j++)
;
/* allow time for context switch */
}
}
程序运行输出:一会儿输出一些“task1”,一会儿输出一些“task2”,再一会儿输出一些“task3”。每次输出了某任务的一部分内容后,就开始输出另一任务的内容,这说明了task1、task2、task3再进行时间片轮转切换。
对于任务的上下文切换,我们可以使用WindView进行观察。WindView是一个图形化的动态诊断和分析工具,它可以向开发者提供目标机硬件上所运行应用程序的许多详细情况。下图显示了使用WindView获取的上述程序的运行结果:
kernelTimeSlice()的函数原型为:
STATUS kernelTimeSlice (int ticks /* time-slice in ticks or 0 to disable round-robin */ );
程序中的kernelTimeSlice(TIMESLICE)展开后为kernelTimeSlice(sysClkRateGet()),即每秒钟进行一次轮转。如果kernelTimeSlice函数中的输入参数为0,时间片轮转调度就不会发生,程序的输出将是:先输出ITER1* ITER2个“task1”,再输出ITER1* ITER2个“task2”,最后输出ITER1* ITER2个“task3”。
如果将例1中三个任务的优先级设置的不相同,即将程序改为:
例2:优先级抢占调度
/* includes */
#include "vxWorks.h"
#include "taskLib.h"
#include "kernelLib.h"
#include "sysLib.h"
/* function prototypes */
void taskOne(void);
void taskTwo(void);
void taskThree(void);
/* globals */
#define ITER1 100
#define ITER2 10
#define HIGH 100 /* high priority */
#define MID 101 /* medium priority */
#define LOW 102 /* low priority */
#define LONG_TIME 0xFFFFFFL
void sched(void) /* function to create the two tasks */
{
int taskIdOne, taskIdTwo, taskIdThree;
printf("\n\n\n\n\n");
/* spawn the three tasks */
if ((taskIdOne = taskSpawn("task1", LOW, 0x100, 20000, (FUNCPTR)taskOne, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0)) == ERROR)
printf("taskSpawn taskOne failed\n");
if ((taskIdTwo = taskSpawn("task2", MID, 0x100, 20000, (FUNCPTR)taskTwo, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0)) == ERROR)
printf("taskSpawn taskTwo failed\n");
if ((taskIdThree = taskSpawn("task3", HIGH, 0x100, 20000, (FUNCPTR)taskThree,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) == ERROR)
printf("taskSpawn taskThree failed\n");
}
void taskOne(void)
{
…//same with example 1
}
void taskTwo(void)
{
…//same with example 1
}
void taskThree(void)
{
…//same with example 1
}
再观察程序的运行结果为:先输出ITER1* ITER2个“task3”,再输出ITER1* ITER2个“task2”,最后输出ITER1* ITER2个“task1”。这是因为task1、task2、task3的优先级顺序是“task3-task2-task1”,故在task3执行完(或被阻塞、挂起)前,task2得不到执行;同理,在在task2执行完(或被阻塞、挂起)前,task1也得不到执行。
在VxWorks中,我们可以使用taskPrioritySet()函数来动态改变任务的优先级,这个函数的原型是:
STATUS taskPrioritySet (int tid, /* task ID */ int newPriority /* new priority */ );
我们将例2的程序修改,在其中增加task4,task4延迟100毫秒后,将task1的优先级设置到最高:
例3:改变任务的优先级
/* includes */
#include "vxWorks.h"
#include "taskLib.h"
/* function prototypes */
void taskOne(void);
void taskTwo(void);
void taskThree(void);
void taskFour(void);
/* globals */
#define ITER1 100
#define ITER2 1
#define LONG_TIME 1000000
#define HIGH 100 /* high priority */
#define MID 101 /* medium priority */
#define LOW 102 /* low priority */
#define TIMESLICE sysClkRateGet()
int taskIdOne, taskIdTwo, taskIdThree;
void sched(void) /* function to create the two tasks */
{
printf("\n\n\n\n\n");
/* spawn the four tasks */
if ((taskIdOne = taskSpawn("task1", LOW, 0x100, 20000, (FUNCPTR)taskOne, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0)) == ERROR)
printf("taskSpawn taskOne failed\n");
if ((taskIdTwo = taskSpawn("task2", MID, 0x100, 20000, (FUNCPTR)taskTwo, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0)) == ERROR)
printf("taskSpawn taskTwo failed\n");
if ((taskIdThree = taskSpawn("task3", HIGH, 0x100, 20000, (FUNCPTR)taskThree,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) == ERROR)
printf("taskSpawn taskThree failed\n");
if (taskSpawn("task4", HIGH, 0x100, 20000, (FUNCPTR)taskFour, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0) == ERROR)
printf("taskSpawn taskFour failed\n");
}
void taskOne(void)
{
…//same with example 1
}
void taskTwo(void)
{
…//same with example 1
}
void taskThree(void)
{
…//same with example 1
}
void taskFour(void)
{
taskDelay(TIMESLICE / 10);
taskPrioritySet(taskIdOne, HIGH - 1);
}
在task2任务运行输出“task2”的过程中,task1抢占了task2的CPU资源,先是输出了所有的“task1”,最后剩余的“task2”得以输出。
我们用WINDVIEW捕获上述程序中各任务切换的运行轨迹,如下图:
Trackback: