uC/OS-II源码分析

 uC/OS-II源码分析

首先从main.c文件看起,下面是uC/OS-II main.C的大致流程:

 

main(){

 OSInit();

 TaskCreate(...);

 OSStart();

}

首先是调用OSInit进行初始化,然后使用TaskCreate创建几个进程/Task,最后调用OSStart,操作系统就开始运行了。

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

OSInit():  最先看看OSInit完成哪些初始化:

void  OSInit (void)

{

#if OS_VERSION >= 204

    OSInitHookBegin();                                    /* Call port specific initialization code   */

#endif

    OS_InitMisc();                                        /* Initialize miscellaneous variables       */

    OS_InitRdyList();                                      /* Initialize the Ready List                */

    OS_InitTCBList();                                     /* Initialize the free list of OS_TCBs      */

    OS_InitEventList();                                    /* Initialize the free list of OS_EVENTs    */

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

    OS_FlagInit();                                       /* Initialize the event flag structures     */

#endif

#if (OS_MEM_EN > 0) && (OS_MAX_MEM_PART > 0)

    OS_MemInit();                                      /* Initialize the memory manager            */

#endif

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

    OS_QInit();                                        /* Initialize the message queue structures  */

#endif

    OS_InitTaskIdle();                                   /* Create the Idle Task  初始化空闲任务  */

#if OS_TASK_STAT_EN > 0

    OS_InitTaskStat();                                  /* Create the Statistic Task初始化统计任务*/

#endif

#if OS_VERSION >= 204

    OSInitHookEnd();                                  /* Call port specific init. code            */

#endif

#if OS_VERSION >= 270 && OS_DEBUG_EN > 0

    OSDebugInit();

#endif

}

OS_InitMisc()完成的是一些其其他他的变量的初始化:

    OSIntNesting  = 0;                                 /* Clear the interrupt nesting counter      */

    OSLockNesting = 0;                                /* Clear the scheduling lock counter        */

    OSTaskCtr     = 0;                               /* Clear the number of tasks                */

    OSRunning     = FALSE;                         /* Indicate that multitasking not started   */

   

    OSCtxSwCtr    = 0;                             /* Clear the context switch counter         */

    OSIdleCtr     = 0L;                             /* Clear the 32-bit idle counter            */

其中包括:中断嵌套标志OSIntNesting,调度锁定标志OSLockNesting,OS标志OSRunning等。OSRunning在这里设置为FALSE,在后面OSStartHighRdy中会被设置为TRUE表示OS开始工作。

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

OS_InitRdyList()初始化就绪Task列表:

static  void  OS_InitRdyList (void)

{

    INT8U    i;

    INT8U   *prdytbl;

    OSRdyGrp      = 0x00;                           /* Clear the ready list       */

    prdytbl       = &OSRdyTbl[0];

    for (i = 0; i < OS_RDY_TBL_SIZE; i++) {

        *prdytbl++ = 0x00;

    }

    OSPrioCur     = 0;

    OSPrioHighRdy = 0;

    OSTCBHighRdy  = (OS_TCB *)0;  //                              

    OSTCBCur      = (OS_TCB *)0;

}

首先将OSRdyTbl[]数组中全部初始化0,同时将OSPrioCur / OSTCBCur始化为0,OSPrioHighRdy/OSTCBHighRdy也初始化为0,这几个变量将在第一个OSSchedule中被赋予正确的值。

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

OS_InitTCBList()这个函数看名称我们就知道是初始化TCB列表。

static  void  OS_InitTCBList (void)

