小盒子的小盒

UC/OS-II-4.0

0
阅读(3726)

第4章      任务管理... 1

4.0         建立任务,OSTaskCreate() 2
4.1         建立任务,OSTaskCreateExt() 6
4.2         任务堆栈... 9
4.3         堆栈检验,OSTaskStkChk() 11
4.4         删除任务,OSTaskDel() 14
4.5         请求删除任务,OSTaskDelReq() 17
4.6         改变任务的优先级,OSTaskChangePrio() 20
4.7         挂起任务,OSTaskSuspend() 23
4.8         恢复任务,OSTaskResume() 25
4.9         获得有关任务的信息,OSTaskQuery() 26
 

第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-Ⅱ版本中,任务的优先级数也可作为任务的标识符使用。
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) {                                             (1)
 
        return (OS_PRIO_INVALID);
 
    }
 
    OS_ENTER_CRITICAL();
 
    if (OSTCBPrioTbl[prio] == (OS_TCB *)0) {                                (2)
 
        OSTCBPrioTbl[prio] = (OS_TCB *)1;                                    (3)
 
        OS_EXIT_CRITICAL();                                                    (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) {                                               (7)
 
            OS_ENTER_CRITICAL();
 
            OSTaskCtr++;                                                        (8)
 
            OSTaskCreateHook(OSTCBPrioTbl[prio]);                           (9)
 
            OS_EXIT_CRITICAL();
 
            if (OSRunning) {                                                  (10)
 
                OSSched();                                                     (11)
 
            }
 
        } else {
 
            OS_ENTER_CRITICAL();
 
            OSTCBPrioTbl[prio] = (OS_TCB *)0;                              (12)
 
            OS_EXIT_CRITICAL();
 
        }
 
        return (err);
 
    } else {
 
        OS_EXIT_CRITICAL();
 
        return (OS_PRIO_EXIST);
 
    }
 
}
 
 
    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的数据单元。
 
程序清单 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;                                                   (1)
 
    if (ptcb != (OS_TCB *)0) {                                              (2)
 
        OSTCBFreeList        = ptcb->OSTCBNext;
 
        OS_EXIT_CRITICAL();
 
        ptcb->OSTCBStkPtr    = ptos;                                        (3)
 
        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
 
 
 
#if OS_TASK_DEL_EN
 
        ptcb->OSTCBDelReq    = OS_NO_ERR;
 
#endif
 
 
 
        ptcb->OSTCBY         = prio >> 3;
 
        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();                                                (4)
 
        OSTCBPrioTbl[prio]   = ptcb;                                        (5)
 
        ptcb->OSTCBNext      = OSTCBList;
 
        ptcb->OSTCBPrev      = (OS_TCB *)0;
 
        if (OSTCBList != (OS_TCB *)0) {
 
            OSTCBList->OSTCBPrev = ptcb;
 
        }
 
        OSTCBList               = ptcb;
 
        OSRdyGrp               |= ptcb->OSTCBBitY;                         (6)
 
        OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;
 
        OS_EXIT_CRITICAL();
 
        return (OS_NO_ERR);                                                  (7)
 
    } else {
 
        OS_EXIT_CRITICAL();
 
        return (OS_NO_MORE_TCB);
 
    }
 
}
 
 
    当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]的入口为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,
 
                       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) {                                             (1)
 
        return (OS_PRIO_INVALID);
 
    }
 
    OS_ENTER_CRITICAL();
 
    if (OSTCBPrioTbl[prio] == (OS_TCB *)0) {                               (2)
 
        OSTCBPrioTbl[prio] = (OS_TCB *)1;                                   (3)
 
        OS_EXIT_CRITICAL();                                                   (4)
 
 
 
        if (opt & OS_TASK_OPT_STK_CHK) {                                    (5)
 
            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)
 
        if (err == OS_NO_ERR) {                                               (8)
 
            OS_ENTER_CRITICAL;
 
            OSTaskCtr++;                                                       (9)
 
            OSTaskCreateHook(OSTCBPrioTbl[prio]);                          (10)
 
            OS_EXIT_CRITICAL();
 
            if (OSRunning) {                                                  (11)
 
                OSSched();                                                    (12)
 
            }
 
        } else {
 
            OS_ENTER_CRITICAL();
 
            OSTCBPrioTbl[prio] = (OS_TCB *)0;                              (13)
 
            OS_EXIT_CRITICAL();
 
        }
 
        return (err);
 
    } else {
 
        OS_EXIT_CRITICAL();
 
        return (OS_PRIO_EXIST);
 
    }
 
}
 
 
    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),因为用户必须得把堆栈的栈顶传递给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()便无法成功地为任务分配堆栈空间。
 
程序清单 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);
 
 
    当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中。
    为了使用µC/OS-Ⅱ的堆栈检验功能,用户必须要做以下几件事情:
l         在OS_CFG.H文件中设OS_TASK_CREATE_EXT为1。
l         用OSTaskCreateExt()建立任务,并给予任务比实际需要更多的内存空间。
l         在OSTaskCreateExt()中,将参数opt设置为OS_TASK_OPT_STK_CHK+OS_TASK_OPT_STK_
CLR。注意如果用户的程序启动代码清除了所有的RAM,并且从未删除过已建立了的任务,那么用户就不必设置选项OS_TASK_OPT_STK_CLR了。这样就会减少OSTaskCreateExt()的执行时间。
l         将用户想检验的任务的优先级作为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%的堆栈空间。在堆栈检验中,用户所得到的只是一个大致的堆栈使用情况,并不能说明堆栈使用的全部实际情况。
    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) {                                              (1)
 
        prio = OSTCBCur->OSTCBPrio;
 
    }
 
    ptcb = OSTCBPrioTbl[prio];
 
    if (ptcb == (OS_TCB *)0) {                                               (2)
 
        OS_EXIT_CRITICAL();
 
        return (OS_TASK_NOT_EXIST);
 
    }
 
    if ((ptcb->OSTCBOpt & OS_TASK_OPT_STK_CHK) == 0) {                   (3)
 
        OS_EXIT_CRITICAL();
 
        return (OS_TASK_OPT_ERR);
 
    }
 
    free = 0;                                                                  (4)
 
    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的话,这一判断过程(任务是否存在)自然是可以通过的,但笔者不准备为这种情况单独写一段代码,因为这样只会增加代码并延长程序的执行时间。