FreeRTOS的任务栈的大小应该如何设置才能尽可能地不浪费内存呢?最直接的方法当然是运行一段时间任务,然后看看任务所使用的最大堆栈大小为多少,然后以此为基准多设置一点点。那么应该怎么实现呢?
在我们创建一个任务的时候,有一个宏定义tskSET_NEW_STACKS_TO_KNOWN_VALUE,这里将其打开,这样在初始化一个任务的时候,它的任务栈的默认都填充为tskSTACK_FILL_BYTE,这样就可以方便我们判断栈溢出、获得栈的最大使用等情况。
#define tskSET_NEW_STACKS_TO_KNOWN_VALUE 1
#define tskSTACK_FILL_BYTE ( 0xa5U )
xTaskCreateprvInitialiseNewTask#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的同时,任务有可能正好进行状态切换,所以执行上面函数时应该进入临界区。