{

    INT8U    i;

    OS_TCB  *ptcb1;

    OS_TCB  *ptcb2;

    OS_MemClr((INT8U *)&OSTCBTbl[0],    sizeof(OSTCBTbl));  /* Clear all the TCBs                 */

    OS_MemClr((INT8U *)&OSTCBPrioTbl[0], sizeof(OSTCBPrioTbl));  /* Clear the priority table           */

    ptcb1 = &OSTCBTbl[0];

    ptcb2 = &OSTCBTbl[1];

    for (i = 0; i < (OS_MAX_TASKS + OS_N_SYS_TASKS - 1); i++) {  /* Init. list of free TCBs            */

        ptcb1->OSTCBNext = ptcb2;

#if OS_TASK_NAME_SIZE > 1

        ptcb1->OSTCBTaskName[0] = '?';                         /* Unknown name               */

        ptcb1->OSTCBTaskName[1] = OS_ASCII_NUL;

#endif

        ptcb1++;

        ptcb2++;

    }

    ptcb1->OSTCBNext = (OS_TCB *)0;                        /* Last OS_TCB 空指针           */

#if OS_TASK_NAME_SIZE > 1

    ptcb1->OSTCBTaskName[0] = '?';                          /* Unknown name               */

    ptcb1->OSTCBTaskName[1] = OS_ASCII_NUL;

#endif

    OSTCBList               = (OS_TCB *)0;                /* TCB lists initializations          */

    OSTCBFreeList           = &OSTCBTbl[0];

}

这里完成的工作很简单,首先把整个数组使用OSTCBNext指针连接成链表链起来,然后将OSTCBList初始化为0,也就是还没有TCB,因为还没有Task产生,OSTCBFreeList指向OSTCBTbl[]数组的第一个表示所有TCB都处于Free状态。

 

从Ucos_ii.c内核代码中学到的

OS_MemClr((INT8U *)&OSTCBTbl[0],sizeof(OSTCBTbl));Clear all the tcb

下面是给结构体数组清零的函数,实质是将结构体占据的内存区域视为字符变量区域

,用循环对单个内存字节单元的清零来实现对结构体占据的内存区域即(结构体)的清零

void  OS_MemClr (INT8U *pdest, INT16U size)

{

    while (size > 0) {

        *pdest++ = (INT8U)0;

        size--;

    }

}

注意函数OS_MemClr中的强制类型转换 INT8U (*)&OSTCBTbl[0],

目的是将结构体指针转化成字符型指针,对单个内存单元的清零(*pdest++ = (INT8U)0;),来完成对结构体的清零(或者是结构体数组)

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

OS_InitEventList()初始化Event列表。

static  void  OS_InitEventList (void)

{

#if OS_EVENT_EN && (OS_MAX_EVENTS > 0)

#if (OS_MAX_EVENTS > 1)

    INT16U     i;

    OS_EVENT  *pevent1;

    OS_EVENT  *pevent2;

 

    OS_MemClr((INT8U *)&OSEventTbl[0], sizeof(OSEventTbl)); /* Clear the event table                   */

    pevent1 = &OSEventTbl[0];

    pevent2 = &OSEventTbl[1];

    for (i = 0; i < (OS_MAX_EVENTS - 1); i++) {             /* Init. list of free EVENT control blocks */

        pevent1->OSEventType    = OS_EVENT_TYPE_UNUSED;

        pevent1->OSEventPtr     = pevent2;

#if OS_EVENT_NAME_SIZE > 1

        pevent1->OSEventName[0] = '?';                      /* Unknown name                            */

        pevent1->OSEventName[1] = OS_ASCII_NUL;

#endif

        pevent1++;

        pevent2++;

    }

    pevent1->OSEventType            = OS_EVENT_TYPE_UNUSED;

    pevent1->OSEventPtr             = (OS_EVENT *)0;

#if OS_EVENT_NAME_SIZE > 1

    pevent1->OSEventName[0]         = '?';                 

    pevent1->OSEventName[1]         = OS_ASCII_NUL;

#endif

    OSEventFreeList                 = &OSEventTbl[0];

#else

    OSEventFreeList                 = &OSEventTbl[0];      /* Only have ONE event control block    */

    OSEventFreeList->OSEventType    = OS_EVENT_TYPE_UNUSED;

    OSEventFreeList->OSEventPtr     = (OS_EVENT *)0;

#if OS_EVENT_NAME_SIZE > 1

    OSEventFreeList->OSEventName[0] = '?';                  /* Unknown name                */

    OSEventFreeList->OSEventName[1] = OS_ASCII_NUL;

#endif

#endif

#endif

}

