uC/OS-II源碼分析

首先從main函數開始,下面是uC/OS-II main函數的大致流程:
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狀態。
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和OSRdyTbl是這麼被賦值的:
     OSRdyGrp               |= ptcb->OSTCBBitY;
        OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;
     
 也就是OSTCBBitY保存的是組別,OSTCBBitX保存的是組內的偏移。而這兩個變量是這麼被初始化的:
     
     ptcb->OSTCBY         = (INT8U)(prio >> 3);
        ptcb->OSTCBBitY      = OSMapTbl[ptcb->OSTCBY];
        ptcb->OSTCBX         = (INT8U)(prio & 0x07);
        ptcb->OSTCBBitX      = OSMapTbl[ptcb->OSTCBX];
 
 由於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};
 
 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中就不需要重新對當前堆棧的值進行保存,只需進行切換就可以了。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章