13 - 可視化追蹤調試

  • 使用RTOS編程,爲每個任務分配多大的堆棧空間就成了一項技術活:分配多了浪費系統資源,分配少了又恐怕會發生堆棧溢出。由於中斷和搶佔式調度器的存在,我們要估算出一個任務需要多少堆棧是非常困難的,今天我們就介紹一種方法,來獲取每個任務的剩餘堆棧空間。本文以NXP LPC177x_8x系列微控制器爲例。
  • 我們將這個功能做成一個命令,添加到《15 - 使用任務通知實現命令行解釋器》一文介紹的命令解釋列表中。當程序運行一段時間後,我們在SecureCRT軟件中輸入命令“task”後回車,能看到如圖1-1所示的任務信息。這裏只有兩個任務,其中堆棧一列中的數字,代表對應任務剩餘的堆棧空間,單位是StackType_t類型,這個類型在移植層定義,一般定義爲4字節。
    任務信息圖1-1:任務信息

使能可視化追蹤和運行時間統計功能

  • 如圖1-1所示,要實現堆棧使用量信息以及CPU使用率信息,必須將FreeRTOSConfig.h文件中的兩個宏設置爲1:
#define configUSE_TRACE_FACILITY	1      
#define configGENERATE_RUN_TIME_STATS 	1
  • 第一個宏用來使能可視化追蹤功能,第二個宏用來使能運行時間統計功能。如果第二個宏設置爲1,則下面兩個宏必須被定義:
  1. portCONFIGURE_TIMER_FOR_RUN_TIME_STATS():用戶程序需要提供一個基準時鐘函數,函數完成初始化基準時鐘功能,這個函數要被define到宏portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()上。這是因爲運行時間統計需要一個比系統節拍中斷頻率還要高分辨率的基準定時器,否則,統計可能不精確。基準定時器中斷頻率要比統節拍中斷快10~100倍。基準定時器中斷頻率越快,統計越精準,但能統計的運行時間也越短(比如,基準定時器10ms中斷一次,8位無符號整形變量可以計到2.55秒,但如果是1秒中斷一次,8位無符號整形變量可以統計到255秒)。
  2. portGET_RUN_TIME_COUNTER_VALUE():用戶程序需要提供一個返回基準時鐘當前“時間”的函數,這個函數要被define到宏portGET_RUN_TIME_COUNTER_VALUE()上。
  • 我們使用定時器1來產生基準時鐘,定時器1初始化函數爲:
/**
* 初始化計時定時器1,用於OS任務運行時間統計
*/
void init_timer1_for_runtime_state(void)
{
    TIM_TIMERCFG_Type Timer0CfgType;
                
    Timer0CfgType.PrescaleOption=TIM_PRESCALE_USVAL;        //預分頻的單位是微秒
    Timer0CfgType.PrescaleValue=500;                        //預分頻後爲500微秒,
    TIM_Init(LPC_TIM1,TIM_TIMER_MODE,&Timer0CfgType);
   
    LPC_TIM1->TCR=0x01;
}
  • 定時器1被配置成每隔500微秒,TC寄存器值增一。我們將定時器1的 TC寄存器值作爲基準時鐘當前時間。當TC寄存器值溢出時,大概要經過24.8天,這對於我們這個應用是足夠的。
  • 在FreeRTOSConfig.h中,定義初始化基準定時器宏和獲取當前時間宏:
extern void init_timer1_for_runtime_state(void);
#define TIMER1_TC         ( * ( ( volatile uint32_t * )0x40008008 ) )
#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() 	init_timer1_for_runtime_state()
#define portGET_RUN_TIME_COUNTER_VALUE() 	TIMER1_TC

獲取任務信息並格式化

  • 獲取每個任務的狀態信息使用的是API函數uxTaskGetSystemState(),該函數定義爲:
UBaseType_tuxTaskGetSystemState(
                       TaskStatus_t * constpxTaskStatusArray,
                       const UBaseType_tuxArraySize,
                       unsigned long * constpulTotalRunTime );
  • 函數uxTaskGetSystemState()向TaskStatus_t結構體填充相關信息,系統中每一個任務的信息都可以填充到TaskStatus_t結構體數組中,數組大小由uxArraySize指定。結構體TaskStatus_t定義如下:
