手把手,嘴對嘴,講解UCOSII嵌入式操做系統的任務(二)

本章重點講解空閒任務的創建過程

任務創建函數定義以下:html

 1 INT8U  OSTaskCreate (void   (*task)(void *p_arg),
 2                      void    *p_arg,
 3                      OS_STK  *ptos,
 4                      INT8U    prio)
 5 {
 6     OS_STK    *psp;
 7     INT8U      err;
 8 #if OS_CRITICAL_METHOD == 3u                 /* Allocate storage for CPU status register               */
 9     OS_CPU_SR  cpu_sr = 0u;
10 #endif
11 
12 
13 
14 #ifdef OS_SAFETY_CRITICAL_IEC61508
15     if (OSSafetyCriticalStartFlag == OS_TRUE) {
16         OS_SAFETY_CRITICAL_EXCEPTION();
17     }
18 #endif
19 
20 #if OS_ARG_CHK_EN > 0u
21     if (prio > OS_LOWEST_PRIO) {             /* Make sure priority is within allowable range           */
22         return (OS_ERR_PRIO_INVALID);
23     }
24 #endif
25     OS_ENTER_CRITICAL();
26     if (OSIntNesting > 0u) {                 /* Make sure we don't create the task from within an ISR  */
27         OS_EXIT_CRITICAL();
28         return (OS_ERR_TASK_CREATE_ISR);
29     }
30     if (OSTCBPrioTbl[prio] == (OS_TCB *)0) { /* Make sure task doesn't already exist at this priority  */
31         OSTCBPrioTbl[prio] = OS_TCB_RESERVED;/* Reserve the priority to prevent others from doing ...  */
32                                              /* ... the same thing until task is created.              */
33         OS_EXIT_CRITICAL();
34         psp = OSTaskStkInit(task, p_arg, ptos, 0u);             /* Initialize the task's stack         */
35         err = OS_TCBInit(prio, psp, (OS_STK *)0, 0u, 0u, (void *)0, 0u);
36         if (err == OS_ERR_NONE) {
37             if (OSRunning == OS_TRUE) {      /* Find highest priority task if multitasking has started */
38                 OS_Sched();
39             }
40         } else {
41             OS_ENTER_CRITICAL();
42             OSTCBPrioTbl[prio] = (OS_TCB *)0;/* Make this priority available to others                 */
43             OS_EXIT_CRITICAL();
44         }
45         return (err);
46     }
47     OS_EXIT_CRITICAL();
48     return (OS_ERR_PRIO_EXIST);
49 }

 

21~23行,判斷咱們傳遞進來的參數優先級是否合法,若是不知足,直接退出(當前系統支持最大64個任務,所以優先級必須小於64)。編程

26~29行,判斷當前系統的中斷狀態,變量OSIntNesting的意義以前講過,若是它大於0,那就表明目前處於中斷服務程序中,在中斷中系統是不容許創建新任務的。數組

30行,首先判斷任務是否已經存在,若是已經存在則跳出,數組OSTCBPrioTbl[prio]的下標是優先級,內容是任務的相關的信息,若是該優先級的任務已創建,那麼其內容必然不等於0.數據結構

31行,當任務不存在時,隨便賦個值給這個任務的管理數組,至關因而在這個元素上作個標誌,告訴系統,這個位置要保留下來不容許別人動,直到這個任務真正創建成功。ide

34行所調用的函數是初始化任務的堆棧,咱們如今詳細看看內部的處理,究竟是如何進行初始化的。函數

棧空間

