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

  • 相关阅读:
    编写pl/sql时,报错
    ORA-00906 missing left parenthesis括号
    数仓理论
    查看oracle实例名
    Oracle 关键字
    oracle关键字作为字段名使用方法
    dump函数
    raw数据类型
    oracle同义词是什么意思?
    10 Useeful Tips for Writing Effective Bash Scripts in Linux
  • 原文地址:https://www.cnblogs.com/beautiful-code/p/5608549.html
Copyright © 2011-2022 走看看