嵌入式实时操作系统uCOSII第4章 任务管理

更新时间:2023-05-23 08:34:01 阅读量: 实用文档 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

嵌入式实时操作系统uCOSII

第4章

4.0 4.1 4.2 4.3 4.4 4.5 4.6 4.7 4.8 4.9

任务管理...................................................................................................................1 建立任务,OSTaskCreate().....................................................................................2 建立任务,OSTaskCreateExt()................................................................................6 任务堆栈...................................................................................................................9 堆栈检验,OSTaskStkChk()..................................................................................11 删除任务,OSTaskDel()........................................................................................14 请求删除任务,OSTaskDelReq().........................................................................17 改变任务的优先级,OSTaskChangePrio()...........................................................20 挂起任务,OSTaskSuspend()................................................................................23 恢复任务,OSTaskResume().................................................................................25 获得有关任务的信息,OSTaskQuery()................................................................26

嵌入式实时操作系统uCOSII

第4章 任务管理

在前面的章节中,笔者曾说过任务可以是一个无限的循环,也可以是在一次执行完毕后被删除掉。这里要注意的是,任务代码并不是被真正的删除了,而只是µC/OS-Ⅱ不再理会该任务代码,所以该任务代码不会再运行。任务看起来与任何C函数一样,具有一个返回类型和一个参数,只是它从不返回。任务的返回类型必须被定义成void型。在本章中所提到的函数可以在OS_TASK文件中找到。如前所述,任务必须是以下两种结构之一:

void YourTask (void *pdata) {

for (;;) { /* 用户代码 */

调用µC/OS-Ⅱ的服务例程之一: OSMboxPend(); OSQPend(); OSSemPend();

OSTaskDel(OS_PRIO_SELF); OSTaskSuspend(OS_PRIO_SELF); OSTimeDly(); OSTimeDlyHMSM(); /* 用户代码 */ } }

void YourTask (void *pdata) {

/* 用户代码 */

OSTaskDel(OS_PRIO_SELF); }

本章所讲的内容包括如何在用户的应用程序中建立任务、删除任务、改变任务的优先级、挂起和恢复任务,以及获得有关任务的信息。 µC/OS-Ⅱ可以管理多达64个任务,并从中保留了四个最高优先级和四个最低优先级的任务供自己使用,所以用户可以使用的只有56个任务。任务的优先级越高,反映优先级的值则越低。在最新的µC/OS-Ⅱ版本中,任务的优先级数也可作为任务的标识符使用。

嵌入式实时操作系统uCOSII

4.0 建立任务,OSTaskCreate()

想让µC/OS-Ⅱ管理用户的任务,用户必须要先建立任务。用户可以通过传递任务地址和其它参数到以下两个函数之一来建立任务:OSTaskCreate() 或 OSTaskCreateExt()。OSTaskCreate()与µC/OS是向下兼容的,OSTaskCreateExt()是OSTaskCreate()的扩展版本,提供了一些附加的功能。用两个函数中的任何一个都可以建立任务。任务可以在多任务调度开始前建立,也可以在其它任务的执行过程中被建立。在开始多任务调度(即调用OSStart())前,用户必须建立至少一个任务。任务不能由中断服务程序(ISR)来建立。

OSTaskCreate()的代码如程序清单 L4.1所述。从中可以知道,OSTaskCreate()需要四个参数:task是任务代码的指针,pdata是当任务开始执行时传递给任务的参数的指针,ptos是分配给任务的堆栈的栈顶指针(参看4.02,任务堆栈),prio是分配给任务的优先级。

程序清单 L4.1 OSTaskCreate()

INT8U OSTaskCreate (void (*task)(void *pd), void *pdata, OS_STK *ptos, INT8U prio) {

void *psp; INT8U err;

if (prio > OS_LOWEST_PRIO) { return (OS_PRIO_INVALID); }

OS_ENTER_CRITICAL();

if (OSTCBPrioTbl[prio] == (OS_TCB *)0) { (2) OSTCBPrioTbl[prio] = (OS_TCB *)1; OS_EXIT_CRITICAL();

(3)

(1)

(4)

psp = (void *)OSTaskStkInit(task, pdata, ptos, 0); (5) err = OSTCBInit(prio, psp, (void *)0, 0, 0, (void *)0, 0); (6) if (err == OS_NO_ERR) { OS_ENTER_CRITICAL(); OSTaskCtr++;

(8)

(9)

(7)

OSTaskCreateHook(OSTCBPrioTbl[prio]); OS_EXIT_CRITICAL(); if (OSRunning) { OSSched(); }

(10) (11)

嵌入式实时操作系统uCOSII

} else {

OS_ENTER_CRITICAL();

OSTCBPrioTbl[prio] = (OS_TCB *)0; OS_EXIT_CRITICAL(); }

return (err); } else {

OS_EXIT_CRITICAL(); return (OS_PRIO_EXIST); } }

(12)