函數OSTaskStkInit定義以下:this

 1 OS_STK *OSTaskStkInit (void (*task)(void *p_arg), void *p_arg, OS_STK *ptos, INT16U opt)
 2 {
 3     OS_STK *stk;
 4 
 5 
 6     (void)opt;                                   /* 'opt' is not used, prevent warning                 */
 7     stk       = ptos;                            /* Load stack pointer                                 */
 8 
 9                                                  /* Registers stacked as if auto-saved on exception    */
10     *(stk)    = (INT32U)0x01000000L;             /* xPSR                                               */
11     *(--stk)  = (INT32U)task;                    /* Entry Point                                        */
12     *(--stk)  = (INT32U)0xFFFFFFFEL;             /* R14 (LR) (init value will cause fault if ever used)*/
13     *(--stk)  = (INT32U)0x12121212L;             /* R12                                                */
14     *(--stk)  = (INT32U)0x03030303L;             /* R3                                                 */
15     *(--stk)  = (INT32U)0x02020202L;             /* R2                                                 */
16     *(--stk)  = (INT32U)0x01010101L;             /* R1                                                 */
17     *(--stk)  = (INT32U)p_arg;                   /* R0 : argument                                      */
18 
19                                                  /* Remaining registers saved on process stack         */
20     *(--stk)  = (INT32U)0x11111111L;             /* R11                                                */
21     *(--stk)  = (INT32U)0x10101010L;             /* R10                                                */
22     *(--stk)  = (INT32U)0x09090909L;             /* R9                                                 */
23     *(--stk)  = (INT32U)0x08080808L;             /* R8                                                 */
24     *(--stk)  = (INT32U)0x07070707L;             /* R7                                                 */
25     *(--stk)  = (INT32U)0x06060606L;             /* R6                                                 */
26     *(--stk)  = (INT32U)0x05050505L;             /* R5                                                 */
27     *(--stk)  = (INT32U)0x04040404L;             /* R4                                                 */
28 
29     return (stk);
30 }

這個函數該怎麼解釋呢?spa

它其實並非真正的去初始化了CPU的棧空間,只是對一個模擬的棧進行了初始化。操作系統

作過單片機程序的同窗都很清楚,在程運行中總遇到中斷髮生,當這個時候就必需要跳進中斷中去運行,但在跳進中斷服務函數以前,咱們必需要作一些處理,好比保存現場,將當前的數據和寄存器值押入棧中,而後在去執行中斷函數,由於只有這樣作了,在執行完中斷函數之後才能找回原點繼續執行剩下的代碼(彙編語言須要本身壓棧和出棧,C語言由系統自動完成)。線程

單片機裸機程序是單線程,代碼的執行邏輯始終處於一條時間線,就算是發生了中斷嵌套,但在某一個具體的時間點,它也是單線的,所以只須要保存一個現場就能夠回到原點。

但在嵌入式操做系統中同時執行了多個任務,每一個任務之間都有可能發生切換,任務與任務之間不存在調用的關係,是相對獨立的存在,任務的這個切換能夠理解爲單片機中發生的中斷,並且切換的順序並非線性的,一旦在任務過多的狀況下,若是依然使用CPU本身的棧空間,那這樣就會致使棧空間不夠用,棧溢出,並且並且很是不方便。

因此,UCOSII系統對於每個任務都建立了一個數組來模擬它的棧空間,好比這個空閒任務,咱們跟蹤它的參數能夠知道:

OS_EXT  OS_STK            OSTaskIdleStk[OS_TASK_IDLE_STK_SIZE];      /* Idle task stack                */
#define OS_TASK_IDLE_STK_SIZE   128u   /* Idle       task stack size (# of OS_STK wide entries)        */

系統定義好了一個128長度的數組來模擬空閒任務的棧空間,專門用來存在和這個任務有關的寄存器數據,當咱們從空閒任務跳出去的時候,就會把相應的信息保存在裏面,等須要跳回的時候,就會從裏面取出相應的信息,功能和CPU的棧空間徹底同樣。

咱們能夠用keil仿真一下。

首先在下面進入棧初始化以前打上斷點:

 

 這個時候咱們能夠看看空閒任務的棧數組,也就是OSTaskIdleStk[],他此刻的狀態以下:

這個數組的長度是128個字節,我不可能所有截完,不過它其中的內容都如上所示,所有是0x0000。

如今咱們在函數的出口後打上斷點,而後全速運行,觀察一下空閒任務的棧初始化完成之後是什麼樣子的,代碼以下:

棧空間初始化完後結果以下:

 

咱們能看出來有些空間被填充了,因爲M3的棧空間是由高往低增加,所以數組的最後幾個字節被填充上的內容,其中第126個元素保存的是空閒任務的任務函數地址,第120個元素保存的傳遞進來的參數,剩下的那些當即數是模擬的CPU的寄存器,至於那些當即數的內容不用關心,基本是用於開發人員調試用的。