同样将EventTbl[]数组中的OSEventType都初始化为OS_EVENT_TYPE_UNUSED。

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

OS_InitTaskIdle(),中间我们跳过其他的如Mem等的初始化,看看Idle Task的初始化。

    (void)OSTaskCreateExt(OS_TaskIdle,

                          (void *)0,                  /* No arguments passed to OS_TaskIdle() */

         &OSTaskIdleStk[OS_TASK_IDLE_STK_SIZE - 1], /* Set Top-Of-Stack                  */

                          OS_IDLE_PRIO,           /* Lowest priority level                */

                          OS_TASK_IDLE_ID,

                          &OSTaskIdleStk[0],        /* Set Bottom-Of-Stack                  */

                          OS_TASK_IDLE_STK_SIZE,

                          (void *)0,                /* No TCB extension                     */

                          OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR);/* Enable stack checking + clear stack  */

其实Idle Task的初始化很简单就是调用OSTaskCrete系列的函数创建一个Task, OSTaskCreate我们后面再做进一步分析。

初始化State Task也是类似调用OSTaskCreate系列函数创建Stat Task。这里只是创建了该Task的各个结构还没有真正运行该Task,直到OSStart中才依据优先级调度运行。

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

OK,到这里OSInit算高一个段落了,我们接着回到main往下看。OSTaskCreate

 

OSTaskCreate负责创建Task所需的数据结构,该函数原形如下所示:

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

其中task是一个函数指针,指向该Task所开始的函数,当这个Task第一次被调度运行时将会从task处开始运行。

p_arg是传给task的参数指针;

ptos是堆栈指针,指向栈顶(堆栈从上往下)或栈底(堆栈从下往上);

prio是进程的优先级,uC/OS-II共支持最大64个优先级,其中最低的两个优先级给Idle和Stat进程,并且各个Task的优先级必须不同。

接下来,我们看看这个函数的执行流程:

#if OS_ARG_CHK_EN > 0

    if (prio > OS_LOWEST_PRIO) {             /* Make sure priority is within allowable range           */

        return (OS_PRIO_INVALID);

    }

#endif

    OS_ENTER_CRITICAL();

    if (OSIntNesting > 0) {                  /* Make sure we don't create the task from within an ISR  */

        OS_EXIT_CRITICAL();

        return (OS_ERR_TASK_CREATE_ISR);

    }

    if (OSTCBPrioTbl[prio] == (OS_TCB *)0) { /* Make sure task doesn't already exist at this priority  */

        OSTCBPrioTbl[prio] = (OS_TCB *)1;    /* Reserve the priority to prevent others from doing ...  */

                                             /* ... the same thing until task is created.              */

        OS_EXIT_CRITICAL();

        psp = (OS_STK *)OSTaskStkInit(task, p_arg, ptos, 0);    /* Initialize the task's stack         */

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

        if (err == OS_NO_ERR) {

            if (OSRunning == TRUE) {         /* Find highest priority task if multitasking has started */

                OS_Sched();

            }

        } else {

            OS_ENTER_CRITICAL();

            OSTCBPrioTbl[prio] = (OS_TCB *)0;/* Make this priority available to others                 */

            OS_EXIT_CRITICAL();

        }

        return (err);

    }

    OS_EXIT_CRITICAL();

    return (OS_PRIO_EXIST);

OS_LOWEST_PRIO在ucos-ii.h中被定义为63,表示Task的优先级从0到63,共64级。首先判断prio是否超过最低优先级,如果是,则返回

OS_PRIO_INVALID错误。

然后调用OS_ENTER_CRITICAL(),进入临界段,在临界段中的代码执行不允许被中断。这个宏是用户自定义的,一般是进行关中断操作,例如在x86中的CLI等。这个宏和OS_EXIT_CRITICAL()相对应,这个宏表示离开临界段。

OSTaskCreate 不允许在中断中调用 ,因此会判断OSIntNesting是否大于0,如果大于0,表示正在中断嵌套,返回OS_ERR_TASK_CREATE_ISR错误。