OSTaskCreate()一开始先检测分配给任务的优先级是否有效[L4.1(1)]。任务的优先级必须在0到OS_LOWEST_PRIO之间。接着,OSTaskCreate()要确保在规定的优先级上还没有建立任务[L4.1(2)]。在使用µC/OS-Ⅱ时,每个任务都有特定的优先级。如果某个优先级是空闲的,µC/OS-Ⅱ通过放置一个非空指针在OSTCBPrioTbl[]中来保留该优先级[L4.1(3)]。这就使得OSTaskCreate()在设置任务数据结构的其他部分时能重新允许中断[L4.1(4)]。 然后,OSTaskCreate()调用OSTaskStkInit()[L4.1(5)],它负责建立任务的堆栈。该函数是与处理器的硬件体系相关的函数,可以在OS_CPU_C.C文件中找到。有关实现OSTaskStkInit()的细节可参看第8章——移植µC/OS-Ⅱ。如果已经有人在你用的处理器上成功地移植了µC/OS-Ⅱ,而你又得到了他的代码,就不必考虑该函数的实现细节了。OSTaskStkInit()函数返回新的堆栈栈顶(psp),并被保存在任务的0S_TCB中。注意用户得将传递给OSTaskStkInit()函数的第四个参数opt置0,因为OSTaskCreate()与OSTaskCreateExt()不同,它不支持用户为任务的创建过程设置不同的选项,所以没有任何选项可以通过opt参数传递给OSTaskStkInit()。 µC/OS-Ⅱ支持的处理器的堆栈既可以从上(高地址)往下(低地址)递减也可以从下往上递增。用户在调用OSTaskCreate()的时候必须知道堆栈是递增的还是递减的(参看所用处理器的OS_CPU.H中的OS_STACK_GROWTH),因为用户必须得把堆栈的栈顶传递给OSTaskCreate(),而栈顶可能是堆栈的最高地址(堆栈从上往下递减),也可能是最低地址(堆栈从下往上长)。 一旦OSTaskStkInit()函数完成了建立堆栈的任务,OSTaskCreate()就调用OSTCBInit()[L4.1(6)],从空闲的OS_TCB池中获得并初始化一个OS_TCB。OSTCBInit()的代码如程序清单 L4.2所示,它存在于0S_CORE.C文件中而不是OS_TASK.C文件中。OSTCBInit()函数首先从OS_TCB缓冲池中获得一个OS_TCB[L4.2(1)],如果OS_TCB池中有空闲的OS_TCB[L4.2(2)],它就被初始化[L4.2(3)]。注意一旦OS_TCB被分配,该任务的创建者就已经完全拥有它了,即使这时内核又创建了其它的任务,这些新任务也不可能对已分配的OS_TCB作任何操作,所以OSTCBInit()在这时就可以允许中断,并继续初始化OS_TCB的数据单元。

嵌入式实时操作系统uCOSII

程序清单 L 4.2 OSTCBInit()

INT8U OSTCBInit (INT8U prio, OS_STK *ptos, OS_STK *pbos, INT16U id, INT16U stk_size, void *pext, INT16U opt) {

OS_TCB *ptcb;

OS_ENTER_CRITICAL(); ptcb = OSTCBFreeList; if (ptcb != (OS_TCB *)0) {

OSTCBFreeList = ptcb->OSTCBNext; OS_EXIT_CRITICAL();

ptcb->OSTCBStkPtr = ptos; ptcb->OSTCBPrio = (INT8U)prio; ptcb->OSTCBStat = OS_STAT_RDY; ptcb->OSTCBDly = 0; #if OS_TASK_CREATE_EXT_EN

ptcb->OSTCBExtPtr = pext; ptcb->OSTCBStkSize = stk_size; ptcb->OSTCBStkBottom = pbos; ptcb->OSTCBOpt = opt; ptcb->OSTCBId = id; #else

pext = pext; stk_size = stk_size; pbos = pbos; opt = opt; id = id; #endif

(3)

(1)

(2)

#if OS_TASK_DEL_EN

ptcb->OSTCBDelReq = OS_NO_ERR; #endif

ptcb->OSTCBY = prio >> 3;

嵌入式实时操作系统uCOSII

ptcb->OSTCBBitY = OSMapTbl[ptcb->OSTCBY]; ptcb->OSTCBX = prio & 0x07;

ptcb->OSTCBBitX = OSMapTbl[ptcb->OSTCBX];

#if OS_MBOX_EN || (OS_Q_EN && (OS_MAX_QS >= 2)) || OS_SEM_EN ptcb->OSTCBEventPtr = (OS_EVENT *)0; #endif

#if OS_MBOX_EN || (OS_Q_EN && (OS_MAX_QS >= 2)) ptcb->OSTCBMsg = (void *)0; #endif

OS_ENTER_CRITICAL();

OSTCBPrioTbl[prio] = ptcb; ptcb->OSTCBNext = OSTCBList; ptcb->OSTCBPrev = (OS_TCB *)0; if (OSTCBList != (OS_TCB *)0) { OSTCBList->OSTCBPrev = ptcb; }

OSTCBList = ptcb;

OSRdyGrp |= ptcb->OSTCBBitY; OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX; OS_EXIT_CRITICAL(); return (OS_NO_ERR); } else {

OS_EXIT_CRITICAL(); return (OS_NO_MORE_TCB); } }

(7)

(6)

(4)

(5)

当OSTCBInit()需要将OS_TCB插入到已建立任务的OS_TCB的双向链表中时[L4.2(5)],它就禁止中断[L4.2(4)]。该双向链表开始于OSTCBList,而一个新任务的OS_TCB常常被插入到链表的表头。最后,该任务处于就绪状态[L4.2(6)],并且OSTCBInit()向它的调用者[OSTaskCreate()]返回一个代码表明OS_TCB已经被分配和初始化了[L4.2(7)]。 现在,我可以继续讨论OSTaskCreate()(程序清单 L4.1)函数了。从OSTCBInit()返回后,OSTaskCreate()要检验返回代码[L4.1(7)],如果成功,就增加OSTaskCtr[L4.1(8)],OSTaskCtr用于保存产生的任务数目。如果OSTCBInit()返回失败,就置OSTCBPrioTbl[prio]

嵌入式实时操作系统uCOSII

