zoukankan      html  css  js  c++  java
  • 手把手,嘴对嘴,讲解UCOSII嵌入式操作系统的任务(二)

    本章重点讲解空闲任务的建立过程

    任务建立函数定义如下:

     1 INT8U  OSTaskCreate (void   (*task)(void *p_arg),
     2                      void    *p_arg,
     3                      OS_STK  *ptos,
     4                      INT8U    prio)
     5 {
     6     OS_STK    *psp;
     7     INT8U      err;
     8 #if OS_CRITICAL_METHOD == 3u                 /* Allocate storage for CPU status register               */
     9     OS_CPU_SR  cpu_sr = 0u;
    10 #endif
    11 
    12 
    13 
    14 #ifdef OS_SAFETY_CRITICAL_IEC61508
    15     if (OSSafetyCriticalStartFlag == OS_TRUE) {
    16         OS_SAFETY_CRITICAL_EXCEPTION();
    17     }
    18 #endif
    19 
    20 #if OS_ARG_CHK_EN > 0u
    21     if (prio > OS_LOWEST_PRIO) {             /* Make sure priority is within allowable range           */
    22         return (OS_ERR_PRIO_INVALID);
    23     }
    24 #endif
    25     OS_ENTER_CRITICAL();
    26     if (OSIntNesting > 0u) {                 /* Make sure we don't create the task from within an ISR  */
    27         OS_EXIT_CRITICAL();
    28         return (OS_ERR_TASK_CREATE_ISR);
    29     }
    30     if (OSTCBPrioTbl[prio] == (OS_TCB *)0) { /* Make sure task doesn't already exist at this priority  */
    31         OSTCBPrioTbl[prio] = OS_TCB_RESERVED;/* Reserve the priority to prevent others from doing ...  */
    32                                              /* ... the same thing until task is created.              */
    33         OS_EXIT_CRITICAL();
    34         psp = OSTaskStkInit(task, p_arg, ptos, 0u);             /* Initialize the task's stack         */
    35         err = OS_TCBInit(prio, psp, (OS_STK *)0, 0u, 0u, (void *)0, 0u);
    36         if (err == OS_ERR_NONE) {
    37             if (OSRunning == OS_TRUE) {      /* Find highest priority task if multitasking has started */
    38                 OS_Sched();
    39             }
    40         } else {
    41             OS_ENTER_CRITICAL();
    42             OSTCBPrioTbl[prio] = (OS_TCB *)0;/* Make this priority available to others                 */
    43             OS_EXIT_CRITICAL();
    44         }
    45         return (err);
    46     }
    47     OS_EXIT_CRITICAL();
    48     return (OS_ERR_PRIO_EXIST);
    49 }

    21~23行,判断我们传递进来的参数优先级是否合法,如果不满足,直接退出(当前系统支持最大64个任务,因此优先级必须小于64)。

    26~29行,判断当前系统的中断状态,变量OSIntNesting的意义之前讲过,如果它大于0,那就代表目前处于中断服务程序中,在中断中系统是不允许建立新任务的。

    30行,首先判断任务是否已经存在,如果已经存在则跳出,数组OSTCBPrioTbl[prio]的下标是优先级,内容是任务的相关的信息,如果该优先级的任务已建立,那么其内容必然不等于0.

    31行,当任务不存在时,随便赋个值给这个任务的管理数组,相当于是在这个元素上做个标志,告诉系统,这个位置要保留下来不允许别人动,直到这个任务真正建立成功。

    34行所调用的函数是初始化任务的堆栈,我们现在详细看看内部的处理,到底是如何进行初始化的。

    栈空间

    函数OSTaskStkInit定义如下:

     1 OS_STK *OSTaskStkInit (void (*task)(void *p_arg), void *p_arg, OS_STK *ptos, INT16U opt)
     2 {
     3     OS_STK *stk;
     4 
     5 
     6     (void)opt;                                   /* 'opt' is not used, prevent warning                 */
     7     stk       = ptos;                            /* Load stack pointer                                 */
     8 
     9                                                  /* Registers stacked as if auto-saved on exception    */
    10     *(stk)    = (INT32U)0x01000000L;             /* xPSR                                               */
    11     *(--stk)  = (INT32U)task;                    /* Entry Point                                        */
    12     *(--stk)  = (INT32U)0xFFFFFFFEL;             /* R14 (LR) (init value will cause fault if ever used)*/
    13     *(--stk)  = (INT32U)0x12121212L;             /* R12                                                */
    14     *(--stk)  = (INT32U)0x03030303L;             /* R3                                                 */
    15     *(--stk)  = (INT32U)0x02020202L;             /* R2                                                 */
    16     *(--stk)  = (INT32U)0x01010101L;             /* R1                                                 */
    17     *(--stk)  = (INT32U)p_arg;                   /* R0 : argument                                      */
    18 
    19                                                  /* Remaining registers saved on process stack         */
    20     *(--stk)  = (INT32U)0x11111111L;             /* R11                                                */
    21     *(--stk)  = (INT32U)0x10101010L;             /* R10                                                */
    22     *(--stk)  = (INT32U)0x09090909L;             /* R9                                                 */
    23     *(--stk)  = (INT32U)0x08080808L;             /* R8                                                 */
    24     *(--stk)  = (INT32U)0x07070707L;             /* R7                                                 */
    25     *(--stk)  = (INT32U)0x06060606L;             /* R6                                                 */
    26     *(--stk)  = (INT32U)0x05050505L;             /* R5                                                 */
    27     *(--stk)  = (INT32U)0x04040404L;             /* R4                                                 */
    28 
    29     return (stk);
    30 }

    这个函数该怎么解释呢?

    它其实并不是真正的去初始化了CPU的栈空间,只是对一个模拟的栈进行了初始化。

    做过单片机程序的同学都很清楚,在程运行中总遇到中断发生,当这个时候就必须要跳进中断中去运行,但在跳进中断服务函数之前,我们必须要做一些处理,比如保存现场,将当前的数据和寄存器值押入栈中,然后在去执行中断函数,因为只有这样做了,在执行完中断函数以后才能找回原点继续执行剩下的代码(汇编语言需要自己压栈和出栈,C语言由系统自动完成)。

    单片机裸机程序是单线程,代码的执行逻辑始终处于一条时间线,就算是发生了中断嵌套,但在某一个具体的时间点,它也是单线的,因此只需要保存一个现场就可以回到原点。

    但在嵌入式操作系统中同时执行了多个任务,每个任务之间都有可能发生切换,任务与任务之间不存在调用的关系,是相对独立的存在,任务的这个切换可以理解为单片机中发生的中断,而且切换的顺序并不是线性的,一旦在任务过多的情况下,如果依然使用CPU自己的栈空间,那这样就会导致栈空间不够用,栈溢出,而且而且非常不方便。

    所以,UCOSII系统对于每一个任务都创建了一个数组来模拟它的栈空间,比如这个空闲任务,我们跟踪它的参数可以知道:

    OS_EXT  OS_STK            OSTaskIdleStk[OS_TASK_IDLE_STK_SIZE];      /* Idle task stack                */
    #define OS_TASK_IDLE_STK_SIZE   128u   /* Idle       task stack size (# of OS_STK wide entries)        */

    系统定义好了一个128长度的数组来模拟空闲任务的栈空间,专门用来存在和这个任务有关的寄存器数据,当我们从空闲任务跳出去的时候,就会把相应的信息保存在里面,等需要跳回的时候,就会从里面取出相应的信息,功能和CPU的栈空间完全一样。

    我们可以用keil仿真一下。

    首先在下面进入栈初始化之前打上断点:

     

     这个时候我们可以看看空闲任务的栈数组,也就是OSTaskIdleStk[],他此刻的状态如下:

    这个数组的长度是128个字节,我不可能全部截完,不过它其中的内容都如上所示,全部是0x0000。

    现在我们在函数的出口后打上断点,然后全速运行,观察一下空闲任务的栈初始化完成以后是什么样子的,代码如下:

    栈空间初始化完后结果如下:

     

    我们能看出来有些空间被填充了,由于M3的栈空间是由高往低增长,因此数组的最后几个字节被填充上的内容,其中第126个元素保存的是空闲任务的任务函数地址,第120个元素保存的传递进来的参数,剩下的那些立即数是模拟的CPU的寄存器,至于那些立即数的内容不用关心,基本是用于开发人员调试用的。

    在我们以前进行单片机C语言的编程中,在一个函数被调用之前,它的堆栈信息的初始化,压栈,出栈都是mcu自行进行的,不需要工程师干预,而在UCOSII系统中,由于MCU的栈空间只有一个,而我们的任务又太多,因此便需要由人工来进行管理,实际上OSTaskStkInit函数便是模拟了这种动作。

    任务信息

    空闲任务的栈空间初始化完成以后,接下来便需要对任务本身的一些信息进行处理,请接着看下一个函数:OS_TCBInit()

            err = OS_TCBInit(prio, psp, (OS_STK *)0, 0u, 0u, (void *)0, 0u);

    其简化后,定义如下:

     1 INT8U  OS_TCBInit (INT8U    prio,
     2                    OS_STK  *ptos,
     3                    OS_STK  *pbos,
     4                    INT16U   id,
     5                    INT32U   stk_size,
     6                    void    *pext,
     7                    INT16U   opt)
     8 {
     9     OS_TCB    *ptcb;
    10 #if OS_CRITICAL_METHOD == 3u                               /* Allocate storage for CPU status register */
    11     OS_CPU_SR  cpu_sr = 0u;
    12 #endif
    13 #if OS_TASK_REG_TBL_SIZE > 0u
    14     INT8U      i;
    15 #endif
    16 
    17 
    18     OS_ENTER_CRITICAL();
    19     ptcb = OSTCBFreeList;                                  /* Get a free TCB from the free TCB list    */
    20     if (ptcb != (OS_TCB *)0) {
    21         OSTCBFreeList            = ptcb->OSTCBNext;        /* Update pointer to free TCB list          */
    22         OS_EXIT_CRITICAL();
    23         ptcb->OSTCBStkPtr        = ptos;                   /* Load Stack pointer in TCB                */
    24         ptcb->OSTCBPrio          = prio;                   /* Load task priority into TCB              */
    25         ptcb->OSTCBStat          = OS_STAT_RDY;            /* Task is ready to run                     */
    26         ptcb->OSTCBStatPend      = OS_STAT_PEND_OK;        /* Clear pend status                        */
    27         ptcb->OSTCBDly           = 0u;                     /* Task is not delayed                      */
    28 
    30         pext                     = pext;                   /* Prevent compiler warning if not used     */
    31         stk_size                 = stk_size;
    32         pbos                     = pbos;
    33         opt                      = opt;
    34         id                       = id;
    35 
    36 #if OS_TASK_DEL_EN > 0u
    37         ptcb->OSTCBDelReq        = OS_ERR_NONE;
    38 #endif
    39 
    40         ptcb->OSTCBY             = (INT8U)(prio >> 3u);
    41         ptcb->OSTCBX             = (INT8U)(prio & 0x07u);
    42 
    43         ptcb->OSTCBBitY          = (OS_PRIO)(1uL << ptcb->OSTCBY);
    44         ptcb->OSTCBBitX          = (OS_PRIO)(1uL << ptcb->OSTCBX);
    45 
    46 #if OS_TASK_REG_TBL_SIZE > 0u                              /* Initialize the task variables            */
    47         for (i = 0u; i < OS_TASK_REG_TBL_SIZE; i++) {
    48             ptcb->OSTCBRegTbl[i] = 0u;
    49         }
    50 #endif
    51 
    52         OSTCBInitHook(ptcb);
    53 
    54         OSTaskCreateHook(ptcb);                            /* Call user defined hook                   */
    55 
    56         OS_ENTER_CRITICAL();
    57         OSTCBPrioTbl[prio] = ptcb;
    58         ptcb->OSTCBNext    = OSTCBList;                    /* Link into TCB chain                      */
    59         ptcb->OSTCBPrev    = (OS_TCB *)0;
    60         if (OSTCBList != (OS_TCB *)0) {
    61             OSTCBList->OSTCBPrev = ptcb;
    62         }
    63         OSTCBList               = ptcb;
    64         OSRdyGrp               |= ptcb->OSTCBBitY;         /* Make task ready to run                   */
    65         OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;
    66         OSTaskCtr++;                                       /* Increment the #tasks counter             */
    67         OS_EXIT_CRITICAL();
    68         return (OS_ERR_NONE);
    69     }
    70     OS_EXIT_CRITICAL();
    71     return (OS_ERR_TASK_NO_MORE_TCB);
    72 }

    第19行,ptcb只是一个局部指针,暂时还没有指向任何地址,因此首先需要从刚才建立的空闲链表中取出一个实际地址。

    第20行,判断该地址是否有效(不等于0)。

    第21行,由于我们从空闲链表取出了一块地址,那么这个空闲链表的首地址就不能用了,所以需要把它的首地址指向下一个位置,以便今后其他任务的建立。

    第23~27行,这几句话是设定任务的参数,比如栈地址,优先级,任务状态,延时参数等等。

    第40~44行,这几句话和优先级策略以及今后的任务切换有关,详细可参考:http://www.cnblogs.com/han-bing/p/8882375.html  

    第52~53行,这是钩子函数的调用,无需多言。

    第57行,这是将已经初始化完成的任务链表TCB赋值给全局的任务数组,这个数组一共有64个元素,其中是按照优先级从高到低来排序。

    第58~59行,更新任务链表的下一个元素和上一个元素,也就是把新建的这个任务块,链接到总的任务控制链接里面。

    第60~62行,判断当前任务链表的状态(由于空闲任务是第一个创建的任务,因此任务控制链表还是空的,上一个链接自然也不存在,所以并不会执行if判断里面代码,ptcb->OSTCBPrev被赋值为0)。

    第63行,把我们新建的这个局部任务块,链接到总任务链表里面,从这时开始,OSTCBList就不在是空的了,里面有了第一个成员。

    第64~65行,这两句话是任务优先级的管理表更新,详细可参考:http://www.cnblogs.com/han-bing/p/8882375.html  

    第66行,任务个数管理变量加1,代表系统建立了多少个任务。

    ----------------------------------------------------------------------------------------------------------------------

    任务建立的过程总体而言不算简单也不算复杂,如果你已经掌握了链表等数据结构的知识,那么对于这段应该很容易掌握,不过若是以前对链表之类的完全不了解,那就需要多花费一点时间了。

    对于新手而言,上面出现的各式各样的链表和数组可能看起来似乎有些眼花缭乱,觉得UCOSII系统太耗费资源,那么大的结构体作成一个64个元素的数组,而且还有好几个……其实,链表和数组不同,不管所管理的空间有多大,链表并不会占据实际的数据结构内存(当然,指针本身还是要占一点内存的,只不过仅仅是一个指针的地址,并不会占据数据结构那么大的内存),别看我们上面提到了那么多种链表,但不管是任务管理链表(OSTCBList),空闲链表(OSTCBFreeList)其实他们管理的都是同一段内存,也就是数组OSTCBTbl[OS_MAX_TASKS + OS_N_SYS_TASKS]。

    就像一条街道拥有的不同叫法,老年人叫“爱国路”,年轻人叫“欢乐路”,小朋友叫“玩耍路”……他们所知的都是同一条街道,只是名字不同罢了。

    如果不明白,请看看代码中的定义:

    1 OS_EXT  OS_TCB           *OSTCBCur;                        /* Pointer to currently running TCB         */
    2 OS_EXT  OS_TCB           *OSTCBFreeList;                   /* Pointer to list of free TCBs             */
    3 OS_EXT  OS_TCB           *OSTCBHighRdy;                    /* Pointer to highest priority TCB R-to-R   */
    4 OS_EXT  OS_TCB           *OSTCBList;                       /* Pointer to doubly linked list of TCBs    */
    5 OS_EXT  OS_TCB           *OSTCBPrioTbl[OS_LOWEST_PRIO + 1u];    /* Table of pointers to created TCBs   */
    6 OS_EXT  OS_TCB            OSTCBTbl[OS_MAX_TASKS + OS_N_SYS_TASKS];   /* Table of TCBs                  */

    前面5个数据定义的类型都是指向别的数据的指针,只有最后一个数组定义的才是占据实际内存的数据,UCOSII系统中所谓的任务管理,其实也就是把这几个链表指向的地址根据需要反复的修改,但是永远限定在OSTCBTbl数组中,就像孙猴子无论怎么飞永远也逃不出如来佛的手掌心,这样从而达到管理任务的目的。

    当空闲任务未建立之前,我们看看具体的数据结构:

     

    首先是那个数组:OSTCBTbl

      

    这是它的内存分部状态,它是实际占据内存的数据。

    在系统刚刚初始化开始,还没有新建任何任务的时候,我们用这个数组和空闲链表做一个比较。

                               数组:OSTCBTbl                                                                                   空闲链表:OSTCBFreeList

         

    由以上两张地址信息对比,可以看出,空闲链表中指向下一个结构的地址,其实就是数组元素的地址,因此它管理的是数组元素。

     这个时候(没有任何任务建立,包括空闲任务),别的链表都是空:

    当空闲任务建立之后,我们再看看具体的数据结构:

    首先是那个数组:OSTCBTbl

       

     这个数组的内容当然是不会变的,不管任务是否建立,不管任务是否存在,它元素的地址永远都不会变。

    在系统建完成空闲任务后,我们用这个数组和空闲链表做一个比较。

                        数组:OSTCBTbl                                                                                                                  空闲链表:OSTCBFreeList

     

    两张图一对比便不难发现,空闲链表的数据内容发生了变化,具体便是,它所有的地址都往前缩进了一位,数组OSTCBTbl的首地址:0x20000A48,已经无法在空闲链表中找到了,空闲链表的首地址变成了,数组的第二个元素的地址。

    此时此刻空闲任务已经建立完成,因此,空闲链表最开始的那个地址已经不空闲了,名花有主了,所以它的首地址只能指向像一个还没有被分配出去的地址,如果我们再建立一个任务,那么空闲链表的首地址还会继续向上缩进一个,因为它所指向的,必须是这个数组中还没有被分配出去的那段空间。

    我们再继续看看别的链表。
    比如说任务管理链表的状态:

                        数组:OSTCBTbl                                                                                                                                                             任务管理链表:OSTCBList

           

     因为我们已经建立了一个任务,因此任务管理链表的首地址,自然就指向了那个仅存的空闲任务,相当于从空闲链表中拿出去,放进了任务管理链表中,每建立一个任务都是如此,不停的从空闲链表中拿出去,然后放进任务管理链表,任务的删除便是反向操作,从任务管理链表中拿出去,放进空闲链表。

     至于别的两个链表,此刻依然是空,因为虽然建立了一个任务,但这个任务还没跑起来,等它跑起来后这两个链表里肯定就不是空了。

     优先级链表:OSTCBPrioTbl

    由于空闲任务的建立,优先级管理链表中也有数据了,由于空闲任务的优先级是63,所以按照它的排序规则,放进最后一个元素中,可以看到它的地址正是那个空闲任务的地址,这也就意味着,他们管理的是同一段数据。

    小结:在UCOSII中,系统所占据的内存其实并不大,虽然里面的各种链表比较多,但都管理的同一段内存,只是根据需要和意义赋予了不同的名字。

    铁打的数组,流水的链表,数组就像一棵巍然不动的千年老松,任凭风吹雨打,那些链表就像一堆迎风就倒的墙头小草,它们所管理的范围是变动的。

    任务管理其实也很简单,它的本质,就是对任务数组的操作,增加、删除、排序……等等。

    任务数组:OSTCBTbl,它里面是按照任务建立的时间来存放任务。

    优先级数组:OSTCBPrioTbl,它里面是按照任务的优先级,对任务数组进行了一次排序。

    因此,空闲任务的建立过程,首先是把空闲任务的信息放进数组OSTCBTbl的第1个位置(建立时间最早),再把空闲任务的信息放进数组OSTCBPrioTbl的最后一个位置(优先级最低)。

    待续……

  • 相关阅读:
    【XSY2720】区间第k小 整体二分 可持久化线段树
    【XSY2719】prime 莫比乌斯反演
    【XSY2718】gift 分数规划 网络流
    【CTSC2017】【BZOJ4903】吉夫特 卢卡斯定理 DP
    【XSY2729】欧拉子图 无向图连通性 数学
    【XSY2730】Ball 多项式exp 多项式ln 多项式开根 常系数线性递推 DP
    【BZOJ1999】【NOIP2007】树网的核 单调队列优化DP
    NOIP2017游记
    【BZOJ2127】happiness 网络流
    【BZOJ3625】【CF438E】小朋友和二叉树 NTT 生成函数 多项式开根 多项式求逆
  • 原文地址:https://www.cnblogs.com/han-bing/p/9025847.html
Copyright © 2011-2022 走看看