接着判断该prio是否已经有Task存在,由于uC/OS-II只支持每一个优先级一个Task,因此如果该prio已经有进程存在,OSTaskCreate会返回OS_PRIO_EXIST错误。

相反,如果该prio先前没有Task存在,则将OSTCBPrioTbl[prio]置1,表示该prio已被占用,然后调用OSTaskStkInit初始化堆栈,调用OS_TCBInit

初始化TCB,如果OSRunning为TRUE表示OS正在运行,则调用OS_Sched进行进程调度;否则返回。

下面来看看OSTaskStkInit和OS_TCBInit这两个函数。

 

OSTaskStkInit是一个用户自定义的函数,因为uC/OS-II在设计时无法知道当前处理器在进行进程调度时需要保存那些信息,OSTaskStkInit就是初始化堆栈,让Task看起来就好像刚刚进入中断并保存好寄存器的值一样,当OS_Sched调度到该Task时,只需切换到该堆栈中,将寄存器值Pop出来,然后执行一个中断返回指令IRET即可。

OSTaskStkInit的原型如下:

OS_STK *OSTaskStkInit (void (*task)(void *pd), void *pdata, OS_STK *ptos, INT16U opt)

和OSTaskCreate类似,task是进程入口地址,pdata是参数地址,ptos是堆栈指针,而opt只是作为一个预留的参数Option而保留。返回的是调整以后的堆栈指针。

在OSTaskStkInit中,一般是将pdata入栈,flag入栈,task入栈,然后将各寄存器依次入栈。

OS_TCBInit初始化TCB数据结构,下面只提取主要部分来看:

INT8U  OS_TCBInit (INT8U prio, OS_STK *ptos, OS_STK *pbos, INT16U id, INT32U stk_size, void *pext, INT16U opt)

{

    OS_TCB    *ptcb;

  

    OS_ENTER_CRITICAL();

    ptcb = OSTCBFreeList;                                  /* Get a free TCB from the free TCB list    */

    if (ptcb != (OS_TCB *)0) {

        OSTCBFreeList        = ptcb->OSTCBNext;         /* Update pointer to free TCB list          */

        OS_EXIT_CRITICAL();

        ptcb->OSTCBStkPtr    = ptos;                     /* Load Stack pointer in TCB              */

        ptcb->OSTCBPrio      = prio;                    /* Load task priority into TCB              */

        ptcb->OSTCBStat      = OS_STAT_RDY;          /* Task is ready to run                     */

        ptcb->OSTCBPendTO    = FALSE;               /* Clear the Pend timeout flag              */

        ptcb->OSTCBDly       = 0;                  /* Task is not delayed                      */

#if OS_TASK_CREATE_EXT_EN > 0

        ptcb->OSTCBExtPtr    = pext;                       /* Store pointer to TCB extension           */

        ptcb->OSTCBStkSize   = stk_size;                    /* Store stack size                   */

        ptcb->OSTCBStkBottom = pbos;                   /* Store pointer to bottom of stack         */

        ptcb->OSTCBOpt       = opt;                   /* Store task options                       */

        ptcb->OSTCBId        = id;                   /* Store task ID                            */

#else

        pext                 = pext;                       /* Prevent compiler warning if not used     */

        stk_size             = stk_size;

        pbos                 = pbos;

        opt                  = opt;

        id                   = id;

#endif

#if OS_TASK_DEL_EN > 0

        ptcb->OSTCBDelReq    = OS_NO_ERR;

#endif

        ptcb->OSTCBY         = (INT8U)(prio >> 3);      /* Pre-compute X, Y, BitX and BitY          */

        ptcb->OSTCBBitY      = OSMapTbl[ptcb->OSTCBY];

        ptcb->OSTCBX         = (INT8U)(prio & 0x07);

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

#if OS_EVENT_EN

        ptcb->OSTCBEventPtr  = (OS_EVENT *)0;          /* Task is not pending on an event          */

#endif

        OSTaskCreateHook(ptcb);                         /* Call user defined hook                   */

        OS_ENTER_CRITICAL();

        OSTCBPrioTbl[prio] = ptcb;

        ptcb->OSTCBNext    = OSTCBList;            /* Link into TCB chain                      */

        ptcb->OSTCBPrev    = (OS_TCB *)0;

        if (OSTCBList != (OS_TCB *)0) {

            OSTCBList->OSTCBPrev = ptcb;

        }

        OSTCBList               = ptcb;

        OSRdyGrp               |= ptcb->OSTCBBitY;      /* Make task ready to run                   */

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

        OSTaskCtr++;                                /* Increment the #tasks counter             */

        OS_EXIT_CRITICAL();

        return (OS_NO_ERR);

    }

    OS_EXIT_CRITICAL();

    return (OS_NO_MORE_TCB);

}