的入口为0[L4.1(12)]以放弃该任务的优先级。然后,OSTaskCreate()调用OSTaskCreateHook()[L4.1(9)],OSTaskCreateHook()是用户自己定义的函数,用来扩展OSTaskCreate()的功能。例如,用户可以通过OSTaskCreateHook()函数来初始化和存储浮点寄存器、MMU寄存器的内容,或者其它与任务相关的内容。一般情况下,用户可以在内存中存储一些针对用户的应用程序的附加信息。OSTaskCreateHook()既可以在OS_CPU_C.C中定义(如果OS_CPU_HOOKS_EN置1),也可以在其它地方定义。注意,OSTaskCreate()在调用OSTaskCreateHook()时,中断是关掉的,所以用户应该使OSTaskCreateHook()函数中的代码尽量简化,因为这将直接影响中断的响应时间。OSTaskCreateHook()在被调用时会收到指向任务被建立时的OS_TCB的指针。这意味着该函数可以访问OS_TCB数据结构中的所有成员。 如果OSTaskCreate()函数是在某个任务的执行过程中被调用(即OSRunning置为True[L4.1(10)]),则任务调度函数会被调用[L4.1(11)]来判断是否新建立的任务比原来的任务有更高的优先级。如果新任务的优先级更高,内核会进行一次从旧任务到新任务的任务切换。如果在多任务调度开始之前(即用户还没有调用OSStart()),新任务就已经建立了,则任务调度函数不会被调用。

4.1 建立任务,OSTaskCreateExt()

用OSTaskCreateExt()函数来建立任务会更加灵活,但会增加一些额外的开销。OSTaskCreateExt()函数的代码如程序清单 L4.3所示。 我们可以看到OSTaskCreateExt()需要九个参数!前四个参数(task,pdata,ptos和prio)与OSTaskCreate()的四个参数完全相同,连先后顺序都一样。这样做的目的是为了使用户能够更容易地将用户的程序从OSTaskCreate()移植到OSTaskCreateExt()上去。 id参数为要建立的任务创建一个特殊的标识符。该参数在µC/OS以后的升级版本中可能会用到,但在µC/OS-Ⅱ中还未使用。这个标识符可以扩展µC/OS-Ⅱ功能,使它可以执行的任务数超过目前的64个。但在这里,用户只要简单地将任务的id设置成与任务的优先级一样的值就可以了。 pbos是指向任务的堆栈栈底的指针,用于堆栈的检验。 stk_size用于指定堆栈成员数目的容量。也就是说,如果堆栈的入口宽度为4字节宽,那么stk_size为10000是指堆栈有40000个字节。该参数与pbos一样,也用于堆栈的检验。 pext是指向用户附加的数据域的指针,用来扩展任务的OS_TCB。例如,用户可以为每个任务增加一个名字(参看实例3),或是在任务切换过程中将浮点寄存器的内容储存到这个附加数据域中,等等。 opt用于设定OSTaskCreateExt()的选项,指定是否允许堆栈检验,是否将堆栈清零,任务是否要进行浮点操作等等。µCOS_Ⅱ.H文件中有一个所有可能选项(OS_TASK_OPT_STK_CHK,OS_TASK_OPT_STK_CLR和OS_TASK_OPT_SAVE_FP)的常数表。每个选项占有opt的一位,并通过该位的置位来选定(用户在使用时只需要将以上OS_TASK_OPT_???选项常数进行位或(OR)操作就可以了)。

程序清单 L 4.3 OSTaskCreateExt()

