1. 程式人生 > >uCOS-III任務堆疊溢位檢測及統計任務堆疊使用量的方法【轉載】

uCOS-III任務堆疊溢位檢測及統計任務堆疊使用量的方法【轉載】

此文章轉載於點選進入原創地址

uCOS-III任務堆疊溢位檢測及統計任務堆疊使用量的方法

  1. 在作業系統任務設計的時候,通常會遇到一個比較麻煩的問題,也就是任務堆疊大小設定的問題,為此我們我需要知道一些問題:
    1.1. 任務堆疊一但溢位,意味著系統的崩潰,在有MMU或者MPU的系統中,對堆疊溢位的檢測十分簡單,因為這是MMU和MPU必備的功能之一。(uCOS-II/uCOS-III中均有針對沒有MMU和MPU的處理器對堆疊溢位檢測的策略)

1.2. 堆疊的大小取決於該任務的需求。設定堆疊大小時,你就需要考慮:所有可能被堆疊呼叫的函式及其函式的巢狀層數,相關區域性變數的大小,中斷服務程式所需要的空間。另外,堆疊還需存入CPU暫存器,如果處理器有浮點數單元FPU暫存器的話還需存入FPU暫存器。(PS:出於這點,所以在嵌入式系統中有個潛規則,避免寫遞迴函式)

1.3. 雖然任務堆疊大小可以通過人工計算出來,但是要考慮的太多,而且不能十分精確的計算。比如逐級巢狀被呼叫的函式的引數使用,上下文切換時候的CPU暫存器空間的儲存,中斷時CPU暫存器空間的儲存和中斷處理函式的堆疊空間等等,未免太過麻煩。特別的,當任務中使用了printf()之類引數可變的函式,那麼統計起來就更困難了。所以這種方式怎麼看怎麼不現實。囧 。

1.4. 建議在不是很精確的確定任務堆疊使用大小(stk_size)的情況下,還是採取stk_size乘以1.5或者2.0的做法,以保證任務的正常執行。

  1. uCOS-III任務堆疊溢位檢測原理
    每個任務都有自己的TCB(Task Control Block 任務控制塊),TCB結構定義在uCOS-III原始碼(我使用的是V3.03.00版本)中的os.h中。TCB中有個StkLimitPtr成員。
    假設在切換到任務S前,程式碼會檢測將要被載入CPU堆疊指標的值是否超出該任務S的TCB中StkLimitPtr限制。因為軟體不能在溢位時就迅速地做出反應,所以應該設定StkLimitPtr的值儘可能遠離&MyTaskStk[0],保證有足夠的溢位緩衝。如下圖。軟體檢測不會像硬體檢測那樣有效,但也可以防止堆疊溢位。當uC/OS-III從一個任務切換到另一個任務的時候,它會呼叫一個hook函式OSTaskSwHook(),它允許使用者擴充套件上下文切換時的功能。所以,如果處理器沒有硬體支援溢位檢測功能,就可以在該hook函式中新增程式碼軟體模擬該能。
    不過我個人的做法是,通常設定StkLimitPtr指向任務棧大小的90%處,然後獲取任務堆疊使用量,如果棧使用率大於90%時就必須做出警告了!下面就來介紹任務棧使用量的獲取。
    圖一
  2. uCOS-III任務堆疊使用量統計的原理和方法
    3.1 原理
    原理其實很簡單,就是統計連續為0的區域的大小就可以知道空閒棧free的大小,而任務在建立時任務棧總量TaskStkSize是確定的,那麼使用了的棧大小used = TaskStkSize - free。
    首先,當任務建立時其堆疊被清零。然後,通過一個低優先順序任務,計算該任務整個堆疊中值為0的記憶體大小。如果發現都不為0,那麼就需要擴充套件堆疊的大小。然後,調整堆疊為的相應大小。這是一種非常有效的方法。注意的是,程式需用執行很長的時間以讓堆疊達到其需要的最大值。
    見圖2。
    圖二
    3.2 方法
    uC/OS-III提供了一個函式OSTaskStkChk()用於實現這個計算功能。那麼就來看看具體怎麼做吧。
    3.2.1 首先建立一個任務來執行任務堆疊統計工作,值得注意的是,這個任務的優先順序建議設定為系統所有任務中最低的一個!
#define  SystemDatasBroadcast_PRIO            12 // 統計任務優先順序最低,我這裡是12,已經低於其他任務的優先順序了  
#define  SystemDatasBroadcast_STK_SIZE       100 // 任務的堆疊大小,做統計一般夠了,統計結果出來後不夠再加..  
OS_TCB  SystemDatasBroadcast_TCB;        // 定義統計任務的TCB  
CPU_STK SystemDatasBroadcast_STK [SystemDatasBroadcast_STK_SIZE];// 開闢陣列作為任務棧給任務使用  