首先调用OS_ENTER_CRITICAL进入临界段,首先从OSTCBFreeList中拿出一个TCB,如果OSTCBFreeList为空,则返回OS_NO_MORE_TCB错误。然后调用OS_EXIT_CRITICAL离开临界段,接着对该TCB进行初始化: 将OSTCBStkPtr初始化为该Task当前堆栈指针;

 OSTCBPrio设置为该Task的prio;

 OSTCBStat设置为OS_STAT_RDY,表示就绪状态;

 OSTCBDly设置为0,当该Task调用OSTimeDly时会初始化这个变量为Delay的时钟数,然后Task转入OS_STAT_状态。这个变量在OSTimeTick中检查,如果大于0表示还需要进行Delay,则进行减1;如果等于零表示无须进行Delay,可以马上运行,转入OS_STAT_RDY状态。

 OSTCBBitY和OSTCBBitX的作用我们在等会专门来讨论。

紧接着就要将该TCB插入OSTCBList列表中,先调用OS_ENTER_CRITICAL进入临界段,将该TCB插入到OSTCBList成为第一个节点,然后调整OSRdyGrp和OSRdyTbl,(这两个变量一会和OSTCBBitX/OSTCBBitY一起讨论),最后将OSTaskCtr计数器加一,调用OS_EXIT_CRITICAL退出临界段。

OSMapTbl和OSUnMapTbl

刚才我们看到TCB数据结构中的OSTCBBitX/OSTCBBitY以及OSRdyGrp/OSRdyTbl的使用,这里专门来讨论讨论这几个变量的用法。

uC/OS-II将64个优先级的进程分为8组,每组8个。刚好可以使用8个INT8U的数据进行表示,于是这就是OSRdyGrp和OSRdyTbl的由来,OSRdyGrp表示组别,从0到7。

在任务就绪表中登记就绪任务的代码为

     OSRdyGrp               |= ptcb->OSTCBBitY;

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

(既是---OSRdyGrp |= OSMapTbl[prio >> 3];

OSRdyTbl[prio >> 3] |= OSMapTbl[prio & 0x07];---)

从任务就绪表中删除任务的代码为:

if ((OSRdyTbl[prio >> 3] &= ~OSMapTbl[prio & 0x07]) = = 0)

//如果被复位的位的所在行全为0,则表示此行没有就绪任务,所以其对应的OSRdyGrp的相应位被复位。

OSRdyGrp &= ~OSMapTbl[prio >> 3];

(既是,if(OSRdyTbl[pricur->OSTCBY]&=~OSMapTbl[pricur->OSTCBX]= =0)//

OSTdyGrp&=pricur->OSTCBBitY

 

 也就是OSTCBBitY保存的是组别,OSTCBBitX保存的是组内的偏移。而这两个变量是这么被初始化的:

        ptcb->OSTCBY         = (INT8U)(prio >> 3);          //就绪OSRdyGrp中的索引

        ptcb->OSTCBBitY      = OSMapTbl[ptcb->OSTCBY];   //就绪表置位时的掩码!

        ptcb->OSTCBX         = (INT8U)(prio & 0x07);       //就绪OSRdyTbl中组内的索引

        ptcb->OSTCBBitX      = OSMapTbl[ptcb->OSTCBX];  //就绪OSRdyGrp置位时的掩码

 

 由于prio不会大于64,prio为6位值,因此OSTCBY为prio高3位,不会大于8,OSTCBX为prio低3位。

 这里就涉及到OSMapTbl数组和OSUnMapTbl数组的用法了。我们先看看OSMapTbl和OSUnMapTbl的定义:

 

 INT8U  const  OSMapTbl[8]   = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};