在咱們之前進行單片機C語言的編程中,在一個函數被調用以前,它的堆棧信息的初始化,壓棧,出棧都是mcu自行進行的,不須要工程師干預,而在UCOSII系統中,因爲MCU的棧空間只有一個,而咱們的任務又太多,所以便須要由人工來進行管理,實際上OSTaskStkInit函數即是模擬了這種動做。

 

任務信息

空閒任務的棧空間初始化完成之後,接下來便須要對任務自己的一些信息進行處理,請接着看下一個函數:OS_TCBInit()

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

 

其簡化後,定義以下:

 1 INT8U  OS_TCBInit (INT8U    prio,
 2                    OS_STK  *ptos,
 3                    OS_STK  *pbos,
 4                    INT16U   id,
 5                    INT32U   stk_size,
 6                    void    *pext,
 7                    INT16U   opt)
 8 {
 9     OS_TCB    *ptcb;
10 #if OS_CRITICAL_METHOD == 3u                               /* Allocate storage for CPU status register */
11     OS_CPU_SR  cpu_sr = 0u;
12 #endif
13 #if OS_TASK_REG_TBL_SIZE > 0u
14     INT8U      i;
15 #endif
16 
17 
18     OS_ENTER_CRITICAL();
19     ptcb = OSTCBFreeList;                                  /* Get a free TCB from the free TCB list    */
20     if (ptcb != (OS_TCB *)0) {
21         OSTCBFreeList            = ptcb->OSTCBNext;        /* Update pointer to free TCB list          */
22         OS_EXIT_CRITICAL();
23         ptcb->OSTCBStkPtr        = ptos;                   /* Load Stack pointer in TCB                */
24         ptcb->OSTCBPrio          = prio;                   /* Load task priority into TCB              */
25         ptcb->OSTCBStat          = OS_STAT_RDY;            /* Task is ready to run                     */
26         ptcb->OSTCBStatPend      = OS_STAT_PEND_OK;        /* Clear pend status                        */
27         ptcb->OSTCBDly           = 0u;                     /* Task is not delayed                      */
28 
30         pext                     = pext;                   /* Prevent compiler warning if not used     */
31         stk_size                 = stk_size;
32         pbos                     = pbos;
33         opt                      = opt;
34         id                       = id;
35 
36 #if OS_TASK_DEL_EN > 0u
37         ptcb->OSTCBDelReq        = OS_ERR_NONE;
38 #endif
39 
40         ptcb->OSTCBY             = (INT8U)(prio >> 3u);
41         ptcb->OSTCBX             = (INT8U)(prio & 0x07u);
42 
43         ptcb->OSTCBBitY          = (OS_PRIO)(1uL << ptcb->OSTCBY);
44         ptcb->OSTCBBitX          = (OS_PRIO)(1uL << ptcb->OSTCBX);
45 
46 #if OS_TASK_REG_TBL_SIZE > 0u                              /* Initialize the task variables            */
47         for (i = 0u; i < OS_TASK_REG_TBL_SIZE; i++) {
48             ptcb->OSTCBRegTbl[i] = 0u;
49         }
50 #endif
51 
52         OSTCBInitHook(ptcb);
53 
54         OSTaskCreateHook(ptcb);                            /* Call user defined hook                   */
55 
56         OS_ENTER_CRITICAL();
57         OSTCBPrioTbl[prio] = ptcb;
58         ptcb->OSTCBNext    = OSTCBList;                    /* Link into TCB chain                      */
59         ptcb->OSTCBPrev    = (OS_TCB *)0;
60         if (OSTCBList != (OS_TCB *)0) {
61             OSTCBList->OSTCBPrev = ptcb;
62         }
63         OSTCBList               = ptcb;
64         OSRdyGrp               |= ptcb->OSTCBBitY;         /* Make task ready to run                   */
65         OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;
66         OSTaskCtr++;                                       /* Increment the #tasks counter             */
67         OS_EXIT_CRITICAL();
68         return (OS_ERR_NONE);
69     }
70     OS_EXIT_CRITICAL();
71     return (OS_ERR_TASK_NO_MORE_TCB);
72 }

 