static  void  AppTaskCreate(void)  
{  
  // .....  
  // 這是系統建立任務的函式,還有其他任務建立的程式碼,這裡就不貼出了  
  // .....  

  OSTaskCreate( (OS_TCB     *)&SystemDatasBroadcast_TCB,  
                (CPU_CHAR   *)"SystemDatasBroadcast",  
                (OS_TASK_PTR ) SystemDatasBroadcast,  
                (void       *) 0,  
                (OS_PRIO     ) SystemDatasBroadcast_PRIO,  
                (CPU_STK    *)&SystemDatasBroadcast_STK[0],  
                (CPU_STK_SIZE) SystemDatasBroadcast_STK_SIZE/10,/*棧溢位臨界值我設定在棧大小的90%處*/  
                (CPU_STK_SIZE) SystemDatasBroadcast_STK_SIZE,  
                (OS_MSG_QTY  ) 0,  
                (OS_TICK     ) 0,  
                (void       *) 0,  
                (OS_OPT      )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),   
                (OS_ERR     *) &err);     
}  

3.2.2 然後在任務函式SystemDatasBroadcast()中開始統計各個任務的棧使用。
在uCOS-III中提供了函式

void OSTaskStkChk(OS_TCB  *p_tcb, CPU_STK_SIZE *p_free, CPU_STK_SIZE  *p_used, OS_ERR  *p_err); 

呼叫上面這個函式就能獲取到指定任務的堆疊使用量。其中
*p_tcb:指向任務的TCB塊
*p_free:任務空閒的堆疊位元組數
*p_used:任務使用的堆疊位元組數
*p_err:函式執行結果程式碼
特別提示,如果想要使用這個功能,那麼必須在os_cfg.h這個作業系統配置檔案中開啟巨集:

#define OS_CFG_STAT_TASK_STK_CHK_EN     1u   /* Check task stacks from statistic task  

任務函式SystemDatasBroadcast()的程式碼如下:

void  SystemDatasBroadcast (void *p_arg)  
{  
  OS_ERR err;  
  CPU_STK_SIZE free,used;  
  (void)p_arg;  
  while(DEF_TRUE)  
  {  
    OSTaskStkChk (&SystemDatasBroadcast_TCB,&free,&used,&err);//  把統計任務本身的堆疊使用量也打印出來  
                                  // 然後從實驗結果看看我們設定100位元組給它是不是真的合適  
    printf("SystemDatasBroadcast  used/free:%d/%d  usage:%%%d\r\n",used,free,(used*100)/(used+free));  

    OSTaskStkChk (&Core_Page_TCB,&free,&used,&err);  
    printf("Core_Page             used/free:%d/%d  usage:%%%d\r\n",used,free,(used*100)/(used+free));  

    OSTaskStkChk (&GUIActive_TCB,&free,&used,&err);  
    printf("GUIActive             used/free:%d/%d  usage:%%%d\r\n",used,free,(used*100)/(used+free));  

    OSTaskStkChk (&KeyCheck_Process_TCB,&free,&used,&err);  
    printf("KeyCheck              used/free:%d/%d  usage:%%%d\r\n",used,free,(used*100)/(used+free));  

    OSTaskStkChk (&Light_Adjust_TCB,&free,&used,&err);  
    printf("Light_Adjust          used/free:%d/%d  usage:%%%d\r\n",used,free,(used*100)/(used+free));  

    OSTaskStkChk (&Calibrate_Process_TCB,&free,&used,&err);  
    printf("Calibrate             used/free:%d/%d  usage:%%%d\r\n",used,free,(used*100)/(used+free));  


    OSTaskStkChk (&Data_Process_TCB,&free,&used,&err);  
    printf("Data_Process          used/free:%d/%d  usage:%%%d\r\n",used,free,(used*100)/(used+free));  

    printf("\r\n\r\n\r\n");  
    OSTimeDlyHMSM(0,0,5,0,(OS_OPT)OS_OPT_TIME_DLY,(OS_ERR*)&err);  
   }  
}  

3.2.3實驗結果
上述程式碼的實驗結果如下圖所示,可以清楚的看到每個任務的堆疊的使用狀況。從結果中我們看到SystemDataBroadcast任務的100位元組的任務棧只用了58位元組,使用率為58%,還有近一半的富餘,100位元組其實是合適了的,而 58X1.5 = 87,58X2.0 = 116, [87,116]之間取一個數,就取100吧,嘿嘿!當然隨著任務功能的增加,堆疊的使用量也會隨之增加,在程式設計除錯階段,最好謹慎的多檢視任務棧使用的情況以便做出調整,當程式設計並測試完成後這些程式碼都可以去掉,然後軟體進入釋出階段。
但是請遵循一個原則:必須讓系統執行足夠久,比如儘量讓系統處於不同的執行狀態下,然後觀察任務堆疊使用的變化,找到堆疊的最高使用率,然後根據上文所說的原則按需重新分配新的任務堆疊大小。
圖三

此文章為轉載*******