- 使用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,則下面兩個宏必須被定義:
- portCONFIGURE_TIMER_FOR_RUN_TIME_STATS():用戶程序需要提供一個基準時鐘函數,函數完成初始化基準時鐘功能,這個函數要被define到宏portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()上。這是因爲運行時間統計需要一個比系統節拍中斷頻率還要高分辨率的基準定時器,否則,統計可能不精確。基準定時器中斷頻率要比統節拍中斷快10~100倍。基準定時器中斷頻率越快,統計越精準,但能統計的運行時間也越短(比如,基準定時器10ms中斷一次,8位無符號整形變量可以計到2.55秒,但如果是1秒中斷一次,8位無符號整形變量可以統計到255秒)。
- portGET_RUN_TIME_COUNTER_VALUE():用戶程序需要提供一個返回基準時鐘當前“時間”的函數,這個函數要被define到宏portGET_RUN_TIME_COUNTER_VALUE()上。
- 我們使用定時器1來產生基準時鐘,定時器1初始化函數爲:
void init_timer1_for_runtime_state(void)
{
TIM_TIMERCFG_Type Timer0CfgType;
Timer0CfgType.PrescaleOption=TIM_PRESCALE_USVAL;
Timer0CfgType.PrescaleValue=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;
UBaseType_t xTaskNumber;
eTaskState eCurrentState;
UBaseType_t uxCurrentPriority;
UBaseType_t uxBasePriority;
unsigned long ulRunTimeCounter;
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(),代碼如下所示:
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
{
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
}
添加到命令解釋列表
- 在《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 -獲取任務信息"},
};