Download presentation
Presentation is loading. Please wait.
1
嵌入式操作系统ucOS-II分析
2
ucOS-II应用程序基本结构 void task ( void* pdata ) { INT8U err;
InitTimer(); // 可选 For( ;; ) // 你的应用程序代码 …….. OSTimeDly(1); // 可选 }
3
一些重要的ucOS-II API 1)任务类 2)消息类 3)同步类 4)时间类 5)临界区与事件类
4
1)OSTaskCreate函数 这个函数应该至少在main函数内调用一次,在OSInit函数调用之后调用,以创建一个任务。
系统首先从TCB空闲列表内申请一个空的TCB指针,然后根据用户给出的参数初始化任务堆栈,并在内部的任务就绪表内标记该任务为就绪状态。最后返回,这样一个任务就创建成功了。
5
2)OSTaskSuspend函数 将指定的任务挂起。如果挂起的是当前任务,会引发系统执行任务切换先导函数OSShed来进行一次任务切换。
这个函数只有一个优先级参数,在系统内部,优先级除了表示一个任务执行的先后次序外,还起着区分每一个任务的作用,即优先级也是任务的ID。所以uCOS-II不允许出现相同优先级的任务。
6
3)OSTaskResume函数 将指定的已经挂起的任务恢复成就绪状态。如果恢复任务的优先级高于当前任务,那么还会引发一次任务切换。
参数类似OSTaskSuspend函数,为指定任务的优先级。需要特别说明是,本函数并不要求和OSTaskSuspend函数成对使用。
7
4)OS_ENTER_CRITICAL宏 分析一下OS_CPU.H文件,它涉及特定CPU的实现。一般都被替换为一条或者几条嵌入式汇编代码。其实,它就是关中断。 只要任务不主动放弃CPU使用权,别的任务就没有占用CPU的机会,这个任务就是独占了。进入临界区后,这个宏应尽量少用,它会破坏系统的一些服务,尤其是时间服务。并使系统对外界响应性能降低。
8
5)OS_EXIT_CRITICAL宏 与OS_ENTER_CRITICAL配套使用的一个宏,在系统手册说明里它是退出临界区,其实就是重新开中断。 它必须和OS_ENTER_CRITICAL成对出现,否则会带来意想不到的后果,如系统会崩溃。我们应尽量少用这两个宏调用,因为他们的确会破坏系统的多任务性能。
9
6)OSTimeDly函数 这是调用最多的一个函数,它的功能是先挂起当前任务,然后进行任务切换,在指定的时间到来之后,将当前任务恢复为就绪状态,但是并不一定运行,如果恢复后是优先级最高的就绪任务话,那么就运行之。 可以将任务延时一定时间后再执行它,依靠时钟机制进行任务切换,暂时放弃CPU的使用权,但多任务性能会降低。
10
ucOS-II范例的源码分析
11
INCLUDES.H ucOS-II中所有的 *.C 文件都包括了以下定义:#include "includes.h“
唯一的缺点是INCLUDES.H中许多头文件在一些*.C文件的编译中是不需要的。虽然逐个编译这些文件要花费额外的时间,但代码的可移植性却增加了。
12
不依赖于编译的数据类型 可移植型数据类型的程序 Typedef unsigned char BOOLEAN;
Typedef unsigned char INT8U; Typedef signed char INT8S; Typedef unsigned int INT16U; Typedef signed int INT16S; Typedef unsigned long INT32U; Typedef signed long INT32S; Typedef float FP32; Typedef double FP64; #define BYTE INT8S | #define UBYTE INT8U | #define WORD INT16S | ucos->ucosII #define UWORD INT16U | #define LONG INT32S | #define ULONG INT32U |
13
全局变量 定义全局宏的程序 #ifdef xxx_GLOBALS #define xxx_EXT #else
#define xxx_EXT extern #endif .H 文件中每个全局变量都加上了xxx_EXT的前缀。xxx代表模块的名字。该模块的.C文件中有以下定义: #define xxx_GLOBALS #include "includes.h"
14
uCOS_II.H中有以下定义: #ifdef OS_GLOBALS #define OS_EXT #else
#define OS_EXT extern #endif OS_EXT INT32U OSIdleCtr; OS_EXT INT32U OSIdleCtrRun; OS_EXT INT32U OSIdleCtrMax;
15
uCOS_II.C中有以下定义: #define OS_GLOBALS #include “includes.h”
当编译器处理uCOS_II.C时,它使uCOS_II.H变成如下所示,因为OS_EXT被设置为空。编译器就会将这些全局变量分配到内存中。 INT32U OSIdleCtr; INT32U OSIdleCtrRun; INT32U OSIdleCtrMax;
16
当编译器处理其他.C文件时,头文件变成了如下的样子,因为OS_GLOBAL没有定义,所以OS_EXT被定义为extern。
extern INT32U OSIdleCtr; extern INT32U OSIdleCtrRun; extern INT32U OSIdleCtrMax; 在这种情况下,不产生内存分配,而任何 .C文件都可以使用这些变量。这样就只需在 .H 文件中定义一次就可以了。
17
OS_ENTER_CRITICAL() 和 OS_EXIT_CRITICAL()
是为了保护临界段代码,这些代码与处理器有关。宏的定义在OS_CPU.H中。 关中断会影响中断延迟,所以要特别小心。用户还可以用信号量来保护临界段代码。
18
基于PC的服务 PC.C 文件和 PC.H 文件是范例中使用到的一些基于PC的服务程序。
19
字符显示 PC_DispClrScr() Clear the screen
PC_DispClrLine() Clear a single row (or line) PC_DispChar() Display a single ASCII character anywhere on the screen PC_DispStr() Display an ASCII string anywhere on the screen
20
时间度量 用PC的82C54定时器2测试一个函数的运行花了多少时间。被测的程序代码放在函数PC_ElapsedStart()和PC_ElapsedStop()之间来测量。在用这两个函数之前,应该调用PC_ElapsedInit()来初始化,它主要是计算运行这两个函数本身所附加的的时间。这样,PC_ElapsedStop()函数中返回的数值就是准确的测量结果了。这两个函数都不具备可重入性,不要有多个任务同时调用这两个函数。
21
测量PC_DisplayChar()的执行时间(us)
测量代码执行时间的程序 INT16U time; PC_ElapsedInit(); .. PC_ElapsedStart(); PC_DispChar(40, 24, ‘A’, DISP_FGND_WHITE); time = PC_ElapsedStop();
22
应用 μC/OS-II 的范例 用BC IDE (Integrated Development Environment)编译通过,在WindowsXP 的DOS窗口下编译运行。可执行代码在每个范例的OBJ子目录中。
23
IDE中编译选项 Code generation Model: Large Options: Treat enums as ints
Assume SS Equals DS: Default for memory model Advanced code generation Floating point: Emulation Instruction set: 80186 Options: Generate underbars Debug info in OBJs Fast floating point
24
IDE中编译选项 Optimizations Global register allocation
Invariant code motion Induction variables Loop optimization Suppress redundant loads Copy propagation Dead code elimination Jump optimization In-line intrinsic functions
25
IDE中编译选项 Register variables Automatic
Common subexpressions Optimize globally Optimize for Speed
26
例1 有13个任务(包括 μC/OS-II 的空闲任务和一个计算CPU利用率的任务)。TaskStart()在函数main()中建立,功能是建立其它任务并且在屏幕上显示如下统计信息: 每秒钟任务切换次数;CPU利用百分率;寄存器切换次数;目前日期和时间;μC/OS-II的版本号; TaskStart()还检查是否按下ESC键,以决定是否返回到DOS。 其余10个任务基于相同的代码——Task();每个任务在屏幕上随机的位置显示一个0到9的数字。
27
main() main()程序从清整个屏幕开始,为的是保证屏幕上不留有以前的DOS下的显示。
用户在使用任何服务之前先调用OSInit() 。建立两个任务:空闲任务和统计任务,前者在没有其它任务处于就绪态时运行;后者计算CPU的利用率。
28
main()程序 void main (void) {
PC_DispClrScr(DISP_FGND_WHITE + DISP_BGND_BLACK); (1) OSInit(); (2) PC_DOSSaveReturn(); /保存当前DOS环境 (3) PC_VectSet(uCOS, OSCtxSw); /设置任务切换处理函数 (4) RandomSem = OSSemCreate(1); /信号量置为1,在某一时刻只有一个任务可以调用随机数产生函数 (5) OSTaskCreate(TaskStart, (void *)0, /启动多任务OSStart()之前,用户至少要先建立一个任务如TaskStart() (void*)&TaskStartStk[TASK_STK_SIZE-1], 0); (6) OSStart(); /启动多任务 (7) }
29
OSInit()初始化【在OS_CORE.C中定义】
void OSInit (void) { #if OS_VERSION >= 204 OSInitHookBegin(); /系统初始化开始接口函数 #endif OS_InitMisc(); /初始化变量 OS_InitRdyList(); /初始化任务就绪列表 OS_InitTCBList(); /初始化任务控制块列表 OS_InitEventList(); /初始化事件控制块列表
30
OSInit()初始化(续) #if (OS_VERSION >= 251) && (OS_FLAG_EN > 0) && (OS_MAX_FLAGS > 0) OS_FlagInit(); /事件标志结构初始化 #endif #if (OS_MEM_EN > 0) && (OS_MAX_MEM_PART > 0) OS_MemInit(); /内存管理初始化 #endif #if (OS_Q_EN > 0) && (OS_MAX_QS > 0) OS_QInit(); /消息队列初始化 OS_InitTaskIdle(); /创建空闲任务(无条件) #if OS_TASK_STAT_EN > 0 OS_InitTaskStat(); /创建统计任务 #if OS_VERSION >= 204 OSInitHookEnd(); /系统初始化结束接口函数 #if OS_VERSION >= 270 && OS_DEBUG_EN > 0 OSDebugInit(); }
31
OS_InitMisc()初始化变量 OSIntNesting = 0; /清除中断嵌套计数器
OSLockNesting = 0; /清除调度锁定计数器 OSTaskCtr = 0; /任务数清零 OSRunning = FALSE; /系统多任务没有执行 OSIdleCtr = 0L; /清除空闲任务计数器 OSCtxSwCtr = 0; /清除程序切换计数器
32
OS_InitRdyList()初始化任务就绪列表
static void OS_InitRdyList (void) { INT8U i; INT8U *prdytbl; OSRdyGrp = 0x00; /清除任务就绪表 prdytbl = &OSRdyTbl[0]; /将OSRdyTbl[]数组全部初始化0 for (i = 0; i < OS_RDY_TBL_SIZE; i++) { *prdytbl++ = 0x00; } /把任务就绪表里面所有的项目全部清零 OSPrioCur = 0; /当前运行任务的优先级寄存器清零 OSPrioHighRdy = 0; /将处于就绪状态的最高优先级的任务寄存器清零 OSTCBHighRdy = (OS_TCB *)0; /将处于就绪态的最高优先级的任务控制块指针寄存器清零 OSTCBCur = (OS_TCB *)0; /将当前运行任务的任务控制块指针寄存器清零 OSTCBList = (OS_TCB *)0; /任务控制块列表清零 for (i = 0; i < (OS_LOWEST_PRIO + 1); i++) {OSTCBPrioTbl[i] = (OS_TCB *)0; } / 清除优先级列表,这个列表存储各个优先级对应的任务控制块的地址,系统用它来寻找下一个要运行的任务地址 }
33
OS_InitTCBList()初始化任务控制块列表
static void OS_InitTCBList (void) { INT8U i; OS_TCB *ptcb1; OS_TCB *ptcb2; OS_MemClr((INT8U *)&OSTCBTbl[0], sizeof(OSTCBTbl)); / 清除TCB OS_MemClr((INT8U *)&OSTCBPrioTbl[0], sizeof(OSTCBPrioTbl)); / 清除优先级表 ptcb1 = &OSTCBTbl[0]; /任务控制块列表的第一个任务块地址给变量PTCB1 ptcb2 = &OSTCBTbl[1]; /任务控制块列表的第二个任务块地址给变量PTCB2 for (i = 0; i < (OS_MAX_TASKS + OS_N_SYS_TASKS - 1); i++) { /初始化任务控制块列表 ptcb1->OSTCBNext = ptcb2; #if OS_TASK_NAME_SIZE > 1 ptcb1->OSTCBTaskName[0] = '?'; /* Unknown name */ ptcb1->OSTCBTaskName[1] = OS_ASCII_NUL;/把前一个任务控制块的下一个任务指针指向下一个任务控制块 #endif ptcb1++; ptcb2++; } ptcb1->OSTCBNext = (OS_TCB *)0; /把最后一个任务控制块的下个指针清零 #if OS_TASK_NAME_SIZE > 1 ptcb1->OSTCBTaskName[0] = ‘?’; /* Unknown name */ ptcb1->OSTCBTaskName[1] = OS_ASCII_NUL; #endif OSTCBList = (OS_TCB *)0; /TCB列表初始化 OSTCBFreeList = &OSTCBTbl[0]; /把第一个任务控制块的地址送给空闲任务控制块列表 }
34
建立其它任务的任务 TaskStart()
void TaskStart (void *data) {TaskStart()将data的值赋给自己,以免某些编译器的警告 TaskStartDispInit(); (1) /在屏幕顶端显示一个标识,说明这是例1 。 OS_ENTER_CRITICAL(); PC_VectSet(0x08, OSTickISR); (2) PC_SetTickRate(200); (3) /关中断,以改变中断向量,让其指向μC/OS-II的时钟节拍处理,然后,改变时钟节拍为 200Hz OS_EXIT_CRITICAL();
35
建立其它任务的任务 TaskStart()(续)
OSStatInit(); /测试处理器的速度(运行任务时的CPU使用率) (4) TaskStartCreateTasks(); /建立10个同样的任务 (5) for (;;) { /每个任务都是一个无限循环 TaskStartDisp();/在Dos窗口底部,显示相关信息(任务个数、CPU利用率、任务切换次数、ucOS-II版本号和处理器是否包含浮点处理单元FPU) If (PC_GetKey(&key)==TRUE) {/检查是否有键按下 if (key==0x1B) {/是否按下Esc键 PC_DOSReturn(); } } OSCtxSWCtr=0; /如果没有按下Esc键,则记录任务切换次数的全局变量清零 OSTimeDlyHMSM(0,0,1,0);/TaskStart()任务将自身挂起1秒,ucOS-II开始调度,找下一个最高优先级(=1)的就绪任务
36
测试CPU速度 void OSStatInit (void) { OSTimeDly(2); /延时两个时钟节拍
OS_ENTER_CRITICAL(); OSIdleCtr = 0L; /32位的计数器OSIdleCtr被清0,并产生另一个延时,这个延时使OSStatInit()挂起 。 OS_EXIT_CRITICAL(); /uCOS-II执行一个无限循环的空闲任务,不断的递增OSIdleCtr OSTimeDly(OS_TICKS_PER_SEC); OS_ENTER_CRITICAL(); OSIdleCtrMax = OSIdleCtr; OSStatRdy = TRUE; /μCOS-II将统计CPU的利用率 OS_EXIT_CRITICAL(); }
37
建立10个同样的任务 Static void TaskStartCreateTasks(void) {INT8U i;
for(i=0;i<N_TASKS;i++){ TaskData[i]=‘0’+i;/初始化字符数组,包含0~9 OSTaskCreate(Task, /循环建立N_TASKS个相同的任务,每个任务显示不同的字符 (void *)&TaskData[i], /每个任务接受一个指向字符数组中元素的指针 &TaskStk[i][TASK_STK_SIZE-1], /每个任务各自的堆栈空间 i+1); /每个任务各自的优先级1~10} }
38
在屏幕随机位置显示任务号(0~9) void Task (void *data) { UBYTE x; UBYTE y;
UBYTE err; for (;;) { OSSemPend(RandomSem, 0, &err); /获取信号量RandomSem ,同时禁止其他任务运行这段代码 x = random(80); /获得一个随机数 y = random(16); /获得一个随机数 OSSemPost(RandomSem); /计算出x和y坐标并释放信号量 PC_DispChar(x, y + 5, *(char *)data, DISP_FGND_LIGHT_GRAY); /在计算的坐标处显示其任务号(0-9) OSTimeDly(1); /延时一个时钟节拍,等待进入下一次循环 }
39
例1的运行结果
40
例2 磁盘文件为\SOFTWARE\uCOS-II\EX2_x86L,它包含9个任务。加上uCOS-II本身的两个任务:空闲任务(idle task)和统计任务,共11个任务。 由main()中的TaskStart()函数建立任务,功能是建立其他任务并在屏幕上显示如下的统计数据: 每秒种任务切换的次数; CPU利用率的百分比; 当前日期和时间; uCOS_II的版本号; 使用带扩展功能的任务建立函数OSTaskCreateExt()和uCOS-II的堆栈检查操作(要使用堆栈检查操作必须用OSTaskCreateExt()建立任务)。
41
μC/OS-II stack checking
uCOS-II的堆栈检查功能要求任务建立时堆栈清零。OSTaskCreateExt()可以执行此项操作(设置选项OS_TASK_OPT_STK_CHK和OS_TASK_OPT_STK_CLR打开此项操作)。OSTaskCreateExt()进行堆栈清零操作是一项很费时的工作,取决于堆栈的大小。执行堆栈检查操作的时候,uCOS-II从栈底向栈顶搜索非0元素(参看上图),同时用一个计数器记录0元素的个数。
42
main() 例2的main()函数和例1的看起来差不多;
有两点区别:第一,main()函数调用PC_ElapsedInit()来初始化定时器记录OSTaskStkChk()的执行时间。第二,所有的任务都使用OSTaskCreateExt()函数来建立(替代了OSTaskCreate()函数),这使得每一个任务都可进行堆栈检查。
43
例2中的Main()函数 void main (void) { PC_DispClrScr(DISP_FGND_WHITE + DISP_BGND_BLACK); OSInit(); PC_DOSSaveReturn(); PC_VectSet(uCOS, OSCtxSw); PC_ElapsedInit(); /对消逝时间的测量进行初始化 OSTaskCreateExt(TaskStart, (void *)0, &TaskStartStk[TASK_STK_SIZE-1], TASK_START_PRIO, TASK_START_ID, &TaskStartStk[0], TASK_STK_SIZE, (void *)0, OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR); (2) OSStart(); } 除了OSTaskCreate()函数的四个参数外,OSTaskCreateExt()还需要五个参数:任务的ID,一个指向任务堆栈栈底的指针,堆栈的大小(以堆栈单元为单位,80X86中为字),一个指向用户定义的TCB扩展数据结构的指针,和一个用于指定对任务操作的变量。
44
TaskStart() TaskStart()建立两个邮箱,分别提供给任务4和任务5。
接着建立一个专门显示时间和日期的任务,随后又建立5个不同的任务。
45
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; /关中断,PC_VectSet() Change the tick rate to 200 Hz; /PC_SetTickRate() ,开中断 Initialize the statistics task;/OSStatInit() 以上几项操作和例1中的相同。
46
TaskStart()的伪码(续) Create 2 mailboxes which are used by Task #4 and #5; (1) / 调用TaskStartCreateTasks(void) Create a task that will display the date and time on the screen; (2) Create 5 application tasks; 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;
47
创建2个邮箱 通过调用2个OSMboxCreate((void *)0)函数,创建2个邮箱。 这2个邮箱在任务4与任务5中使用。
48
TaskStartCreateTasks(void)
static void TaskStartCreateTasks (void) { OSTaskCreateExt(TaskClk, (void *)0, &TaskClkStk[TASK_STK_SIZE - 1], TASK_CLK_PRIO, TASK_CLK_ID, &TaskClkStk[0], TASK_STK_SIZE, (void *)0, OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR); OSTaskCreateExt(Task1, (void *)0, &Task1Stk[TASK_STK_SIZE - 1], TASK_1_PRIO, TASK_1_ID,&Task1Stk[0], OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR); ………………………………………… OSTaskCreateExt(Task5, ……..); }
49
时钟显示任务 TaskClk()函数用于显示当前日期和时间,每秒更新一次。
50
时钟显示任务程序 void TaskClk (void *data) { Struct time now;
Struct date today; char s[40]; data = data; for (;;) { PC_GetDateTime(s); PC_DispStr(0, 24, s, DISP_FGND_BLUE + DISP_BGND_CYAN); OSTimeDly(OS_TICKS_PER_SEC); }
51
任务1 任务1将检查其他七个任务堆栈的大小,同时记录OSTaskStkChk()函数的执行时间,并与堆栈大小(所有堆栈的大小都是以字节为单位)一起显示出来。任务1每秒执行10次(间隔100ms)。
52
任务1程序 void Task1 (void *pdata) { INT8U err; OS_STK_DATA data; /任务堆栈数据
INT16U time; /执行时间 INT8U i; char s[80]; pdata = pdata;
53
任务1程序 (续) for (;;) { for (i = 0; i < 7; i++) {
PC_ElapsedStart();/ 统计OSTaskStkChk()函数运行时间开始 err = OSTaskStkChk(TASK_START_PRIO+i, &data); time = PC_ElapsedStop(); /统计OSTaskStkChk()函数运行时间结束 if (err == OS_NO_ERR) { sprintf(s, "%3ld %3ld %3ld %5d", data.OSFree + data.OSUsed, data.OSFree, data.OSUsed, time); PC_DispStr(19, 12+i, s, DISP_FGND_YELLOW); } /显示统计结果 } OSTimeDlyHMSM(0, 0, 0, 100); /延时100ms }
54
任务2 任务2在屏幕上显示一个顺时针旋转的指针(用横线__,竖线|,斜线/、\,等字符表示),每200ms旋转一格。
55
任务2程序 void Task2 (void *data) { data = data; for (;;) {
PC_DispChar(70, 15, '|', DISP_FGND_WHITE + DISP_BGND_RED); OSTimeDly(10); PC_DispChar(70, 15, '/',
56
任务2程序 (续) OSTimeDly(10); PC_DispChar(70, 15, '-',
DISP_FGND_WHITE + DISP_BGND_RED); PC_DispChar(70, 15, '\\', OSTimeDly(10); } }
57
任务3 任务3也显示了一个旋转指针,但是与任务2旋转的方向不同。任务3在堆栈中分配了一个500B的数组,占用了Task3的堆栈,程序运行后可看出,Task3的空闲堆栈比Task2少了502B(500B的数组和一个2B的整型变量)。
58
任务3程序 void Task3 (void *data) { char dummy[500]; INT16U i;
data = data; for (I = 0; i < 499; i++) { dummy[i] = '?'; }
59
任务3程序 (续) for (;;) { PC_DispChar(70, 16, '|',
DISP_FGND_WHITE + DISP_BGND_BLUE); OSTimeDly(20); PC_DispChar(70, 16, '\\', PC_DispChar(70, 16, '-', PC_DispChar(70, 16, '/', OSTimeDly(20); } }
60
任务4 任务4向任务5发送消息并等待确认。发送的消息是一个指向字符的指针。每当任务4从任务5收到确认,就将传递的ASCII码加1再发送,结果是不断的传送“ABCDEFG....”。
61
任务4程序 void Task4 (void *data) { char txmsg; INT8U err; data = data;
txmsg = 'A';
62
任务4程序 (续) for (;;) { while (txmsg <= 'Z'){
OSMboxPost(TxMbox, (void *)&txmsg); /向任务5发送消息(发送到TxMbox中) OSMboxPend(AckMbox, 0, &err); /等待任务5的应答(任务5应答发送到AckMbox中),第2个参数0表示永远等待下去。 txmsg++; /Task5应答后,更新发送下一条消息 } txmsg = ‘A’; /开始一个新的消息序列
63
任务5 当任务5 接收消息后(发送的字符),就将消息显示到屏幕上,然后延时1秒,再向任务4发送确认信息。
64
任务5程序 void Task5 (void *data) { char *rxmsg; INT8U err; data = data;
65
任务5程序 (续) for (;;) { rxmsg = (char *)OSMboxPend(TxMbox, 0, &err);
/Task5无限期等待TxMbox邮箱(任务4发送)的消息 PC_DispChar(70, 18, *rxmsg, /显示邮箱的消息 DISP_FGND_YELLOW+DISP_BGND_RED); OSTimeDlyHMSM(0, 0, 1, 0); (3) /延时等待1s OSMboxPost(AckMbox, (void *)1); /当接收到消息后,给出应答 }
66
例2的运行结果
67
例3 使用了uCOS-II提供的许多附加功能。
使用了OSTaskCreateExt()中TCB的扩展数据结构,用户定义的任务切换对外接口函数(OSTaskSwHook()),用户定义的统计任务(statistic task )的对外接口函数(OSTaskStatHook())以及消息队列。 例3的磁盘文件位于\SOFTWARE\uCOS-II\EX3_x86L。除了空闲任务(idle task)和统计任务(statistic task ),还有7个任务,总共9个任务。
68
main() main()函数和例2中的相差不多;
不同的是在用户定义的TCB扩展数据结构中可以保存每个任务的名称(扩展结构的声明在INCLUDES.H中定义)。 定义了30个字节来存放任务名(包括空格)。本例中没有用到堆栈检查操作,TaskStart()中禁止该操作。
69
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"); 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(); }
70
TCB扩展数据结构 typedef struct { char TaskName[30]; (1) INT16U TaskCtr;
INT16U TaskExecTime; INT32U TaskTotExecTime; } TASK_USER_DATA; 定义了30个字节来存放任务名(包括空格)
71
TaskStart() 与例2有3处不同: 为任务1,2,3建立了一个消息队列; 每个任务都有一个名字,保存在任务的TCB扩展数据结构中;
禁止堆栈检查。
72
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;
73
TaskStart()的伪码 (续) Create a message queue; /为任务1,2,3建立一个消息队列
/调用TaskStartCreateTasks()创建任务 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.; /每个任务的名字都保存在任务的TCB扩展数据结构中 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; }
74
为任务1,2,3建立一个消息队列 MsgQueue = OSQCreate(&MsgQueueTbl[0], MSG_QUEUE_SIZE);
75
TaskStartCreateTasks()
void TaskStartCreateTasks (void) { strcpy(TaskUserData[TASK_CLK_ID].TaskName, "Clock Task"); OSTaskCreateExt(TaskClk, (void *)0, &TaskClkStk[TASK_STK_SIZE - 1], TASK_CLK_PRIO,TASK_CLK_ID, &TaskClkStk[0], TASK_STK_SIZE, &TaskUserData[TASK_CLK_ID],0); /创建显示日期与时间的任务 strcpy(TaskUserData[TASK_1_ID].TaskName, "MsgQ Rx Task"); OSTaskCreateExt(Task1,(void *)0, &Task1Stk[TASK_STK_SIZE - 1], TASK_1_PRIO,TASK_1_ID, &Task1Stk[0],TASK_STK_SIZE, &TaskUserData[TASK_1_ID],0); /创建任务1 strcpy(TaskUserData[TASK_5_ID].TaskName, "TimeDlyTask"); OSTaskCreateExt(Task5,(void *)0,&Task5Stk[TASK_STK_SIZE - 1], TASK_5_PRIO,TASK_5_ID,&Task5Stk[0],TASK_STK_SIZE, &TaskUserData[TASK_5_ID],0); /创建任务5 }
76
时钟显示任务 TaskClk()函数用于显示当前日期和时间。 与例2类似。
77
任务1 任务1向消息队列发送一个消息,然后延时等待消息发送完成。这段时间可以让接收消息的任务显示收到的消息。 发送的消息有三种。
78
任务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); } }
79
任务2 任务2处于等待消息的挂起状态,且不设定最大等待时间。 任务2将一直等待直到收到消息。
当收到消息后,任务2显示消息并且延时500mS,延时的时间可以使任务3检查消息队列。
80
任务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) /延时500mS,同时使任务3检查消息队列 OSTimeDlyHMSM(0, 0, 0, 500); (3) } }
81
任务3 任务3同样处于等待消息的挂起状态,但是它设定了等待结束时间250mS。
如果有消息来到,任务3将显示消息号,如果超过了等待时间,任务3就显示“T”(意为timeout)。
82
任务3程序 void Task3 (void *data) { INT8U *msg; INT8U err; data = data;
for (;;) { msg = (INT8U *)OSQPend(MsgQueue, OS_TICKS_PER_SEC/4, &err); (1) /等待消息的挂起状态,设定等待结束时间250mS If (err == OS_TIMEOUT) {PC_DispChar(70,15,'T',DISP_FGND_YELLOW+DISP_BGND_RED); (2) /如果超过了等待时间,就显示“T” } else {PC_DispChar(70,15,*msg, DISP_FGND_YELLOW+DISP_BGND_BLUE); (3) /有消息来到,任务3将显示消息号 } }
83
任务4 任务4的操作只是从邮箱发送和接收。 用户可以测量任务在自己PC上执行的时间。 任务4每10mS执行一次。
84
任务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) /10mS执行一次 }
85
任务5 任务5除了延时一个时钟节拍以外什么也不做。
注意所有的任务都应该调用uCOS-II的函数,等待延时结束或者事件的发生而让出CPU。如果始终占用CPU,这将使低优先级的任务无法得到CPU。
86
任务5程序 void Task5 (void *data) { data = data; for (;;) {
OSTimeDly(1); (1) }
87
OSTaskSwHook()函数 通过OSStart ()中的OSStartHighRdy()来调用OSTaskSwHook()函数;
该函数用来测量每个任务的执行时间,可以用来统计每一个任务的调度频率,也可以统计每个任务运行时间的总和。并将这些信息存储在每个任务的TCB扩展数据结构中。 每次任务切换的时候OSTaskSwHook()都将被调用。
88
用户定义的OSTaskSwHook() void OSTaskSwHook (void)
{ INT16U time; TASK_USER_DATA *puser; time = PC_ElapsedStop(); /获取任务的运行时间,并赋予time变量,然后清零,为下一次计数做准备。 PC_ElapsedStart(); /启动PC的定时器2开始记数 puser = OSTCBCur->OSTCBExtPtr; if (puser != (void *)0) {/如果任务分配了TCB扩展数据结构 puser->TaskCtr++; /计数器TaskCtr进行累加以统计任务被切换的频繁程度,或检查某个任务是否在运行。 puser->TaskExecTime= time; /记录函数从切入到切出的运行时间 puser->TaskTotExecTime += time; /记录任务总的运行时间 } }
89
OSTaskStatHook()函数 本例的统计任务(statistic task)通过OS_TaskStat ()调用对外接口函数OSTaskStatHook()(设置OS_CFG.H文件中的OS_TASK_STAT_EN为1,允许对外接口函数),统计任务每秒运行一次。 对OSTaskSwHook()中每个任务的TaskExecTime与TaskTotExecTime两个变量进行统计,可以计算出一段时间内各个任务所占用CPU的百分比。 OSTaskStatHook()函数会显示这些统计信息。
90
用户定义的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) /将数字显示为ASCII字符 DispTaskStat(i); (2)}
91
用户定义的OSTaskStatHook()(续)
if (total > 0) {for (i = 0; i < 7; i++) {pct = 100 * TaskUserData[i].TaskTotExecTime / total; /计算每个任务运行时间的百分比 sprintf(s, "%3d %%", pct); PC_DispStr(62, i + 11, s, DISP_FGND_YELLOW);/显示出来 } } if (total > L) {for (i = 0; i < 7; i++) {TaskUserData[i].TaskTotExecTime = 0L;} } }
92
例3的运行结果
93
例4 与例4相关的磁盘文件位于\SOFTWARE\uCOS-II\Ix86L-FP和\SOFTWARE\uCOS-II\EX4_x86L.FP中。除了空闲任务(idle task)和统计任务(statistic task ),还有10个相同的任务,总共13个任务。TaskStart()在函数main()中建立,功能是建立其它任务并且在屏幕上显示如下统计信息:任务总数;每秒钟任务切换次数;CPU利用率;μC/OS-II的版本号; 其余10个任务基于同一代码—Task();每个任务按任务序号1~10 (优先级顺序)显示相应的角度、角度的余弦和角度的正弦。
94
main() main()函数和前3例中的相差不多;
不同的是创建了10个相同的任务,并且对于每个任务,均设定为OS_TASK_OPT_SAVE_FP(支持统计任务的浮点运算)。
95
main()函数 void main (void)
{ PC_DispClrScr(DISP_FGND_WHITE + DISP_BGND_BLACK); OSInit(); PC_DOSSaveReturn(); PC_VectSet(uCOS, OSCtxSw); OSTaskCreateExt(TaskStart,(void *)0, TaskStartStk[TASK_STK_SIZE - 1], 0, /任务优先级为0(最高) 0, &TaskStartStk[0], TASK_STK_SIZE, (void *)0, OS_TASK_OPT_SAVE_FP);/支持浮点运算 OSStart(); }
96
TaskStart ()函数 与前例1差不多,主要功能是显示相关的统计信息,并且调用TaskStartCreateTasks()创建10个相同的任务。
97
TaskStartCreateTasks()函数
static void TaskStartCreateTasks (void) { INT8U i; INT8U prio; for (i = 0; i < N_TASKS; i++) { /创建N_TASKS =10个相同的任务 prio = i + 1; /每个任务的序号也是优先级的序号 TaskData[i] = prio; OSTaskCreateExt(Task,(void *)&TaskData[i], &TaskStk[i][TASK_STK_SIZE - 1], prio,0,&TaskStk[i][0], TASK_STK_SIZE, (void *)0, OS_TASK_OPT_SAVE_FP);/支持浮点运算 }
98
Task ()函数 void Task (void *pdata)
{ FP32 x; FP32 y; FP32 angle; FP32 radians; /设定浮点变量 char s[81]; INT8U ypos; ypos = *(INT8U *)pdata + 7; angle = (FP32)(*(INT8U *)pdata) * (FP32)36.0; /计算角度 for (;;) { radians = (FP32)2.0 * (FP32) * angle / (FP32)360.0; x = cos(radians); /计算角度的余弦 y = sin(radians); /计算角度的正弦 sprintf(s, " %2d %8.3f %8.3f %8.3f", *(INT8U *)pdata, angle, x, y); PC_DispStr(0, ypos, s, DISP_FGND_BLACK + ISP_BGND_LIGHT_GRAY); if (angle >= (FP32)360.0) { angle = (FP32)0.0; } else { angle += (FP32)0.01; } OSTimeDly(1); }
99
例4的运行结果
Similar presentations