INT8U OSTaskCreateExt (void (*task)(void *pd), void *pdata, OS_STK *ptos,

嵌入式实时操作系统uCOSII

INT8U prio, INT16U id, OS_STK *pbos, INT32U stk_size, void *pext, INT16U opt) {

void *psp; INT8U err; INT16U i; OS_STK *pfill;

if (prio > OS_LOWEST_PRIO) { return (OS_PRIO_INVALID); }

OS_ENTER_CRITICAL();

if (OSTCBPrioTbl[prio] == (OS_TCB *)0) { OSTCBPrioTbl[prio] = (OS_TCB *)1; OS_EXIT_CRITICAL();

(2) (3)

(1)

(4)

if (opt & OS_TASK_OPT_STK_CHK) { if (opt & OS_TASK_OPT_STK_CLR) { Pfill = pbos;

for (i = 0; i < stk_size; i++) { #if OS_STK_GROWTH == 1 *pfill++ = (OS_STK)0; #else

*pfill-- = (OS_STK)0; #endif } } }

psp = (void *)OSTaskStkInit(task, pdata, ptos, opt); (6) err = OSTCBInit(prio, psp, pbos, id, stk_size, pext, opt); (7)

(5)

嵌入式实时操作系统uCOSII

if (err == OS_NO_ERR) { OS_ENTER_CRITICAL; OSTaskCtr++;

(8)

(9)

(10)

OSTaskCreateHook(OSTCBPrioTbl[prio]); OS_EXIT_CRITICAL(); if (OSRunning) { OSSched(); } } else {

OS_ENTER_CRITICAL();

OSTCBPrioTbl[prio] = (OS_TCB *)0; OS_EXIT_CRITICAL(); }

return (err); } else {

OS_EXIT_CRITICAL(); return (OS_PRIO_EXIST); } }

(11) (12)

(13)

OSTaskCreateExt()一开始先检测分配给任务的优先级是否有效[L4.3(1)]。任务的优先级必须在0到OS_LOWEST_PRIO之间。接着,OSTaskCreateExt()要确保在规定的优先级上还没有建立任务[L4.3(2)]。在使用µC/OS-Ⅱ时,每个任务都有特定的优先级。如果某个优先级是空闲的,µC/OS-Ⅱ通过放置一个非空指针在OSTCBPrioTbl[]中来保留该优先级[L4.3(3)]。这就使得OSTaskCreateExt()在设置任务数据结构的其他部分时能重新允许中断[L4.3(4)]。 为了对任务的堆栈进行检验[参看4.03,堆栈检验,OSTaskStkChk()],用户必须在opt参数中设置OS_TASK_OPT_STK_CHK标志。堆栈检验还要求在任务建立时堆栈的存储内容都是0(即堆栈已被清零)。为了在任务建立的时候将堆栈清零,需要在opt参数中设置OS_TASK_OPT_STK_CLR。当以上两个标志都被设置好后,OSTaskCreateExt()才能将堆栈清零[L4.3(5)]。 接着,OSTaskCreateExt()调用OSTaskStkInit()[L4.3(6)],它负责建立任务的堆栈。该函数是与处理器的硬件体系相关的函数,可以在OS_CPU_C.C文件中找到。有关实现OSTaskStkInit()的细节可参看第八章——移植µC/OS-Ⅱ。如果已经有人在你用的处理器上成功地移植了µC/OS-Ⅱ,而你又得到了他的代码,就不必考虑该函数的实现细节了。OSTaskStkInit()函数返回新的堆栈栈顶(psp),并被保存在任务的0S_TCB中。 µC/OS-Ⅱ支持的处理器的堆栈既可以从上(高地址)往下(低地址)递减也可以从下往上递增(参看4.02,任务堆栈)。用户在调用OSTaskCreateExt()的时候必须知道堆栈是递增的还是递减的(参看用户所用处理器的OS_CPU.H中的OS_STACK_GROWTH),因为用户必须得把

嵌入式实时操作系统uCOSII

堆栈的栈顶传递给OSTaskCreateExt(),而栈顶可能是堆栈的最低地址(当OS_STK_GROWTH为0时),也可能是最高地址(当OS_STK_GROWTH为1时)。 一旦OSTaskStkInit()函数完成了建立堆栈的任务,OSTaskCreateExt()就调用OSTCBInit() [L4.3(7)],从空闲的OS_TCB缓冲池中获得并初始化一个OS_TCB。OSTCBInit()的代码在OSTaskCreate()中曾描述过(参看4.00节), 从OSTCBInit()返回后,OSTaskCreateExt()要检验返回代码[L4.3(8)],如果成功,就增加OSTaskCtr[L4.3(9)],OSTaskCtr用于保存产生的任务数目。如果OSTCBInit()返回失败,就置OSTCBPrioTbl[prio]的入口为0[L4.3(13)]以放弃对该任务优先级的占用。然后,OSTaskCreateExt()调用OSTaskCreateHook()[L4.3(10)],OSTaskCreateHook()是用户自己定义的函数,用来扩展OSTaskCreateExt()的功能。OSTaskCreateHook()可以在OS_CPU_C.C中定义(如果OS_CPU_HOOKS_EN置1),也可以在其它地方定义(如果OS_CPU_HOOKS_EN置0)。注意,OSTaskCreateExt()在调用OSTaskCreateHook()时,中断是关掉的,所以用户应该使OSTaskCreateHook()函数中的代码尽量简化,因为这将直接影响中断的响应时间。OSTaskCreateHook()被调用时会收到指向任务被建立时的OS_TCB的指针。这意味着该函数可以访问OS_TCB数据结构中的所有成员。 如果OSTaskCreateExt()函数是在某个任务的执行过程中被调用的(即OSRunning置为True[L4.3(11)]),以任务调度函数会被调用[L4.3(12)]来判断是否新建立的任务比原来的任务有更高的优先级。如果新任务的优先级更高,内核会进行一次从旧任务到新任务的任务切换。如果在多任务调度开始之前(即用户还没有调用OSStart()),新任务就已经建立了,则任务调度函数不会被调用。

4.2 任务堆栈

每个任务都有自己的堆栈空间。堆栈必须声明为OS_STK类型,并且由连续的内存空间组成。用户可以静态分配堆栈空间(在编译的时候分配)也可以动态地分配堆栈空间(在运行的时候分配)。静态堆栈声明如程序清单 L4.4和4.5所示,这两种声明应放置在函数的外面。

程序清单 L4.4 静态堆栈

static OS_STK MyTaskStack[stack_size]; 或

程序清单 L4.5 静态堆栈

OS_STK MyTaskStack[stack_size];

用户可以用C编译器提供的malloc()函数来动态地分配堆栈空间,如程序清单 L4.6所示。在动态分配中,用户要时刻注意内存碎片问题。特别是当用户反复地建立和删除任务时,内存堆中可能会出现大量的内存碎片,导致没有足够大的一块连续内存区域可用作任务堆栈,这时malloc()便无法成功地为任务分配堆栈空间。

嵌入式实时操作系统uCOSII

程序清单 L L4.6 用malloc()为任务分配堆栈空间

OS_STK *pstk;

pstk = (OS_STK *)malloc(stack_size);

if (pstk != (OS_STK *)0) { /* 确认malloc()能得到足够地内存空间 */ Create the task; }

图4.1表示了一块能被malloc()动态分配的3K字节的内存堆 [F4.1(1)]。为了讨论问题方便,假定用户要建立三个任务(任务A,B和C),每个任务需要1K字节的空间。设第一个1K字节给任务A, 第二个1K字节给任务B, 第三个1K字节给任务

C[F4.1(2)]。然后,用户的应用程序删除任务A和任务C,用free()函数释放内存到内存堆中[F4.1(3)]。现在,用户的内存堆虽有2K字节的自由内存空间,但它是不连续的,所以用户不能建立另一个需要2K字节内存的任务(即任务D)。如果用户并不会去删除任务,使用malloc()是非常可行的。

图 F4.1 内存碎片

µC/OS-Ⅱ支持的处理器的堆栈既可以从上(高地址)往下(低地址)长也可以从下往上长(参看4.02,任务堆栈)。用户在调用OSTaskCreate()或OSTaskCreateExt()的时候必须知道堆栈是怎样长的,因为用户必须得把堆栈的栈顶传递给以上两个函数,当OS_CPU.H文件中的OS_STK_GROWTH置为0时,用户需要将堆栈的最低内存地址传递给任务创建函数,如程序清单4.7所示。

程序清单 L4.7 堆栈从下往上递增

OS_STK TaskStack[TASK_STACK_SIZE];

OSTaskCreate(task, pdata, &TaskStack[0], prio);

嵌入式实时操作系统uCOSII

当OS_CPU.H文件中的OS_STK_GROWTH置为1时,用户需要将堆栈的最高内存地址传递给任务创建函数,如程序清单4.8所示。

程序清单 L4.8 堆栈从上往下递减

OS_STK TaskStack[TASK_STACK_SIZE];

OSTaskCreate(task, pdata, &TaskStack[TASK_STACK_SIZE-1], prio);

这个问题会影响代码的可移植性。如果用户想将代码从支持往下递减堆栈的处理器中移植到支持往上递增堆栈的处理器中的话,用户得使代码同时适应以上两种情况。在这种特殊情况下,程序清单 L4.7和4.8可重新写成如程序清单 L4.9所示的形式。

程序清单 L 4.9 对两个方向增长的堆栈都提供支持

OS_STK TaskStack[TASK_STACK_SIZE];

#if OS_STK_GROWTH == 0

OSTaskCreate(task, pdata, &TaskStack[0], prio); #else

OSTaskCreate(task, pdata, &TaskStack[TASK_STACK_SIZE-1], prio); #endif

任务所需的堆栈的容量是由应用程序指定的。用户在指定堆栈大小的时候必须考虑用户的任务所调用的所有函数的嵌套情况,任务所调用的所有函数会分配的局部变量的数目,以及所有可能的中断服务例程嵌套的堆栈需求。另外,用户的堆栈必须能储存所有的CPU寄存器。

4.3 堆栈检验,OSTaskStkChk()

有时候决定任务实际所需的堆栈空间大小是很有必要的。因为这样用户就可以避免为任务分配过多的堆栈空间,从而减少自己的应用程序代码所需的RAM(内存)数量。µC/OS-Ⅱ提供的OSTaskStkChk()函数可以为用户提供这种有价值的信息。

在图4.2中,笔者假定堆栈是从上往下递减的(即OS_STK_GROWTH被置为1),但以下的讨论也同样适用于从下往上长的堆栈[F4.2(1)]。µC/OS-Ⅱ是通过查看堆栈本身的内容来决定堆栈的方向的。只有内核或是任务发出堆栈检验的命令时,堆栈检验才会被执行,它不会自动地去不断检验任务的堆栈使用情况。在堆栈检验时,µC/OS-Ⅱ要求在任务建立的时候堆栈中存储的必须是0值(即堆栈被清零)[F4.2(2)]。另外,µC/OS-Ⅱ还需要知道堆栈栈底(BOS)的位置和分配给任务的堆栈的大小[F4.2(2)]。在任务建立的时候,BOS的位置及堆栈的这两个值储存在任务的OS_TCB中。

嵌入式实时操作系统uCOSII

为了使用µC/OS-Ⅱ的堆栈检验功能,用户必须要做以下几件事情: 在OS_CFG.H文件中设OS_TASK_CREATE_EXT为1。

用OSTaskCreateExt()建立任务,并给予任务比实际需要更多的内存空间。 在OSTaskCreateExt()中,将参数opt设置为OS_TASK_OPT_STK_CHK+OS_TASK_OPT_STK_

CLR。注意如果用户的程序启动代码清除了所有的RAM,并且从未删除过已建立了的任务,那么用户就不必设置选项OS_TASK_OPT_STK_CLR了。这样就会减少OSTaskCreateExt()的执行时间。

将用户想检验的任务的优先级作为OSTaskStkChk()的参数并调用之。

图 4.2 堆栈检验

OSTaskStkChk()顺着堆栈的栈底开始计算空闲的堆栈空间大小,具体实现方法是统计储存值为0的连续堆栈入口的数目,直到发现储存值不为0的堆栈入口[F4.2(5)]。注意堆栈入口的储存值在进行检验时使用的是堆栈的数据类型(参看OS_CPU.H中的OS_STK)。换句话说,如果堆栈的入口有32位宽,对0值的比较也是按32位完成的。所用的堆栈的空间大小是指从用户在OSTaskCreateExt()中定义的堆栈大小中减去了储存值为0的连续堆栈入口以后的大小。OSTaskStkChk()实际上把空闲堆栈的字节数和已用堆栈的字节数放置在0S_STK_DATA数据结构中(参看µCOS_Ⅱ.H)。注意在某个给定的时间,被检验的任务的堆栈指针可能会指向最初的堆栈栈顶(TOS)与堆栈最深处之间的任何位置[F4.2(7)]。每次在调用OSTaskStkChk()的时候,用户也可能会因为任务还没触及堆栈的最深处而得到不同的堆栈的空闲空间数。 用户应该使自己的应用程序运行足够长的时间,并且经历最坏的堆栈使用情况,这样才能得到正确的数。一旦OSTaskStkChk()提供给用户最坏情况下堆栈的需求,用户就可以重新设置堆栈的最后容量了。为了适应系统以后的升级和扩展,用户应该多分配10%-100%

嵌入式实时操作系统uCOSII

的堆栈空间。在堆栈检验中,用户所得到的只是一个大致的堆栈使用情况,并不能说明堆栈使用的全部实际情况。 OSTaskStkChk()函数的代码如程序清单 L4.10所示。0S_STK_DATA(参看µCOS_Ⅱ.H)数据结构用来保存有关任务堆栈的信息。笔者打算用一个数据结构来达到两个目的。第一,把OSTaskStkChk()当作是查询类型的函数,并且使所有的查询函数用同样的方法返回,即返回查询数据到某个数据结构中。第二,在数据结构中传递数据使得笔者可以在不改变OSTaskStkChk()的API(应用程序编程接口)的条件下为该数据结构增加其它域,从而扩展OSTaskStkChk()的功能。现在,0S_STK_DATA只包含两个域:OSFree和OSUsed。从代码中用户可看到,通过指定执行堆栈检验的任务的优先级可以调用OSTaskStkChk()。如果用户指定0S_PRIO_SELF[L4.10(1)],那么就表明用户想知道当前任务的堆栈信息。当然,前提是任务已经存在[L4.10(2)]。要执行堆栈检验,用户必须已用OSTaskCreateExt()建立了任务并且已经传递了选项OS_TASK_OPT_CHK[L4.10(3)]。如果所有的条件都满足了,OSTaskStkChk()就会象前面描述的那样从堆栈栈底开始统计堆栈的空闲空间[L4.10(4)]。最后,储存在0S_STK_DATA中的信息就被确定下来了[L4.10(5)]。注意函数所确定的是堆栈的实际空闲字节数和已被占用的字节数,而不是堆栈的总字节数。当然,堆栈的实际大小(用字节表示)就是该两项之和。

程序清单 L 4.10 堆栈检验函数

INT8U OSTaskStkChk (INT8U prio, OS_STK_DATA *pdata) {

OS_TCB *ptcb; OS_STK *pchk; INT32U free; INT32U size;

pdata->OSFree = 0; pdata->OSUsed = 0;

if (prio > OS_LOWEST_PRIO && prio != OS_PRIO_SELF) { return (OS_PRIO_INVALID); }

OS_ENTER_CRITICAL();

if (prio == OS_PRIO_SELF) { prio = OSTCBCur->OSTCBPrio; }

ptcb = OSTCBPrioTbl[prio]; if (ptcb == (OS_TCB *)0) { OS_EXIT_CRITICAL();

(2) (1)

嵌入式实时操作系统uCOSII

return (OS_TASK_NOT_EXIST); }

if ((ptcb->OSTCBOpt & OS_TASK_OPT_STK_CHK) == 0) { OS_EXIT_CRITICAL(); return (OS_TASK_OPT_ERR); }

free = 0;

(4)

(3)

size = ptcb->OSTCBStkSize; pchk = ptcb->OSTCBStkBottom; OS_EXIT_CRITICAL(); #if OS_STK_GROWTH == 1 while (*pchk++ == 0) { free++; } #else

while (*pchk-- == 0) { free++; } #endif

pdata->OSFree = free * sizeof(OS_STK);

(5)

pdata->OSUsed = (size - free) * sizeof(OS_STK); return (OS_NO_ERR); }

4.4 删除任务,OSTaskDel()

有时候删除任务是很有必要的。删除任务,是说任务将返回并处于休眠状态(参看3.02,任务状态),并不是说任务的代码被删除了,只是任务的代码不再被µC/OS-Ⅱ调用。通过调用OSTaskDel()就可以完成删除任务的功能(如程序清单 L4.11所示)。OSTaskDel()一开始应确保用户所要删除的任务并非是空闲任务,因为删除空闲任务是不允许的[L4.11(1)]。不过,用户可以删除statistic任务[L4.11(2)]。接着,OSTaskDel()还应确保用户不是在ISR例程中去试图删除一个任务,因为这也是不被允许的[L4.11(3)]。调用此函数的任务可以通过指定OS_PRIO_SELF参数来删除自己[L4.11(4)]。接下来OSTaskDel()会保证被删除的任务是确实存在的[L4.11(3)]。如果指定的参数是OS_PRIO_SELF的话,这一判断过程(任务是否存在)自然是可以通过的,但笔者不准备为这种情况单独写一段代码,因为这样只会增加代码并延长程序的执行时间。

嵌入式实时操作系统uCOSII

程序清单 L 4.11 删除任务

INT8U OSTaskDel (INT8U prio) {

OS_TCB *ptcb; OS_EVENT *pevent;

if (prio == OS_IDLE_PRIO) { return (OS_TASK_DEL_IDLE); }

if (prio >= OS_LOWEST_PRIO && prio != OS_PRIO_SELF) { return (OS_PRIO_INVALID); }

OS_ENTER_CRITICAL(); if (OSIntNesting > 0) { OS_EXIT_CRITICAL(); return (OS_TASK_DEL_ISR); }

if (prio == OS_PRIO_SELF) { Prio = OSTCBCur->OSTCBPrio; }

if ((ptcb = OSTCBPrioTbl[prio]) != (OS_TCB *)0) {

if ((OSRdyTbl[ptcb->OSTCBY] &= ~ptcb->OSTCBBitX) == 0) { OSRdyGrp &= ~ptcb->OSTCBBitY; }

if ((pevent = ptcb->OSTCBEventPtr) != (OS_EVENT *)0) {

(7) (5) (6) (4) (3) (2) (1)

if ((pevent->OSEventTbl[ptcb->OSTCBY] &= ~ptcb->OSTCBBitX) == 0) {

pevent->OSEventGrp &= ~ptcb->OSTCBBitY; } }

Ptcb->OSTCBDly = 0;

Ptcb->OSTCBStat = OS_STAT_RDY; OSLockNesting++; OS_EXIT_CRITICAL(); OSDummy();

(8) (9) (10) (11) (12)

嵌入式实时操作系统uCOSII

OS_ENTER_CRITICAL(); OSLockNesting--; OSTaskDelHook(ptcb); OSTaskCtr--;

OSTCBPrioTbl[prio] = (OS_TCB *)0; if (ptcb->OSTCBPrev == (OS_TCB *)0) {

ptcb->OSTCBNext->OSTCBPrev = (OS_TCB *)0; OSTCBList = ptcb->OSTCBNext; } else {

ptcb->OSTCBPrev->OSTCBNext = ptcb->OSTCBNext; ptcb->OSTCBNext->OSTCBPrev = ptcb->OSTCBPrev; }

ptcb->OSTCBNext = OSTCBFreeList; OSTCBFreeList = ptcb; OS_EXIT_CRITICAL(); OSSched();

return (OS_NO_ERR); } else {

OS_EXIT_CRITICAL(); return (OS_TASK_DEL_ERR); } }

(18) (17) (15) (16) (13) (14)

一旦所有条件都满足了,OS_TCB就会从所有可能的µC/OS-Ⅱ的数据结构中移除。OSTaskDel()分两步完成该移除任务以减少中断响应时间。首先,如果任务处于就绪表中,它会直接被移除[L4.11(6)]。如果任务处于邮箱、消息队列或信号量的等待表中,它就从自己所处的表中被移除[L4.11(7)]。接着,OSTaskDel()将任务的时钟延迟数清零,以确保自己重新允许中断的时候,ISR例程不会使该任务就绪[L4.11(8)]。最后,OSTaskDel()置任务的.OSTCBStat标志为OS_STAT_RDY。注意,OSTaskDel()并不是试图使任务处于就绪状态,而是阻止其它任务或ISR例程让该任务重新开始执行(即避免其它任务或ISR调用OSTaskResume()[L4.11(9)])。这种情况是有可能发生的,因为OSTaskDel()会重新打开中断,而ISR可以让更高优先级的任务处于就绪状态,这就可能会使用户想删除的任务重新开始执行。如果不想置任务的.OSTCBStat标志为OS_STAT_RDY,就只能清除OS_STAT_SUSPEND位了(这样代码可能显得更清楚,更容易理解一些),但这样会使得处理时间稍长一些。 要被删除的任务不会被其它的任务或ISR置于就绪状态,因为该任务已从就绪任务表中删除了,它不是在等待事件的发生,也不是在等待延时期满,不能重新被执行。为了达到删除任务的目的,任务被置于休眠状态。正因为这样,OSTaskDel()必须得阻止任务调度程序[L4.11(10)]在删除过程中切换到其它的任务中去,因为如果当前的任务正在被删除,它不可能被再次调度!接下来,OSTaskDel()重新允许中断以减少中断的响应时间[L4.11(11)]。

嵌入式实时操作系统uCOSII

这样,OSTaskDel()就能处理中断服务了,但由于它增加了OSLockNesting,ISR执行完后会返回到被中断任务,从而继续任务的删除工作。注意OSTaskDel()此时还没有完全完成删除任务的工作,因为它还需要从TCB链中解开OS_TCB,并将OS_TCB返回到空闲OS_TCB表中。 另外需要注意的是,笔者在调用OS_EXIT_CRITICAL()函数后,马上调用了OSDummy() [L4.11(12)],该函数并不会进行任何实质性的工作。这样做只是因为想确保处理器在中断允许的情况下至少执行一个指令。对于许多处理器来说,执行中断允许指令会强制CPU禁止中断直到下个指令结束!Intel 80x86和Zilog Z-80处理器就是如此工作的。开中断后马上关中断就等于从来没开过中断,当然这会增加中断的响应时间。因此调用OSDummy()确保在再次禁止中断之前至少执行了一个调用指令和一个返回指令。当然,用户可以用宏定义将OSDummy()定义为一个空操作指令(译者注:例如MC68HC08指令中的NOP指令),这样调用OSDummy()就等于执行了一个空操作指令,会使OSTaskDel()的执行时间稍微缩短一点。但笔者认为这种宏定义是没价值的,因为它会增加移植µCOS-Ⅱ的工作量。 现在,OSTaskDel()可以继续执行删除任务的操作了。在OSTaskDel()重新关中断后,它通过锁定嵌套计数器(OSLockNesting)减一以重新允许任务调度[L4.11(13)]。接着,OSTaskDel()调用用户自定义的OSTaskDelHook()函数[L4.11(14)],用户可以在这里删除或释放自定义的TCB附加数据域。然后,OSTaskDel()减少µCOS-Ⅱ的任务计数器。OSTaskDel()简单地将指向被删除的任务的OS_TCB的指针指向NULL[L4.11(15)],从而达到将OS_TCB从优先级表中移除的目的。再接着,OSTaskDel()将被删除的任务的OS_TCB从OS_TCB双向链表中移除[L4.11(16)]。注意,没有必要检验ptcb->OSTCBNext==0的情况,因为OSTaskDel()不能删除空闲任务,而空闲任务就处于链表的末端(ptcb->OSTCBNext==0)。接下来,OS_TCB返回到空闲OS_TCB表中,并允许其它任务的建立[L4.11(17)]。最后,调用任务调度程序来查看在OSTaskDel()重新允许中断的时候[L4.11(11)],中断服务子程序是否曾使更高优先级的任务处于就绪状态[L4.11(18)]。

4.5 请求删除任务,OSTaskDelReq()

有时候,如果任务A拥有内存缓冲区或信号量之类的资源,而任务B想删除该任务,这些资源就可能由于没被释放而丢失。在这种情况下,用户可以想法子让拥有这些资源的任务在使用完资源后,先释放资源,再删除自己。用户可以通过OSTaskDelReq()函数来完成该功能。

发出删除任务请求的任务(任务B)和要删除的任务(任务A)都需要调用OSTaskDelReq()函数。任务B的代码如程序清单 L4.12所示。任务B需要决定在怎样的情况下请求删除任务[L4.12(1)]。换句话说,用户的应用程序需要决定在什么样的情况下删除任务。如果任务需要被删除,可以通过传递被删除任务的优先级来调用OSTaskDelReq()[L4.12(2)]。如果要被删除的任务不存在(即任务已被删除或是还没被建立),OSTaskDelReq()返回OS_TASK_NOT_EXIST。如果OSTaskDelReq()的返回值为OS_NO_ERR,则表明请求已被接受但任务还没被删除。用户可能希望任务B等到任务A删除了自己以后才继续进行下面的工作,这时用户可以象笔者一样,通过让任务B延时一定时间来达到这个目的[L4.12(3)]。笔者延时了一个时钟节拍。如果需要,用户可以延时得更长一些。当任务A完全删除自己后,[L4.12(2)]中的返回值成为0S_TASK_NOT_EXIST,此时循环结束[L4.12(4)]。

程序清单 L 4.12 请求删除其它任务的任务(任务B)

void RequestorTask (void *pdata)

嵌入式实时操作系统uCOSII

INT8U err;

pdata = pdata; for (;;) {

/* 应用程序代码 */

if ('TaskToBeDeleted()' 需要被删除) {

(1)

while (OSTaskDelReq(TASK_TO_DEL_PRIO) != OS_TASK_NOT_EXIST) {

OSTimeDly(1); } }

/*应用程序代码*/ } }

(4)

(2)

(3)

程序清单 L 4.13 需要删除自己的任务(任务A)

void TaskToBeDeleted (void *pdata) {

INT8U err;

pdata = pdata; for (;;) {

/*应用程序代码*/

If (OSTaskDelReq(OS_PRIO_SELF) == OS_TASK_DEL_REQ) { 释放所有占用的资源; 释放所有动态内存;

OSTaskDel(OS_PRIO_SELF); (3) } else {

/*应用程序代码*/ } }

(1)

(2)

嵌入式实时操作系统uCOSII

需要删除自己的任务(任务A)的代码如程序清单 L4.13所示。在OS_TAB中存有一个标志,任务通过查询这个标志的值来确认自己是否需要被删除。这个标志的值是通过调用OSTaskDelReq(OS_PRIO_SELF)而得到的。当OSTaskDelReq()返回给调用者OS_TASK_DEL_REQ[L4.13(1)]时,则表明已经有另外的任务请求该任务被删除了。在这种情况下,被删除的任务会释放它所拥有的所用资源[L4.13(2)],并且调用OSTaskDel(OS_PRIO_SELF)来删除自己[L4.13(3)]。前面曾提到过,任务的代码没有被真正的删除,而只是µC/OS-Ⅱ不再理会该任务代码,换句话说,就是任务的代码不会再运行了。但是,用户可以通过调用OSTaskCreate()或OSTaskCreateExt()函数重新建立该任务。 OSTaskDelReq()的代码如程序清单 L4.14所示。通常OSTaskDelReq()需要检查临界条件。首先,如果正在删除的任务是空闲任务,OSTaskDelReq()会报错并返回[L4.14(1)]。接着,它要保证调用者请求删除的任务的优先级是有效的[L4.14(2)]。如果调用者就是被删除任务本身,存储在OS_TCB中的标志将会作为返回值[L4.14(3)]。如果用户用优先级而不是OS_PRIO_SELF指定任务,并且任务是存在的[L4.14(4)],OSTaskDelReq()就会设置任务的内部标志[L4.14(5)]。如果任务不存在,OSTaskDelReq()则会返回OS_TASK_NOT_EXIST,表明任务可能已经删除自己了[L4.14(6)]。

程序清单 L 4.14 OSTaskDelReq().

INT8U OSTaskDelReq (INT8U prio) {

BOOLEAN stat; INT8U err; OS_TCB *ptcb;

if (prio == OS_IDLE_PRIO) { (1) return (OS_TASK_DEL_IDLE); }

if (prio >= OS_LOWEST_PRIO && prio != OS_PRIO_SELF) { (2)

return (OS_PRIO_INVALID); }

if (prio == OS_PRIO_SELF) { (3) OS_ENTER_CRITICAL();

stat = OSTCBCur->OSTCBDelReq; OS_EXIT_CRITICAL(); return (stat);

嵌入式实时操作系统uCOSII

} else {

OS_ENTER_CRITICAL();

if ((ptcb = OSTCBPrioTbl[prio]) != (OS_TCB *)0) { (4)

ptcb->OSTCBDelReq = OS_TASK_DEL_REQ; (5) err = OS_NO_ERR; } else {

err = OS_TASK_NOT_EXIST; (6) }

OS_EXIT_CRITICAL(); return (err); } }

4.6 改变任务的优先级,OSTaskChangePrio()

在用户建立任务的时候会分配给任务一个优先级。在程序运行期间,用户可以通过调用OSTaskChangePrio()来改变任务的优先级。换句话说,就是µC/OS-Ⅱ允许用户动态的改变任务的优先级。 OSTaskChangePrio()的代码如程序清单 L4.15所示。用户不能改变空闲任务的优先级[L4.15(1)],但用户可以改变调用本函数的任务或者其它任务的优先级。为了改变调用本函数的任务的优先级,用户可以指定该任务当前的优先级或OS_PRIO_SELF,OSTaskChangePrio()会决定该任务的优先级。用户还必须指定任务的新(即想要的)优先级。因为µC/OS-Ⅱ不允许多个任务具有相同的优先级,所以OSTaskChangePrio()需要检验新优先级是否是合法的(即不存在具有新优先级的任务)[L4.15(2)]。如果新优先级是合法的,µC/OS-Ⅱ通过将某些东西储存到OSTCBPrioTbl[newprio]中保留这个优先级[L4.15(3)]。如此就使得OSTaskChangePrio()可以重新允许中断,因为此时其它任务已经不可能建立拥有该优先级的任务,也不能通过指定相同的新优先级来调用OSTaskChangePrio()。接下来OSTaskChangePrio()可以预先计算新优先级任务的OS_TCB中的某些值[L4.15(4)]。而这些值用来将任务放入就绪表或从该表中移除(参看3.04,就绪表)。

接着,OSTaskChangePrio()检验目前的任务是否想改变它的优先级[L4.15(5)]。然后,OSTaskChangePrio()检查想要改变优先级的任务是否存在[L4.15(6)]。很明显,如果要改变优先级的任务就是当前任务,这个测试就会成功。但是,如果OSTaskChangePrio()想要改变优先级的任务不存在,它必须将保留的新优先级放回到优先级表OSTCBPrioTbl[]中[L4.15(17)],并返回给调用者一个错误码。 现在,OSTaskChangePrio()可以通过插入NULL指针将指向当前任务OS_TCB的指针从优先级表中移除了[L4.15(7)]。这就使得当前任务的旧的优先级可以重新使用了。接着,我们检验一下OSTaskChangePrio()想要改变优先级的任务是否就绪[L4.15(8)]。如果该任务处于就绪状态,它必须在当前的优先级下从就绪表中移除[L4.15(9)],然后在新的优先级下插

本文来源:https://www.bwwdw.com/article/3kd4.html

Top