uC/OS-II源码分析(总体思路 二)
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) {????????????
??????? return (OS_PRIO_INVALID);
??? }
#endif
??? OS_ENTER_CRITICAL();
??? if (OSIntNesting > 0) {?????????????????
??????? OS_EXIT_CRITICAL();
??????? return (OS_ERR_TASK_CREATE_ISR);
??? }
??? if (OSTCBPrioTbl[prio] == (OS_TCB *)0) {
??????? OSTCBPrioTbl[prio] = (OS_TCB *)1;???
????????????????????????????????????????????
??????? OS_EXIT_CRITICAL();
??????? psp = (OS_STK *)OSTaskStkInit(task, p_arg, ptos, 0);???
??????? err = OS_TCBInit(prio, psp, (OS_STK *)0, 0, 0, (void *)0, 0);
??????? if (err == OS_NO_ERR) {
??????????? if (OSRunning == TRUE) {????????
??????????????? OS_Sched();
??????????? }
??????? } else {
??????????? OS_ENTER_CRITICAL();
??????????? OSTCBPrioTbl[prio] = (OS_TCB *)0;
??????????? 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;?????????????????????????????????
??? if (ptcb != (OS_TCB *)0) {
??????? OSTCBFreeList??????? = ptcb->OSTCBNext;???????????
??????? OS_EXIT_CRITICAL();
??????? ptcb->OSTCBStkPtr??? = ptos;??????????????????????
??????? ptcb->OSTCBPrio????? = prio;??????????????????????
??????? ptcb->OSTCBStat????? = OS_STAT_RDY;???????????????
??????? ptcb->OSTCBPendTO??? = FALSE;?????????????????????
??????? ptcb->OSTCBDly?????? = 0;?????????????????????????
#if OS_TASK_CREATE_EXT_EN > 0
??????? ptcb->OSTCBExtPtr??? = pext;??????????????????????
??????? ptcb->OSTCBStkSize?? = stk_size;??????????????????
??????? ptcb->OSTCBStkBottom = pbos;??????????????????????
??????? ptcb->OSTCBOpt?????? = opt;???????????????????????
??????? ptcb->OSTCBId??????? = id;????????????????????????
#else
??????? pext???????????????? = pext;??????????????????????
??????? 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);????????
??????? ptcb->OSTCBBitY????? = OSMapTbl[ptcb->OSTCBY];
??????? ptcb->OSTCBX???????? = (INT8U)(prio & 0x07);
??????? ptcb->OSTCBBitX????? = OSMapTbl[ptcb->OSTCBX];
#if OS_EVENT_EN
??????? ptcb->OSTCBEventPtr? = (OS_EVENT *)0;?????????????
#endif
??????? OSTaskCreateHook(ptcb);???????????????????????????
???????
??????? OS_ENTER_CRITICAL();
??????? OSTCBPrioTbl[prio] = ptcb;
??????? ptcb->OSTCBNext??? = OSTCBList;???????????????????
??????? ptcb->OSTCBPrev??? = (OS_TCB *)0;
??????? if (OSTCBList != (OS_TCB *)0) {
??????????? OSTCBList->OSTCBPrev = ptcb;
??????? }
??????? OSTCBList?????????????? = ptcb;
??????? OSRdyGrp?????????????? |= ptcb->OSTCBBitY;????????
??????? OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;
??????? OSTaskCtr++;??????????????????????????????????????
??????? 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,??????
???? 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,??????
???? 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,??????
???? 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,??????
???? 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,??????
???? 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,??????
???? 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,??????
???? 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,??????
???? 7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,??????
???? 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,??????
???? 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,??????
???? 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,??????
???? 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,??????
???? 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,??????
???? 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,??????
???? 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0???????
?};
?OSMapTbl分別是一個INT8U的八個位,而OSUnMap數組中的值就是從0x00到0xFF的八位中,每一個值所對應的最低位的值。我們在調度的時候只需將OSRdyGrp的值代入OSUnMapTbl數組中,得到OSUnMapTbl[OSRdyGrp]的值就是哪個優先級最高的Group有Ready進程存在,再使用該Group對應OSRdyTbl[]數組中的值一樣帶入OSUnMapTbl中就可以得出哪個Task是優先級最高的。
?于是我們提前來看看OS_Sched()中獲取最高優先級所使用的方法:
?y???????????? = OSUnMapTbl[OSRdyGrp];?????
??? 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];???????
??????? x???????????? = OSUnMapTbl[OSRdyTbl[y]];
??????? OSPrioHighRdy = (INT8U)((y << 3) + x);
??????? OSPrioCur???? = OSPrioHighRdy;
??????? OSTCBHighRdy? = OSTCBPrioTbl[OSPrioHighRdy];
??????? OSTCBCur????? = OSTCBHighRdy;
??????? OSStartHighRdy();???????????????????????????
??? }
}
如果OSRunning為TRUE,表示OS已經在運行了,則OSStart不做任何事。
OSRunning為FALSE,則找出最高優先級的Ready的Task,并將該指針賦給OSTCBHighRdy和OSTCBCur。然后調用OSStartHighRdy()開始運行該進程。
OSStartHighRdy()為用戶自定義函數,在這個函數中,主要功能就是進行堆棧切換并將OSRunning設置為TRUE表示OS已經開始運行,然后將保存的寄存器彈出,最后執行中斷返回指令IRET就跳到OSTCBHighRdy的最開始處運行了。
?
?
總結
以上是生活随笔為你收集整理的uC/OS-II源码分析(总体思路 二)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 四年来首次!苹果营收出现下滑 iPhon
- 下一篇: uC/OS-II源码分析(总体思路 三)