1.09例3<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
例3中使用了许多uCOS-II提供的附加功能。任务3使用了OSTaskCreateExt()中TCB的扩展数据结构,用户定义的任务切换对外接口函数(OSTaskSwHook()),用户定义的统计任务(statistic task )的对外接口函数(OSTaskStatHook())以及消息队列。例3的磁盘文件是\SOFTWARE\uCOS-II\EX3_x86L,它包括9个任务。除了空闲任务(idle task)和统计任务(statistic task ),还有7个任务。与例1,例2一样,TaskStart()由main()函数建立,其功能是建立其他任务,并显示统计信息。
1.09.01 main()
main()函数[程序清单L1.19]和例2中的相不多,不同的是在用户定义的TCB扩展数据结构中可以保存每个任务的名称[程序清单L1.19(1)](扩展结构的声明在INCLUDES.H中定义,也可参看程序清单L1.20)。笔者定义了30个字节来存放任务名(包括空格)[程序清单L1.20(1)]。本例中没有用到堆栈检查操作,TaskStart()中禁止该操作[程序清单L1.19(2)]。
程序清单 L 1.19 例3的main()函数 |
void main (void) |
{ |
PC_DispClrScr(DISP_FGND_WHITE + DISP_BGND_BLACK); |
OSInit(); |
PC_DOSSaveReturn(); |
PC_VectSet(uCOS, OSCtxSw); |
PC_ElapsedInit(); |
|
Strcpy(TaskUserData[TASK_START_ID].TaskName, "StartTask"); (1) |
OSTaskCreateExt(TaskStart, |
(void *)0, |
&TaskStartStk[TASK_STK_SIZE-1], |
TASK_START_PRIO, |
TASK_START_ID, |
&TaskStartStk[0], |
TASK_STK_SIZE, |
&TaskUserData[TASK_START_ID], |
0); (2) |
OSStart(); |
} |
程序清单 L 1.20 TCB扩展数据结构。 |
typedef struct { |
char TaskName[30]; (1) |
INT16U TaskCtr; |
INT16U TaskExecTime; |
INT32U TaskTotExecTime; |
} TASK_USER_DATA; |
1.09.02任务
TaskStart()的伪码如程序清单L1.21所示,与例2有3处不同:
l 为任务1,2,3建立了一个消息队列[程序清单L1.21(1)];
l 每个任务都有一个名字,保存在任务的TCB扩展数据结构中[程序清单L1.21(2)];
l 禁止堆栈检查。
程序清单 L 1.21 TaskStart()的伪码。 |
void TaskStart (void *data) |
{ |
Prevent compiler warning by assigning ‘data’ to itself; |
Display a banner and non-changing text; |
Install uC/OS-II’s tick handler; |
Change the tick rate to 200 Hz; |
Initialize the statistics task; |
Create a message queue; (1) |
Create a task that will display the date and time on the screen; |
Create 5 application tasks with a name stored in the TCB ext.; (2) |
for (;;) { |
Display #tasks running; |
Display CPU usage in %; |
Display #context switches per seconds; |
Clear the context switch counter; |
Display uC/OS-II’s version; |
If (Key was pressed) { |
if (Key pressed was the ESCAPE key) { |
Return to DOS; |
} |
} |
Delay for 1 second; |
} |
} |
任务1向消息队列发送一个消息[程序清单L1.22(1)],然后延时等待消息发送完成[程序清单L1.22(2)]。这段时间可以让接收消息的任务显示收到的消息。发送的消息有三种。
程序清单 L 1.22 任务1。 |
void Task1 (void *data) |
{ |
char one = '1'; |
char two = '2'; |
char three = '3'; |
|
|
data = data; |
for (;;) { |
OSQPost(MsgQueue, (void *)&one); (1) |
OSTimeDlyHMSM(0, 0, 1, 0); (2) |
OSQPost(MsgQueue, (void *)&two); |
OSTimeDlyHMSM(0, 0, 0, 500); |
OSQPost(MsgQueue, (void *)&three); |
OSTimeDlyHMSM(0, 0, 1, 0); |
} |
} |
任务2处于等待消息的挂起状态,且不设定最大等待时间[程序清单L1.23(1)]。所以任务2将一直等待直到收到消息。当收到消息后,任务2显示消息并且延时500mS[程序清单L1.23(2)],延时的时间可以使任务3检查消息队列。
程序清单 L 1.23 任务2。 |
void Task2 (void *data) |
{ |
INT8U *msg; |
INT8U err; |
|
|
data = data; |
for (;;) { |
msg = (INT8U *)OSQPend(MsgQueue, 0, &err); (1) |
PC_DispChar(70, 14, *msg, DISP_FGND_YELLOW+DISP_BGND_BLUE); (2) |
OSTimeDlyHMSM(0, 0, 0, 500); (3) |
} |
} |
任务3同样处于等待消息的挂起状态,但是它设定了等待结束时间250mS[程序清单L1.24(1)]。如果有消息来到,任务3将显示消息号[程序清单L1.24(3)],如果超过了等待时间,任务3就显示“T”(意为timeout)[程序清单L1.24(2)]。
程序清单 L 1.24任务3 |
void Task3 (void *data) |
{ |
INT8U *msg; |
INT8U err; |
|
|
data = data; |
for (;;) { |
msg = (INT8U *)OSQPend(MsgQueue, OS_TICKS_PER_SEC/4, &err); (1) |
If (err == OS_TIMEOUT) { |
PC_DispChar(70,15,'T',DISP_FGND_YELLOW+DISP_BGND_RED); (2) |
} else { |
PC_DispChar(70,15,*msg,DISP_FGND_YELLOW+DISP_BGND_BLUE); (3) |
} |
} |
} |
任务4的操作只是从邮箱发送[程序清单L1.25(1)]和接收[程序清单L1.25(2)],这使得用户可以测量任务在自己PC上执行的时间。任务4每10mS执行一次[程序清单L1.25(3)]。
程序清单 L 1.25 任务4。 |
void Task4 (void *data) |
{ |
OS_EVENT *mbox; |
INT8U err; |
|
|
data = data; |
mbox = OSMboxCreate((void *)0); |
for (;;) { |
OSMboxPost(mbox, (void *)1); (1) |
OSMboxPend(mbox, 0, &err); (2) |
OSTimeDlyHMSM(0, 0, 0, 10); (3) |
} |
} |
任务5除了延时一个时钟节拍以外什么也不做[程序清单L1.26(1)]。注意所有的任务都应该调用uCOS-II的函数,等待延时结束或者事件的发生而让出CPU。如果始终占用CPU,这将使低优先级的任务无法得到CPU。
程序清单 L 1.26 任务5。 |
void Task5 (void *data) |
{ |
data = data; |
for (;;) { |
OSTimeDly(1); (1) |
} |
} |
同样, TaskClk()函数[程序清单L1.18]显示当前日期和时间。
1.09.03注意
有些程序的细节只有请您仔细读一读EX3L.C才能理解。EX3L.C中有OSTaskSwHook()函数的代码,该函数用来测量每个任务的执行时间,可以用来统计每一个任务的调度频率,也可以统计每个任务运行时间的总和。这些信息将存储在每个任务的TCB扩展数据结构中。每次任务切换的时候OSTaskSwHook()都将被调用。
每次任务切换发生的时候,OSTaskSwHook()先调用PC_ElapsedStop()函数[程序清单L1.27(1)] 来获取任务的运行时间[程序清单L1.27(1)],PC_ElapsedStop()要和PC_ElapsedStart()一起使用,上述两个函数用到了PC的定时器2(timer 2)。其中PC_ElapsedStart()功能为启动定时器开始记数;而PC_ElapsedStop()功能为获取定时器的值,然后清零,为下一次计数做准备。从定时器取得的计数将拷贝到time变量[程序清单L1.27(1)]。然后OSTaskSwHook()调用PC_ElapsedStart()重新启动定时器做下一次计数[程序清单L1.27(2)]。需要注意的是,系统启动后,第一次调用PC_ElapsedStart()是在初始化代码中,所以第一次任务切换调用PC_ElapsedStop()所得到的计数值没有实际意义,但这没有什么影响。如果任务分配了TCB扩展数据结构[程序清单L1.27(4)],其中的计数器TaskCtr进行累加[程序清单L1.27(5)]。TaskCtr可以统计任务被切换的频繁程度,也可以检查某个任务是否在运行。TaskExecTime [程序清单L1.27(6)]用来记录函数从切入到切出的运行时间,TaskTotExecTime[程序清单L1.27(7)]记录任务总的运行时间。统计每个任务的上述两个变量,可以计算出一段时间内各个任务占用CPU的百分比。OSTaskStatHook()函数会显示这些统计信息。
程序清单 L 1.27 用户定义的OSTaskSwHook() |
void OSTaskSwHook (void) |
{ |
INT16U time; |
TASK_USER_DATA *puser; |
|
|
time = PC_ElapsedStop(); (1) |
PC_ElapsedStart(); (2) |
puser = OSTCBCur->OSTCBExtPtr; (3) |
if (puser != (void *)0) { (4) |
puser->TaskCtr++; (5) |
puser->TaskExecTime = time; (6) |
puser->TaskTotExecTime += time; (7) |
} |
} |
本例中的统计任务(statistic task)将调用对外接口函数OSTaskStatHook()(设置OS_CFG.H文件中的OS_TASK_STAT_EN为1允许对外接口函数)。统计任务每秒运行一次,本例中OSTaskStatHook()用来计算并显示各任务占用CPU的情况。
OSTaskStatHook()函数中首先计算所有任务的运行时间[程序清单L1.28(1)],DispTaskStat()用来将数字显示为ASCII字符[程序清单L1.28(2)]。然后是计算每个任务运行时间的百分比[程序清单L1.28(3)],显示在合适的位置上 [程序清单L1.28(4)]。
程序清单 L 1.28 用户定义的OSTaskStatHook(). |
void OSTaskStatHook (void) |
{ |
char s[80]; |
INT8U i; |
INT32U total; |
INT8U pct; |
|
|
total = 0L; |
for (I = 0; i < 7; i++) { |
total += TaskUserData[i].TaskTotExecTime; (1) |
DispTaskStat(i); (2) |
} |
if (total > 0) { |
for (i = 0; i < 7; i++) { |
pct = 100 * TaskUserData[i].TaskTotExecTime / total; (3) |
sprintf(s, "%3d %%", pct); |
PC_DispStr(62, i + 11, s, DISP_FGND_YELLOW); (4) |
} |
} |
if (total > 1000000000L) { |
for (i = 0; i < 7; i++) { |
TaskUserData[i].TaskTotExecTime = 0L; |
} |
} |
} |