即{00000001,00000010,00000100,00001000,00010000,00100000,01000000,10000000}

 

 INT8U  const  OSUnMapTbl[256] = {

     0, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x00 to 0x0F                             */

     4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x10 to 0x1F                             */

     5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x20 to 0x2F                             */

     4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x30 to 0x3F                             */

     6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x40 to 0x4F                             */

     4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x50 to 0x5F                             */

     5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x60 to 0x6F                             */

     4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x70 to 0x7F                             */

     7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x80 to 0x8F                             */

     4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x90 to 0x9F                             */

     5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0xA0 to 0xAF                             */

     4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0xB0 to 0xBF                             */

     6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0xC0 to 0xCF                             */

     4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0xD0 to 0xDF                             */

     5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0xE0 to 0xEF                             */

     4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0        /* 0xF0 to 0xFF                             */

 };

 OSMapTbl分别是一个INT8U的八个位,而OSUnMap数组中的值就是从0x00到0xFF的八位中,每一个值所对应的最低位的值。我们在调度的时候只需将OSRdyGrp的值代入OSUnMapTbl数组中,得到OSUnMapTbl[OSRdyGrp]的值就是哪个优先级最高的Group有Ready进程存在,再使用该Group对应OSRdyTbl[]数组中的值一样带入OSUnMapTbl中就可以得出哪个Task是优先级最高的。 于是我们提前来看看OS_Sched()中获取最高优先级所使用的方法:

 y     = OSUnMapTbl[OSRdyGrp];      /* Get pointer to HPT ready to run              */

    OSPrioHighRdy = (INT8U)((y << 3) + OSUnMapTbl[OSRdyTbl[y]]);

 显然,先得到的y就是存在最高优先级的Group,然后OSUnMapTbl[OSRdyTbl[y]]就是Group中的偏移,因此OSPrioHighRdy最高优先级就应该是

Group<<3再加上这个偏移。

 

 于是乎,我们就可以对上面那一小段很模糊的代码做一下总结:

 prio只有6位,高3位代表着某一个Group保存在OSTCBY中,OSTCBBitY表示该Group所对应的Bit,将OSRdyGrp的该位置1表示该Group中有进程是

Ready的;低3位代表着该Group中的第几个进程,保存在OSTCBX中,OSTCBBitX表示该进程在该Group中所对应的Bit,OSRdyTbl[ptcb->OSTCBY] |=ptcb->OSTCBBitX就等于将该进程所对应的Bit置1了。

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

OSStart

OK,接下来我们来看这个开始函数了。OSStart其实很短,只有匆匆几句代码:

void  OSStart (void)

{

    INT8U y;

    INT8U x;

 

    if (OSRunning = = FALSE) {

        y             = OSUnMapTbl[OSRdyGrp];        /* Find highest priority's task priority number   */

        x             = OSUnMapTbl[OSRdyTbl[y]];

        OSPrioHighRdy = (INT8U)((y << 3) + x);

        OSPrioCur     = OSPrioHighRdy;

        OSTCBHighRdy  = OSTCBPrioTbl[OSPrioHighRdy]; /* Point to highest priority task ready to run    */

        OSTCBCur      = OSTCBHighRdy;

        OSStartHighRdy();                            /* Execute target specific code to start task     */

    }

}

如果OSRunning为TRUE,表示OS已经在运行了,则OSStart不做任何事。

OSRunning为FALSE,则找出最高优先级的Ready的Task,并将该指针赋给OSTCBHighRdy和OSTCBCur。然后调用OSStartHighRdy()开始运行该进程。

