• FreeRTOS遍历所有任务的TCB并获得栈的最大使用量


    FreeRTOS的任务栈的大小应该如何设置才能尽可能地不浪费内存呢?最直接的方法当然是运行一段时间任务,然后看看任务所使用的最大堆栈大小为多少,然后以此为基准多设置一点点。那么应该怎么实现呢?

    1 原理

    在我们创建一个任务的时候,有一个宏定义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 */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    接下来我们就根据这个原理来输出任务栈的最大使用情况。FreeRTOS提供了一个函数uxTaskGetStackHighWaterMark,使用这个函数需要把宏定义INCLUDE_uxTaskGetStackHighWaterMark打开。

    #define INCLUDE_uxTaskGetStackHighWaterMark     1
    UBaseType_t uxTaskGetStackHighWaterMark( TaskHandle_t xTask )
    
    • 1
    • 2

    这个函数的原理是从栈的最后开始往前数,直到数到第一个不为tskSTACK_FILL_BYTE的数,这样就是没有使用的栈空间大小,再用总的栈大小减去这个剩余的栈空间大小,就得到已经使用的栈空间的大小。

    2 实现

    从上面的分析可知,我们只需要提供各个任务的任务句柄TaskHandle_t,然后调用uxTaskGetStackHighWaterMark即可。但是我们有很多个任务,难道每个任务的任务句柄都要再写一个函数或者extern声明吗?任务句柄一般用在获取任务相关信息、挂起/恢复任务等操作上,实际上大部分任务是不需要使用任务句柄的,即在创建任务的时候填入NULL。

    所以我们的目的就是写一个函数,它能遍历所有的任务并找到它保存在系统内部的TaskHandle_t


    那么我们应该从何入手呢?我们知道系统中肯定有保存在readysuspend等状态下的任务的链表。首先来看一下uxTaskGetStackHighWaterMark函数找找思路

    #define prvGetTCBFromHandle( pxHandle ) ( ( ( pxHandle ) == NULL ) ? pxCurrentTCB : ( pxHandle ) )
    
    UBaseType_t uxTaskGetStackHighWaterMark( TaskHandle_t xTask )
    	...
    	TCB_t *pxTCB;
    	pxTCB = prvGetTCBFromHandle( xTask );
    	...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 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. */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在后面也看见了suspend状态的链表

    #if ( INCLUDE_vTaskSuspend == 1 )
    	PRIVILEGED_DATA static List_t xSuspendedTaskList;					/*< Tasks that are currently suspended. */
    #endif
    
    • 1
    • 2
    • 3

    其中

    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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在这些链表中肯定就有我们想要的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 */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    我们也发现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);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    接着就是其它的任务了,遍历流程都一样,这里以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;                   \
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    也就是说链表的下一项在List_tpxIndex->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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    这样我们就完成了对所有任务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(......);  //自行格式化输出
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    注意:从各个状态的链表中获取这些TaskHandle的同时,任务有可能正好进行状态切换,所以执行上面函数时应该进入临界区。

  • 相关阅读:
    OKR 及其对员工的重要性
    分布式精华笔记!带你深入剖析一致性共识算法还不来看?
    JimuReport积木报表 v1.6.5 版本发布—免费报表工具
    SpringBoot实战系列之图形验证码开发并池化Redis6存储
    SpringBoot入门教程:数据库恢复(mysqldump和mysqlbinlog)
    煤炭行业数智化供应商管理系统解决方案:数据驱动,供应商智慧平台助力企业降本增效
    软考报名季,软考高级应该怎么选?
    知识产权恶意侵权是什么意思
    Hive 如何使用 Grouping Sets
    【Nginx30】Nginx学习:代理模块(四)响应头与SSL
  • 原文地址:https://blog.csdn.net/tilblackout/article/details/128105515