第19行,ptcb只是一個局部指針,暫時尚未指向任何地址,所以首先須要從剛纔創建的空閒鏈表中取出一個實際地址。

第20行,判斷該地址是否有效(不等於0)。

第21行,因爲咱們從空閒鏈表取出了一塊地址,那麼這個空閒鏈表的首地址就不能用了,因此須要把它的首地址指向下一個位置,以便從此其餘任務的創建。

第23~27行,這幾句話是設定任務的參數,好比棧地址,優先級,任務狀態,延時參數等等。

第40~44行,這幾句話和優先級策略以及從此的任務切換有關,詳細可參考:http://www.cnblogs.com/han-bing/p/8882375.html  

第52~53行,這是鉤子函數的調用,無需多言。

第57行,這是將已經初始化完成的任務鏈表TCB賦值給全局的任務數組,這個數組一共有64個元素,其中是按照優先級從高到低來排序。

第58~59行,更新任務鏈表的下一個元素和上一個元素,也就是把新建的這個任務塊,連接到總的任務控制連接裏面。

第60~62行,判斷當前任務鏈表的狀態(因爲空閒任務是第一個建立的任務,所以任務控制鏈表仍是空的,上一個連接天然也不存在,因此並不會執行if判斷裏面代碼,ptcb->OSTCBPrev被賦值爲0)。

第63行,把咱們新建的這個局部任務塊,連接到總任務鏈表裏面,從這時開始,OSTCBList就不在是空的了,裏面有了第一個成員。

第64~65行,這兩句話是任務優先級的管理表更新,詳細可參考:http://www.cnblogs.com/han-bing/p/8882375.html  

第66行,任務個數管理變量加1,表明系統創建了多少個任務。

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

任務創建的過程整體而言不算簡單也不算複雜,若是你已經掌握了鏈表等數據結構的知識,那麼對於這段應該很容易掌握,不過如果之前對鏈表之類的徹底不瞭解,那就須要多花費一點時間了。

對於新手而言,上面出現的各式各樣的鏈表和數組可能看起來彷佛有些眼花繚亂,以爲UCOSII系統太耗費資源,那麼大的結構體做成一個64個元素的數組,並且還有好幾個……其實,鏈表和數組不一樣,無論所管理的空間有多大,鏈表並不會佔據實際的數據結構內存(固然,指針自己仍是要佔一點內存的,只不過僅僅是一個指針的地址,並不會佔據數據結構那麼大的內存),別看咱們上面提到了那麼多種鏈表,但不論是任務管理鏈表(OSTCBList),空閒鏈表(OSTCBFreeList)其實他們管理的都是同一段內存,也就是數組OSTCBTbl[OS_MAX_TASKS + OS_N_SYS_TASKS]。

就像一條街道擁有的不一樣叫法,老年人叫「愛國路」,年輕人叫「歡樂路」,小朋友叫「玩耍路」……他們所知的都是同一條街道,只是名字不一樣罷了。

若是不明白,請看看代碼中的定義:

1 OS_EXT  OS_TCB           *OSTCBCur;                        /* Pointer to currently running TCB         */
2 OS_EXT  OS_TCB           *OSTCBFreeList;                   /* Pointer to list of free TCBs             */
3 OS_EXT  OS_TCB           *OSTCBHighRdy;                    /* Pointer to highest priority TCB R-to-R   */
4 OS_EXT  OS_TCB           *OSTCBList;                       /* Pointer to doubly linked list of TCBs    */
5 OS_EXT  OS_TCB           *OSTCBPrioTbl[OS_LOWEST_PRIO + 1u];    /* Table of pointers to created TCBs   */
6 OS_EXT  OS_TCB            OSTCBTbl[OS_MAX_TASKS + OS_N_SYS_TASKS];   /* Table of TCBs                  */

 

