uCos的多任务实现

uCos的多任务实现

作为操作系统(OS),最基本的一项服务就是提供多线程,在实时操作系统uCos里,多线程被称为多任务(Task)。多任务并不是CPU能真正同时运行多个程序,实际是靠CPU在多个任务之间转换切换实现的,CPU轮番的服务于一系列的任务,这样CPU在宏观上好像在同时执行多个任务,实际在微观上CPU绝对是“单任务”的。这里要注意区别多线程和多核,如果系统里是有多个CPU,则可以实现真正的多线程了。

按照上面的思路,多任务的实现,就是要实现CPU在不同的任务之间切换。按照uCos作者的话说:“就是不断的保存,恢复CPU的那些寄存器”。

我们知道uCos的多任务(这里以两个任务为例)的程序模型如下:

void  Task_A(void *p_arg)

{

   while(1)

   {    

        OSTimeDly(10);  //任务里必须有类似的主动释放CPU的函数

......       

   }

}

 

void  Task_B(void *p_arg)

{

   while(1)

   {    

        OSTimeDly(10);           

       ......

        }

}

使用uCos,程序将在这两个任务之间轮换,这两个while(1)里的程序都可以得到执行。

我们知道,在裸机编程里,如果出现while(1)这样的语句,那么程序将永远在这个循环里执行(当然是要程序主动调用break除外),他是不会放弃CPU的,那为什么加了操作系统后,程序能在这两个while(1)之间轮换?

操作系统都需要使用“时钟节拍”技术来实现对任务的监控,并能主动调度和切换任务的执行。uCos也同样是使用“时钟节拍”来实现任务的监管的,以STM32单片机为例,一般用SysTick这个系统时钟定时器来实现,比如我们设置这个定时器10ms的定时间隔,那么每隔10ms都会调用下面的中断服务(所以移植的时候,需要实现这个函数):

void SysTickHandler(void)

{

    OSIntEnter();

    OSTimeTick();    

    OSIntExit(); 

}

假设现在程序在Task_A的while(1)里,然后SysTick中断来了,SysTickHandler服务程序开始执行,先执行OSIntEnter():

void  OSIntEnter (void)

{

    if (OSRunning == OS_TRUE) {

        if (OSIntNesting < 255u) {

            OSIntNesting++;                          

        }

    }

}

这个函数很简单,他是uCos的内核函数,主要是给中断嵌套计数器OSIntNesting加一,因为uCos内核需要实时的判断程序的当前执行是不是在中断里,要知道,大部分的处理器是可以中断嵌套的,这里我们先不管判断程序是不是在中断里有什么用,后面马上会说到。

然后开始执行OSTimeTick(),这个函数我们只分析关键代码,也就是跟任务管理有关的代码:

void  OSTimeTick (void)

{

    OS_TCB    *ptcb;

#if OS_TICK_STEP_EN > 0

    BOOLEAN    step;

#endif

#if OS_CRITICAL_METHOD == 3                                /* Allocate storage for CPU status register     */

    OS_CPU_SR  cpu_sr = 0;

#endif

 

#if OS_TIME_TICK_HOOK_EN > 0

    OSTimeTickHook();                                      /* Call user definable hook                     */

#endif

 

#if OS_TIME_GET_SET_EN > 0

    OS_ENTER_CRITICAL();                                   /* Update the 32-bit tick counter               */

    OSTime++;

    OS_EXIT_CRITICAL();

#endif

 

    if (OSRunning == OS_TRUE) {

#if OS_TICK_STEP_EN > 0

        switch (OSTickStepState) {                         /* Determine whether we need to process a tick  */

            case OS_TICK_STEP_DIS:                         /* Yes, stepping is disabled                    */

                 step = OS_TRUE;

                 break;

 

            case OS_TICK_STEP_WAIT:                        /* No,  waiting for uC/OS-View to set ...       */

                 step = OS_FALSE;                          /*      .. OSTickStepState to OS_TICK_STEP_ONCE */

                 break;

 

            case OS_TICK_STEP_ONCE:                        /* Yes, process tick once and wait for next ... */

                 step            = OS_TRUE;                /*      ... step command from uC/OS-View        */

                 OSTickStepState = OS_TICK_STEP_WAIT;

                 break;

 

            default:                                       /* Invalid case, correct situation              */

                 step            = OS_TRUE;

                 OSTickStepState = OS_TICK_STEP_DIS;

                 break;

        }

        if (step == OS_FALSE) {                            /* Return if waiting for step command           */

            return;

        }

#endif

        ptcb = OSTCBList;                                  /* Point at first TCB in TCB list               */

        while (ptcb->OSTCBPrio != OS_TASK_IDLE_PRIO) {     /* Go through all TCBs in TCB list              */

            OS_ENTER_CRITICAL();

            if (ptcb->OSTCBDly != 0) {                     /* No, Delayed or waiting for event with TO     */

                if (--ptcb->OSTCBDly == 0) {               /* Decrement nbr of ticks to end of delay       */

                                                           /* Check for timeout                            */

                    if ((ptcb->OSTCBStat & OS_STAT_PEND_ANY) != OS_STAT_RDY) {

                        ptcb->OSTCBStat   &= ~OS_STAT_PEND_ANY;                /* Yes, Clear status flag   */

                        ptcb->OSTCBPendTO  = OS_TRUE;                          /* Indicate PEND timeout    */

                    } else {

                        ptcb->OSTCBPendTO  = OS_FALSE;

                    }

 

                    if ((ptcb->OSTCBStat & OS_STAT_SUSPEND) == OS_STAT_RDY) {  /* Is task suspended?       */

                        OSRdyGrp               |= ptcb->OSTCBBitY;             /* No,  Make ready          */

                        OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;

                    }

                }

            }

            ptcb = ptcb->OSTCBNext;                        /* Point at next TCB in TCB list                */

            OS_EXIT_CRITICAL();

        }

    }

}