typedef struct xTASK_STATUS
{
   /* 任務句柄*/
   TaskHandle_t xHandle;
 
   /* 指針,指向任務名*/
   const signed char *pcTaskName;
 
   /*任務ID,是一個獨一無二的數字*/
   UBaseType_t xTaskNumber;
 
   /*填充結構體時,任務當前的狀態(運行、就緒、掛起等等)*/
   eTaskState eCurrentState;
 
   /*填充結構體時,任務運行(或繼承)的優先級。*/
   UBaseType_t uxCurrentPriority;
 
   /* 當任務因繼承而改變優先級時,該變量保存任務最初的優先級。僅當configUSE_MUTEXES定義爲1有效。*/
   UBaseType_t uxBasePriority;
 
   /* 分配給任務的總運行時間。僅當宏configGENERATE_RUN_TIME_STATS爲1時有效。*/
   unsigned long ulRunTimeCounter;
 
   /* 從任務創建起,堆棧剩餘的最小數量,這個值越接近0,堆棧溢出的可能越大。 */
   unsigned short usStackHighWaterMark;
}TaskStatus_t;
  • 注意,這個函數僅用來調試用,調用此函數會掛起所有任務,直到函數結束後才恢復掛起的任務,因此任務可能被掛起很長時間。在文件FreeRTOSConfig.h中,宏configUSE_TRACE_FACILITY必須設置爲1,此函數纔有效。
  • 由於我們不使用動態內存分配策略,所以實現定義了最大任務個數並預先分配好了存儲任務狀態信息的數組:
#defineMAX_TASK_NUM 	5
TaskStatus_tpxTaskStatusArray[MAX_TASK_NUM];
  • 正確調用函數uxTaskGetSystemState()後,任務的信息會被放在TaskStatus_t結構體中,我們需要將這些信息格式化爲容易閱讀的形式,並共通過串口打印到屏幕。完成這些功能的函數叫做get_task_state(),代碼如下所示:
/*獲取OS任務信息*/
voidget_task_state(int32_t argc,void *cmd_arg)
{
    const chartask_state[]={'r','R','B','S','D'};
    volatile UBaseType_t uxArraySize, x;
    uint32_t ulTotalRunTime,ulStatsAsPercentage;
 
    /* 獲取任務總數目 */
    uxArraySize = uxTaskGetNumberOfTasks();
   if(uxArraySize>MAX_TASK_NUM)
    {
        MY_DEBUGF(CMD_LINE_DEBUG,("當前任務數量過多!\n"));
    }
 
    /*獲取每個任務的狀態信息 */
    uxArraySize = uxTaskGetSystemState(pxTaskStatusArray, uxArraySize, &ulTotalRunTime );
 
    #if (configGENERATE_RUN_TIME_STATS==1)
   
    MY_DEBUGF(CMD_LINE_DEBUG,("任務名      狀態  ID    優先級  堆棧    CPU使用率\n"));
 
    /* 避免除零錯誤 */
    if( ulTotalRunTime > 0 )
    {
        /* 將獲得的每一個任務狀態信息部分的轉化爲程序員容易識別的字符串格式 */
        for( x = 0; x < uxArraySize; x++ )
        {
            char tmp[128];
           
            /* 計算任務運行時間與總運行時間的百分比。*/
            ulStatsAsPercentage =(uint64_t)(pxTaskStatusArray[ x ].ulRunTimeCounter)*100 / ulTotalRunTime;
 
            if( ulStatsAsPercentage > 0UL )
            {
 
               sprintf(tmp,"%-12s%-6c%-6d%-8d%-8d%d%%",pxTaskStatusArray[ x].pcTaskName,task_state[pxTaskStatusArray[ x ].eCurrentState],
                                                                       pxTaskStatusArray[ x ].xTaskNumber,pxTaskStatusArray[ x].uxCurrentPriority,
                                                                       pxTaskStatusArray[ x ].usStackHighWaterMark,ulStatsAsPercentage);
            }
            else
            {
                /* 任務運行時間不足總運行時間的1%*/
                sprintf(tmp,"%-12s%-6c%-6d%-8d%-8dt<1%%",pxTaskStatusArray[x ].pcTaskName,task_state[pxTaskStatusArray[ x ].eCurrentState],
                                                                       pxTaskStatusArray[ x ].xTaskNumber,pxTaskStatusArray[ x].uxCurrentPriority,
                                                                       pxTaskStatusArray[ x ].usStackHighWaterMark);               
            }
           MY_DEBUGF(CMD_LINE_DEBUG,("%s\n",tmp));
        }
    }
    MY_DEBUGF(CMD_LINE_DEBUG,("任務狀態:   r-運行  R-就緒  B-阻塞  S-掛起  D-刪除\n"));
    #endif //#if (configGENERATE_RUN_TIME_STATS==1)
}

添加到命令解釋列表

  • 在《FreeRTOS系列第15篇—使用任務通知實現命令行解釋器》一文我們講過了命令表,這裏只需要將get_task_state()函數添加到命令列表中,命令設置爲”task”,代碼如下所示:
/*命令表*/
const cmd_list_structcmd_list[]={
/*   命令    參數數目    處理函數        幫助信息                                  */   
    {"?",       0,     handle_help,     "?                                  -打印幫助信息"},                 
    {"reset",   0,     handle_reset,    "reset                              -重啓控制器"},
    {"arg",     8,     handle_arg,      "arg<arg1> <arg2> ...               -測試用,打印輸入的參數"},
    {"hello",   0,     printf_hello,    "hello                              -打印HelloWorld!"},
    {"task",    0,     get_task_state,  "task                               -獲取任務信息"},
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章