前面5個數據定義的類型都是指向別的數據的指針,只有最後一個數組定義的纔是佔據實際內存的數據,UCOSII系統中所謂的任務管理,其實也就是把這幾個鏈表指向的地址根據須要反覆的修改,可是永遠限定在OSTCBTbl數組中,就像孫猴子不管怎麼飛永遠也逃不出如來佛的手掌心,這樣從而達到管理任務的目的。

當空閒任務未創建以前,咱們看看具體的數據結構:

 

首先是那個數組:OSTCBTbl

  

這是它的內存分部狀態,它是實際佔據內存的數據。

在系統剛剛初始化開始,尚未新建任何任務的時候,咱們用這個數組和空閒鏈表作一個比較。

 

                           數組:OSTCBTbl                                                                                   空閒鏈表:OSTCBFreeList

     

由以上兩張地址信息對比,能夠看出,空閒鏈表中指向下一個結構的地址,其實就是數組元素的地址,所以它管理的是數組元素。

 這個時候(沒有任何任務創建,包括空閒任務),別的鏈表都是空:

 

 

當空閒任務創建以後,咱們再看看具體的數據結構:

首先是那個數組:OSTCBTbl

   

 這個數組的內容固然是不會變的,無論任務是否創建,無論任務是否存在,它元素的地址永遠都不會變。

 

在系統建完成空閒任務後,咱們用這個數組和空閒鏈表作一個比較。

 

                    數組:OSTCBTbl                                                                                                                  空閒鏈表:OSTCBFreeList

 

兩張圖一對比便不難發現,空閒鏈表的數據內容發生了變化,具體即是,它全部的地址都往前縮進了一位,數組OSTCBTbl的首地址:0x20000A48,已經沒法在空閒鏈表中找到了,空閒鏈表的首地址變成了,數組的第二個元素的地址。

此時此刻空閒任務已經創建完成,所以,空閒鏈表最開始的那個地址已經不空閒了,名花有主了,因此它的首地址只能指向像一個尚未被分配出去的地址,若是咱們再創建一個任務,那麼空閒鏈表的首地址還會繼續向上縮進一個,由於它所指向的,必須是這個數組中尚未被分配出去的那段空間。

咱們再繼續看看別的鏈表。
好比說任務管理鏈表的狀態:

                    數組:OSTCBTbl                                                                                                                                                             任務管理鏈表:OSTCBList

       

 由於咱們已經創建了一個任務,所以任務管理鏈表的首地址,天然就指向了那個僅存的空閒任務,至關於從空閒鏈表中拿出去,放進了任務管理鏈表中,每創建一個任務都是如此,不停的從空閒鏈表中拿出去,而後放進任務管理鏈表,任務的刪除即是反向操做,從任務管理鏈表中拿出去,放進空閒鏈表。

 至於別的兩個鏈表,此刻依然是空,由於雖然創建了一個任務,但這個任務還沒跑起來,等它跑起來後這兩個鏈表裏確定就不是空了。

 優先級鏈表:OSTCBPrioTbl

因爲空閒任務的創建,優先級管理鏈表中也有數據了,因爲空閒任務的優先級是63,因此按照它的排序規則,放進最後一個元素中,能夠看到它的地址正是那個空閒任務的地址,這也就意味着,他們管理的是同一段數據。

 

小結:在UCOSII中,系統所佔據的內存其實並不大,雖然裏面的各類鏈表比較多,但都管理的同一段內存,只是根據須要和意義賦予了不一樣的名字。

鐵打的數組,流水的鏈表,數組就像一棵巍然不動的千年老鬆,任憑風吹雨打,那些鏈表就像一堆迎風就倒的牆頭小草,它們所管理的範圍是變更的。

任務管理其實也很簡單,它的本質,就是對任務數組的操做,增長、刪除、排序……等等。

任務數組:OSTCBTbl,它裏面是按照任務創建的時間來存聽任務。

優先級數組:OSTCBPrioTbl,它裏面是按照任務的優先級,對任務數組進行了一次排序。

所以,空閒任務的創建過程,首先是把空閒任務的信息放進數組OSTCBTbl的第1個位置(創建時間最先),再把空閒任務的信息放進數組OSTCBPrioTbl的最後一個位置(優先級最低)。

待續……

相關文章
相關標籤/搜索