在这个函数里,出现了一个全局变量OSTCBList,这个变量是uCos内核里任务控制块(TCB)链表的表头指针,问题又来了,TCB是什么,TCB链表又是什么?

原来uCos的每个任务都需要有一个TCB来管理,这个TCB记录了该任务的所有信息,同时uCos用链表来管理所有的这些TCB,TCB的结构如下:

typedef struct os_tcb {

    OS_STK          *OSTCBStkPtr;      /* Pointer to current top of stack                              */

#if OS_TASK_CREATE_EXT_EN > 0

    void            *OSTCBExtPtr;      /* Pointer to user definable data for TCB extension             */

    OS_STK          *OSTCBStkBottom;   /* Pointer to bottom of stack                                   */

    INT32U           OSTCBStkSize;     /* Size of task stack (in number of stack elements)             */

    INT16U           OSTCBOpt;         /* Task options as passed by OSTaskCreateExt()                  */

    INT16U           OSTCBId;          /* Task ID (0..65535)                                           */

#endif

 

    struct os_tcb   *OSTCBNext;        /* Pointer to next     TCB in the TCB list                      */

    struct os_tcb   *OSTCBPrev;        /* Pointer to previous TCB in the TCB list                      */

 

#if OS_EVENT_EN

    OS_EVENT        *OSTCBEventPtr;    /* Pointer to event control block                               */

#endif

 

#if ((OS_Q_EN > 0) && (OS_MAX_QS > 0)) || (OS_MBOX_EN > 0)

    void            *OSTCBMsg;         /* Message received from OSMboxPost() or OSQPost()              */

#endif

 

#if (OS_VERSION >= 251) && (OS_FLAG_EN > 0) && (OS_MAX_FLAGS > 0)

#if OS_TASK_DEL_EN > 0

    OS_FLAG_NODE    *OSTCBFlagNode;    /* Pointer to event flag node                                   */

#endif

    OS_FLAGS         OSTCBFlagsRdy;    /* Event flags that made task ready to run                      */

#endif

 

    INT16U           OSTCBDly;         /* Nbr ticks to delay task or, timeout waiting for event        */

    INT8U            OSTCBStat;        /* Task status                                                  */

    BOOLEAN          OSTCBPendTO;      /* Flag indicating PEND timed out (OS_TRUE == timed out)           */

    INT8U            OSTCBPrio;        /* Task priority (0 == highest)                                 */

 

    INT8U            OSTCBX;           /* Bit position in group  corresponding to task priority        */

    INT8U            OSTCBY;           /* Index into ready table corresponding to task priority        */

#if OS_LOWEST_PRIO <= 63

    INT8U            OSTCBBitX;        /* Bit mask to access bit position in ready table               */

    INT8U            OSTCBBitY;        /* Bit mask to access bit position in ready group               */

#else

    INT16U           OSTCBBitX;        /* Bit mask to access bit position in ready table               */

    INT16U           OSTCBBitY;        /* Bit mask to access bit position in ready group               */

#endif

 

#if OS_TASK_DEL_EN > 0

    INT8U            OSTCBDelReq;      /* Indicates whether a task needs to delete itself              */

#endif

 

#if OS_TASK_PROFILE_EN > 0

    INT32U           OSTCBCtxSwCtr;    /* Number of time the task was switched in                      */

    INT32U           OSTCBCyclesTot;   /* Total number of clock cycles the task has been running       */

    INT32U           OSTCBCyclesStart; /* Snapshot of cycle counter at start of task resumption        */

    OS_STK          *OSTCBStkBase;     /* Pointer to the beginning of the task stack                   */

    INT32U           OSTCBStkUsed;     /* Number of bytes used from the stack                          */

#endif

 

#if OS_TASK_NAME_SIZE > 1

    INT8U            OSTCBTaskName[OS_TASK_NAME_SIZE];

#endif

} OS_TCB;

 

