基于嵌入式操作系统
VxWorks
的多任务并发程序设计(
3
)
――任务调度
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
执行就绪队列中的下一个任务。
6. 任务调度
6.1时间片轮转调度
我们来看一个具体的例子,在这个程序中,用户启动了三个优先级相同的任务,并通过对
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
”。
6.2优先级抢占调度
如果将例
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
也得不到执行。
6.3改变任务的优先级
在
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
捕获上述程序中各任务切换的运行轨迹