FreeRTOS的任务栈的大小应该如何设置才能尽可能地不浪费内存呢?最直接的方法当然是运行一段时间任务,然后看看任务所使用的最大堆栈大小为多少,然后以此为基准多设置一点点。那么应该怎么实现呢?
在我们创建一个任务的时候,有一个宏定义tskSET_NEW_STACKS_TO_KNOWN_VALUE
,这里将其打开,这样在初始化一个任务的时候,它的任务栈的默认都填充为tskSTACK_FILL_BYTE
,这样就可以方便我们判断栈溢出、获得栈的最大使用等情况。
#define tskSET_NEW_STACKS_TO_KNOWN_VALUE 1
#define tskSTACK_FILL_BYTE ( 0xa5U )
xTaskCreate
prvInitialiseNewTask
#if( tskSET_NEW_STACKS_TO_KNOWN_VALUE == 1 )
{
/* Fill the stack with a known value to assist debugging. */
( void ) memset( pxNewTCB->pxStack, ( int ) tskSTACK_FILL_BYTE, ( size_t ) ulStackDepth * sizeof( StackType_t ) );
}
#endif /* tskSET_NEW_STACKS_TO_KNOWN_VALUE */
接下来我们就根据这个原理来输出任务栈的最大使用情况。FreeRTOS提供了一个函数uxTaskGetStackHighWaterMark
,使用这个函数需要把宏定义INCLUDE_uxTaskGetStackHighWaterMark
打开。
#define INCLUDE_uxTaskGetStackHighWaterMark 1
UBaseType_t uxTaskGetStackHighWaterMark( TaskHandle_t xTask )
这个函数的原理是从栈的最后开始往前数,直到数到第一个不为tskSTACK_FILL_BYTE
的数,这样就是没有使用的栈空间大小,再用总的栈大小减去这个剩余的栈空间大小,就得到已经使用的栈空间的大小。
从上面的分析可知,我们只需要提供各个任务的任务句柄TaskHandle_t
,然后调用uxTaskGetStackHighWaterMark
即可。但是我们有很多个任务,难道每个任务的任务句柄都要再写一个函数或者extern
声明吗?任务句柄一般用在获取任务相关信息、挂起/恢复任务等操作上,实际上大部分任务是不需要使用任务句柄的,即在创建任务的时候填入NULL。
所以我们的目的就是写一个函数,它能遍历所有的任务并找到它保存在系统内部的TaskHandle_t
。
那么我们应该从何入手呢?我们知道系统中肯定有保存在ready
、suspend
等状态下的任务的链表。首先来看一下uxTaskGetStackHighWaterMark
函数找找思路
#define prvGetTCBFromHandle( pxHandle ) ( ( ( pxHandle ) == NULL ) ? pxCurrentTCB : ( pxHandle ) )
UBaseType_t uxTaskGetStackHighWaterMark( TaskHandle_t xTask )
...
TCB_t *pxTCB;
pxTCB = prvGetTCBFromHandle( xTask );
...
TaskHandle_t
和TCB_t *
是同一个数据类型也就是如何参数是NULL,就取当前正在运行的任务的TCB获取WaterMark
。所以pxCurrentTCB
就保存了当前运行任务的句柄,在它的定义处也找到了其它状态的链表变量声明。
/*lint -save -e956 A manual analysis and inspection has been used to determine
which static variables must be declared volatile. */
PRIVILEGED_DATA TCB_t * volatile pxCurrentTCB = NULL;
/* Lists for ready and blocked tasks. --------------------
xDelayedTaskList1 and xDelayedTaskList2 could be move to function scople but
doing so breaks some kernel aware debuggers and debuggers that rely on removing
the static qualifier. */
PRIVILEGED_DATA static List_t pxReadyTasksLists[ configMAX_PRIORITIES ];/*< Prioritised ready tasks. */
PRIVILEGED_DATA static List_t xDelayedTaskList1; /*< Delayed tasks. */
PRIVILEGED_DATA static List_t xDelayedTaskList2; /*< Delayed tasks (two lists are used - one for delays that have overflowed the current tick count. */
PRIVILEGED_DATA static List_t * volatile pxDelayedTaskList; /*< Points to the delayed task list currently being used. */
PRIVILEGED_DATA static List_t * volatile pxOverflowDelayedTaskList; /*< Points to the delayed task list currently being used to hold tasks that have overflowed the current tick count. */
PRIVILEGED_DATA static List_t xPendingReadyList; /*< Tasks that have been readied while the scheduler was suspended. They will be moved to the ready list when the scheduler is resumed. */
在后面也看见了suspend
状态的链表
#if ( INCLUDE_vTaskSuspend == 1 )
PRIVILEGED_DATA static List_t xSuspendedTaskList; /*< Tasks that are currently suspended. */
#endif
其中
typedef struct xLIST
{
listFIRST_LIST_INTEGRITY_CHECK_VALUE /*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
volatile UBaseType_t uxNumberOfItems;
ListItem_t * configLIST_VOLATILE pxIndex; /*< Used to walk through the list. Points to the last item returned by a call to listGET_OWNER_OF_NEXT_ENTRY (). */
MiniListItem_t xListEnd; /*< List item that contains the maximum possible item value meaning it is always at the end of the list and is therefore used as a marker. */
listSECOND_LIST_INTEGRITY_CHECK_VALUE /*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
} List_t;
在这些链表中肯定就有我们想要的TaskHandle_t
,所以我们要做到的就是遍历这些链表,但是我们还不知道这个链表的数据结构的哪一项保存了TCB,也不想再去细细研究这个结构体中的每个成员是怎么被赋值的。但我们知道系统肯定要从pxReadyTasksLists
中取出最高优先级的任务来调度,函数如下:
#define taskSELECT_HIGHEST_PRIORITY_TASK() \
{ \
UBaseType_t uxTopPriority = uxTopReadyPriority; \
\
/* Find the highest priority queue that contains ready tasks. */ \
while( listLIST_IS_EMPTY( &( pxReadyTasksLists[ uxTopPriority ] ) ) ) \
{ \
configASSERT( uxTopPriority ); \
--uxTopPriority; \
} \
\
/* listGET_OWNER_OF_NEXT_ENTRY indexes through the list, so the tasks of \
the same priority get an equal share of the processor time. */ \
listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) ); \
uxTopReadyPriority = uxTopPriority; \
} /* taskSELECT_HIGHEST_PRIORITY_TASK */
我们也发现pxReadyTasksLists
和其它状态的链表不一样,它是一个List_t
数组,我们就根据上面这个宏定义先写pxReadyTasksLists
的遍历,代码如下:
UBaseType_t uxTopPriority = uxTopReadyPriority;
TCB_t *pTCB_TMP;
while(uxTopPriority-- != 0)
{
if(!listLIST_IS_EMPTY( &( pxReadyTasksLists[ uxTopPriority ] ) ))
{
listGET_OWNER_OF_NEXT_ENTRY( pTCB_TMP, &( pxReadyTasksLists[ uxTopPriority ] ) );
/* 在此函数中调用water mark函数并输出 */
log_stack_info(pTCB_TMP);
}
}
接着就是其它的任务了,遍历流程都一样,这里以xSuspendedTaskList
为例进行分析,因为大部分任务都在这里面。那又要怎么实现呢?很明显前面是通过listGET_OWNER_OF_NEXT_ENTRY
将List中的TaskHandle取出来的,所以我们看一下这个宏定义:
#define listGET_OWNER_OF_NEXT_ENTRY(pxTCB, pxList) \
{ \
List_t *const pxConstList = (pxList); \
(pxConstList)->pxIndex = (pxConstList)->pxIndex->pxNext; \
if ((void *)(pxConstList)->pxIndex == \
(void *)&((pxConstList)->xListEnd)) \
{ \
(pxConstList)->pxIndex = (pxConstList)->pxIndex->pxNext; \
} \
(pxTCB) = (pxConstList)->pxIndex->pvOwner; \
}
也就是说链表的下一项在List_t
的pxIndex->pxNext
中,而TCB在pxIndex->pvOwner
中。我们注意到List_t
中有一个成员项uxNumberOfItems
即为该链表中的任务的个数,现在我们就可以来遍历了。
TCB_t *pTCB_TMP;
ListItem_t *pListCur;
pListCur = xDelayedTaskList1.pxIndex;
for(int i=0; ipxNext;
pTCB_TMP = pListCur->pvOwner;
log_stack_info(pTCB_TMP);
}
这样我们就完成了对所有任务TCB的获取,在log_stack_info
函数中获取watermark并输出即可:
staticvoid log_stack_info(TCB_t *pTmp)
{
uint32_t total, mark;
mark = uxTaskGetStackHighWaterMark((TaskHandle_t)pTmp);
total = pTmp->pxEndOfStack - pTmp->pxStack; /* portSTACK_GROWTH=-1 */
mark = total - mark;
log_d(......); //自行格式化输出
}
注意:从各个状态的链表中获取这些TaskHandle的同时,任务有可能正好进行状态切换,所以执行上面函数时应该进入临界区。