OSStartHighRdy()为用户自定义函数,在这个函数中,主要功能就是进行堆栈切换并将OSRunning设置为TRUE表示OS已经开始运行,然后将保存的寄存器弹出,最后执行中断返回指令IRET就跳到OSTCBHighRdy的最开始处运行了。

 

OSTimeDly

 在Task中,一般执行一段时间之后调用OSTimeDly推迟一段时间再继续运行,OSTimeDly将本进程从Ready TCBList中删除,然后将Delay的时间设置

给OSTCBDly,最后调用OS_Sched进行进程调度。

void  OSTimeDly (INT16U ticks)

{

    INT8U      y;

  

    if (ticks > 0) {                             /* 0 means no delay!                                  */

        OS_ENTER_CRITICAL();

        y =  OSTCBCur->OSTCBY;            /* Delay current task                                 */

        OSRdyTbl[y] &= ~OSTCBCur->OSTCBBitX;

        if (OSRdyTbl[y] == 0) { 

            OSRdyGrp &= ~OSTCBCur->OSTCBBitY;

        }

        OSTCBCur->OSTCBDly = ticks;         /* Load ticks in TCB                 */

        OS_EXIT_CRITICAL();

        OS_Sched();                /* Find next task to run!                             */

    }

}

如果ticks为零,说明不需延迟,则什么事情都不做。否则,调用OS_ENTER_CRITICAL进入临界段,将本进程从Ready TCBList中删除的代码如下:

        y      =  OSTCBCur->OSTCBY;        /* Delay current task                        */

        OSRdyTbl[y] &= ~OSTCBCur->OSTCBBitX;

        if (OSRdyTbl[y] == 0) { 

            OSRdyGrp &= ~OSTCBCur->OSTCBBitY;

        }

y为当前进程所在Group,OSRdyTbl[y]为该Group所在字节,&=~则将该字节中本进程所占用的Bit清零。如果OSRdyTbl[y]为0,则说明这个Group中没有进程处于Ready状态,则将OSRdyGrp中该Group所占用的Bit清零。

然后将ticks保存在OSTCBDly中,每次OSTimeTick运行时会将这个值减一直至为零。

调用OS_EXIT_CRITICAL离开临界段,紧接着调用OS_Sched进入调度例程。

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

OS_Sched

OS_Sched是进程调度所使用的函数,在这里面找到最高优先级的进程,然后切换到该进程运行。

void  OS_Sched (void)

{

    INT8U      y;

    OS_ENTER_CRITICAL();

    if (OSIntNesting == 0) {                           /* Schedule only if all ISRs done and ...       */

        if (OSLockNesting == 0) {                      /* ... scheduler is not locked                  */

            y       = OSUnMapTbl[OSRdyGrp];      /* Get pointer to HPT ready to run              */

            OSPrioHighRdy = (INT8U)((y << 3) + OSUnMapTbl[OSRdyTbl[y]]);

            if (OSPrioHighRdy != OSPrioCur) {          /* No Ctx Sw if current task is highest rdy     */

                OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];

                OSCtxSwCtr++;                   /* Increment context switch counter             */

                OS_TASK_SW();                   /* Perform a context switch                     */

            }

        }

    }

    OS_EXIT_CRITICAL();

}

OS_Sched不允许在中断嵌套中调用,因此先判断是否是中断嵌套,并且是否限制进程调度,这两个条件都满足之后,找到最高优先级的进程,如果这个进程不是当前进程,则将新的进程TCB指针保存到OSTCBHighRdy中,为调度计数器OSCtxSwCtr加一,然后调用宏OS_TASK_SW()进行切换。

OS_TASK_SW()宏也是一个自定义的宏,uC/OS-II推荐使用软中断方式实现。

OSCtxSw是一个中断响应函数,一般我们在初始化时将这个软终端和OSCtxSw挂接好。在OSCtxSw中所需要做的事情就是将当前寄存器的值保存到当前堆栈中,然后切换堆栈到新进程的堆栈,将寄存器的值出栈,然后调用中断返回指令IRET就返回到新进程中断前的地方继续执行了。

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

定时中断

 