这结构体稍微有点大,这里我们先知需要关心OSTCBDly这个成员,这个成员记录该任务的延时值,当我们调用uCos的系统函数OSTimeDly(),OSQPend()等这些阻塞类型的函数时,该任务的TCB成员OSTCBDly就会被赋予相应值。

我们继续分析OSTimeTick(),这个函数,在这个函数里,主要是给每个TCB的OSTCBDly的计数值减一,如果OSTCBDly为零了,则在任务就绪表里标记该任务,这里又引出了一问题,什么是就绪表,uCos的就绪表的实现用了一个比较巧妙的算法,这里先不仔细去分析,只有知道就绪表里记录了当前系统中,哪些任务是处于就绪态,也就是可被CPU执行的状态。

好,到这里OSTimeTick()分析完了,他完成了对每个任务的延时值减一操作,并更新了就绪表。下面开始执行OSIntExit(),这个函数是个很关键的函数,她将实现任务的上下文切换。

OSIntExit函数的源码如下:

void  OSIntExit (void)

{

#if OS_CRITICAL_METHOD == 3                               

    OS_CPU_SR  cpu_sr = 0;

#endif

 

    if (OSRunning == OS_TRUE) {   /* 如果uCos还没有启动运行,则不进行任务调度,因为这时候uCos的初始化还没完成 */

        OS_ENTER_CRITICAL();

        if (OSIntNesting > 0) {    /* 将中断嵌套计数器减一 */                      

            OSIntNesting--;

        }

        if (OSIntNesting == 0) {   /* 只有在中断嵌套为0,也就是即将退出中断时才进行任务调度 */                      

            if (OSLockNesting == 0) {                     

                OS_SchedNew();/* 找出当前就绪表中的最高优先级任务,并更新全局变量OSPrioHighRdy */

                if (OSPrioHighRdy != OSPrioCur) {/*如果发现当前最高优先级任务不是正在运行的任务,就要进行任务切换*/        

                    OSTCBHighRdy  = OSTCBPrioTbl[OSPrioHighRdy];

#if OS_TASK_PROFILE_EN > 0

                    OSTCBHighRdy->OSTCBCtxSwCtr++;        

#endif

                    OSCtxSwCtr++;                         

                    OSIntCtxSw(); /*调用专门用于中断服务里的上下文切换函数,进行山下文的切换 */                        

                }

            }

        }

        OS_EXIT_CRITICAL();

    }

}

        从上面的源码和代码注释可以总结出该函数完成的功能为:找出就绪表里的最高优先级任务,并执行OSIntCtxSw函数来进行任务切换。这里要注意,虽然执行了任务切换,但不会立刻进行上下文的切换,这个实现过程跟CPU的硬件有关,在STM32中,上下文的切换是用的“悬起中断”,该中断只有当CPU的所有中断完成了,也就是退出了所有的中断嵌套后,才会执行。OSIntCtxSw函数是移植uCos的一个非常关键的函数,他负责恢复运行任务的上次中断现场,这个函数跟CPU体系有紧密的联系,这里先不仔细分析。

        到这里我们基本可以看到uCos利用系统时钟滴答实现多任务的大概流程如下:

uCos的任务切换过程就分析跟踪完了,这里要注意几点,任务的切换并不只会发生在系统时钟滴答的中断服务里,调用发送信号量,发送消息等这些系统函数时也会引起任务切换,但大致的原理都差不多,这里我们只对程序的原理和框架做了说明,实际还是有些细节需要处理的。

posted on 2016-06-22 20:49  让编程成为一种习惯  阅读(2535)  评论(0编辑  收藏  举报