uC/OS-II的定时中断必须在OSStart之后初始化,而不能在OSStart之前,因为害怕第一个TimeTick发生时第一个进程还没有开始运行,而这时uC/OS是处于不可预期状态,会导致死机。

因此对于定时中断,我一般是放在最高级进程的初始化中进行,然后将定时中断和OSTickISR挂接。

OSTickISR也是一个用户自定义函数,所要完成的功能一个是保存当前的寄存器到当前堆栈将OSIntNesting加一,然后调用uC/OS提供的OSTimeTick函数,然后调用OSIntExit()将OSIntNesting减一,最后将各寄存器值出栈,使用中断返回指令IRET返回。

OSTimeTick在每个时钟中断中被调用一次,在该函数中会更新各个进程TCB所对应的OSTCBDly,如果该OSTCBDly减为0,则对应的TCB就被放入Ready

TCBList中。

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

    OSTime++;

    OS_EXIT_CRITICAL();   

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

        while (ptcb->OSTCBPrio != OS_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  = TRUE;         /* Indicate PEND timeout    */

                    } else {

                        ptcb->OSTCBPendTO  = 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();

        }

首先在临界段将OSTime加一,然后遍历整个非Free的TCBList,如果OSTCBDly不为0,则,将OSTCBDly减一,如果这时OSTCBDly为0,而且TCB对应的进程需要等待任何信号量或Event等,则说明超时时间到了,将当前TCB的State中OS_STAT_PEND_ANY位去掉,然后将OSTCBPendTo设置为TRUE,表示这是PEND的超时,否则设置OSTCBPendTO为FALSE。

如果OSTCBDly减为零,且该进程没有Suspend,则将该进程放入Ready TCBList中,使用方法同TaskCreate中的方法。然后我们来说说OSIntExit这个函数。该函数代码如下:

void  OSIntExit (void)

{

    INT8U      y;

 

    if (OSRunning == TRUE) {

        OS_ENTER_CRITICAL();

        if (OSIntNesting > 0) {                            /* Prevent OSIntNesting from wrapping       */

            OSIntNesting--;

        }

        if (OSIntNesting == 0) {                           /* Reschedule only if all ISRs complete ... */

            if (OSLockNesting == 0) {                      /* ... and not locked.                      */

  y             = OSUnMapTbl[OSRdyGrp];         

                OSPrioHighRdy = (INT8U)((y << 3) + OSUnMapTbl[OSRdyTbl[y]]);

                if (OSPrioHighRdy != OSPrioCur) {          /* No Ctx Sw if current task is highest rdy */

                    OSTCBHighRdy  = OSTCBPrioTbl[OSPrioHighRdy];

                    OSCtxSwCtr++;                          /* Keep track of the number of ctx switches */

                    OSIntCtxSw();                          /* Perform interrupt level ctx switch       */

                }

            }

        }

 OS_EXIT_CRITICAL();

    }

}

首先判断OSRunning是否为1,也就是OS是否在运行,当然没有运行就什么都不做。

然后将OSIntNesting减一,这个是需要在临界段进行的。如果OSIntNesting减为零,并且没有限制进程切换,则找到当前最高优先级的进程(方法同OS_Sched()),然后调用OSIntCtxSw进行进程切换。

OSIntCtxSw()是用户自定义函数,该函数的主要功能与OSCtxSw类似,只是需要对当前的堆栈进行稍微的调整,将OSIntExit和OSIntCtxSw调用所需要的堆栈去掉,然后做的和OSCtxSw一样。

在实际的Porting中发现要去掉OSIntExit和OSIntCtxSw调用所占用的堆栈还是比较麻烦的,因此我就现在OSTickISR刚开始的时候保存好现场之后就将堆栈指针赋给当前进程TCB的OSStkPtr,这样,在OSIntCtxSw中就不需要重新对当前堆栈的值进行保存,只需进行切换就可以了。

 OK,到这里应该对uC/OS的运行机制有一点点理解了,我们的分析之旅告个段落。以后如果有兴趣我们再继续对Event、信号量等等之类的分模块进行分析。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值