【嵌入式】一种FreeRTOS任务堆栈分析方法

说明:在使用FreeRTOS的项目工程中遇到某个任务无法接收队列消息的情况(已经确认发送消息过程是正常的),但是其他任务的消息发送接收可以正常运行。这个现象非常难还原,这里刚好有一台设备出现了这种情况,但是原先的工程中并没有添加UBaseType_t uxTaskGetStackHighWaterMark( TaskHandle_t xTask )函数去监管任务的堆栈,而且当时也不认为会是这个问题,但是既然出现了这个问题就要想办法去解决,由于这个现象非常难出现,所以修改代码再去测试比较费时间,这里研究出了一种新的方法。

通过阅读FreeRTOS的底层函数可以发现,不管代码中是否有调用UBaseType_t uxTaskGetStackHighWaterMark( TaskHandle_t xTask ),创建的每个任务系统都会分配一个如下图所示的结构体数据,而任务的剩余堆栈情况就存在StackType_t *pxStack;中。

typedef struct tskTaskControlBlock
{
    volatile StackType_t    *pxTopOfStack;  /*< Points to the location of the last item placed on the tasks stack.  THIS MUST BE THE FIRST MEMBER OF THE TCB STRUCT. */

    #if ( portUSING_MPU_WRAPPERS == 1 )
        xMPU_SETTINGS   xMPUSettings;       /*< The MPU settings are defined as part of the port layer.  THIS MUST BE THE SECOND MEMBER OF THE TCB STRUCT. */
    #endif

    ListItem_t          xStateListItem; /*< The list that the state list item of a task is reference from denotes the state of that task (Ready, Blocked, Suspended ). */
    ListItem_t          xEventListItem;     /*< Used to reference a task from an event list. */
    UBaseType_t         uxPriority;         /*< The priority of the task.  0 is the lowest priority. */
    StackType_t         *pxStack;           /*< Points to the start of the stack. */
    char                pcTaskName[ configMAX_TASK_NAME_LEN ];/*< Descriptive name given to the task when created.  Facilitates debugging only. */ /*lint !e971 Unqualified char types are allowed for strings and single characters only. */

    #if ( ( portSTACK_GROWTH > 0 ) || ( configRECORD_STACK_HIGH_ADDRESS == 1 ) )
        StackType_t     *pxEndOfStack;      /*< Points to the highest valid address for the stack. */
    #endif

    #if ( portCRITICAL_NESTING_IN_TCB == 1 )
        UBaseType_t     uxCriticalNesting;  /*< Holds the critical section nesting depth for ports that do not maintain their own count in the port layer. */
    #endif

    #if ( configUSE_TRACE_FACILITY == 1 )
        UBaseType_t     uxTCBNumber;        /*< Stores a number that increments each time a TCB is created.  It allows debuggers to determine when a task has been deleted and then recreated. */
        UBaseType_t     uxTaskNumber;       /*< Stores a number specifically for use by third party trace code. */
    #endif

    #if ( configUSE_MUTEXES == 1 )
        UBaseType_t     uxBasePriority;     /*< The priority last assigned to the task - used by the priority inheritance mechanism. */
        UBaseType_t     uxMutexesHeld;
    #endif

    #if ( configUSE_APPLICATION_TASK_TAG == 1 )
        TaskHookFunction_t pxTaskTag;
    #endif

    #if( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 )
        void            *pvThreadLocalStoragePointers[ configNUM_THREAD_LOCAL_STORAGE_POINTERS ];
    #endif

    #if( configGENERATE_RUN_TIME_STATS == 1 )
        uint32_t        ulRunTimeCounter;   /*< Stores the amount of time the task has spent in the Running state. */
    #endif

    #if ( configUSE_NEWLIB_REENTRANT == 1 )
        /* Allocate a Newlib reent structure that is specific to this task.
        Note Newlib support has been included by popular demand, but is not
        used by the FreeRTOS maintainers themselves.  FreeRTOS is not
        responsible for resulting newlib operation.  User must be familiar with
        newlib and must provide system-wide implementations of the necessary
        stubs. Be warned that (at the time of writing) the current newlib design
        implements a system-wide malloc() that must be provided with locks. */
        struct  _reent xNewLib_reent;
    #endif

    #if( configUSE_TASK_NOTIFICATIONS == 1 )
        volatile uint32_t ulNotifiedValue;
        volatile uint8_t ucNotifyState;
    #endif

    /* See the comments above the definition of
    tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE. */
    #if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ) /*lint !e731 Macro has been consolidated for readability reasons. */
        uint8_t ucStaticallyAllocated;      /*< Set to pdTRUE if the task is a statically allocated to ensure no attempt is made to free the memory. */
    #endif

    #if( INCLUDE_xTaskAbortDelay == 1 )
        uint8_t ucDelayAborted;
    #endif

} tskTCB;

通过下图的代码可以发现,只需要找到相应任务的TaskHandle_t xTask句柄就能知道堆栈情况了。

查看项目代码不难发现,这个参数就是xTaskCreate()中的TaskHandle_t * const pxCreatedTask参数,本文提到任务就是m_lcd_handle

通过Keil工程编译的.map文件,找到该变量的地址是0x20007a70。

通过JLINK Commander连接设备,发送h命令Halt住芯片,然后读取该内存地址的内容,这个内容就是存放对应任务句柄的地址。

J-Link>h
PC = 00048C04, CycleCnt = E3746582
R0 = 2000FDE8, R1 = 00000034, R2 = 00000000, R3 = 00000000
R4 = 20008864, R5 = 00000003, R6 = 00000000, R7 = 00000000
R8 = 00000000, R9 = 00000004, R10= 20007D74, R11= A5A5A5A5
R12= A5A5A5A5
SP(R13)= 200221C0, MSP= 200221C0, PSP= 2000FB48, R14(LR) = 000344F1
XPSR = 01000034: APSR = nzcvq, EPSR = 01000000, IPSR = 014 (INTISR4)
CFBP = 00000000, CONTROL = 00, FAULTMASK = 00, BASEPRI = 00, PRIMASK = 00

FPS0 = 00000000, FPS1 = 00000000, FPS2 = 00000000, FPS3 = 3FF80000
FPS4 = 00000000, FPS5 = 00000000, FPS6 = 00000000, FPS7 = 00000000
FPS8 = 00000000, FPS9 = 00000000, FPS10= 00000000, FPS11= 00000000
FPS12= 00000000, FPS13= 00000000, FPS14= 00000000, FPS15= 00000000
FPS16= 00000000, FPS17= 00000000, FPS18= 00000000, FPS19= 00000000
FPS20= 00000000, FPS21= 00000000, FPS22= 00000000, FPS23= 00000000
FPS24= 00000000, FPS25= 00000000, FPS26= 00000000, FPS27= 00000000
FPS28= 00000000, FPS29= 00000000, FPS30= 00000000, FPS31= 00000000
FPSCR= 00000000
J-Link>mem 0x20007a70,0x04
20007A70 = 40 E7 00 20

接着读取这个地址的数据。

J-Link>mem 0x2000E740,0x48
2000E740 = 34 E6 00 20 63 8E 8E 0E 8C EF 00 20 D4 F7 00 20
2000E750 = 40 E7 00 20 70 10 01 20 04 00 00 00 14 FC 00 20
2000E760 = 14 FC 00 20 40 E7 00 20 00 00 00 00 02 00 00 00
2000E770 = 40 DB 00 20 46 4C 53 00 02 00 00 00 00 00 00 00
2000E780 = 00 00 00 00 00 00 00 00

上图的0x48是结构体tskTCB的大小,这里再贴出tskTCB结构体内部几个关键结构体的Size。

0>  app: sizeof(StackType_t):4.
0>  app: sizeof(ListItem_t):20.
0>  app: sizeof(UBaseType_t):4.
0>  app: sizeof(UBaseType_t):72.

结合0x2000E740地址数据,以及通过计算,StackType_t *pxStack;在结构体中的偏移地址是(4+20+20+4)= 48(0x30),所以0x2000E740+0x30=0x2000E770,存放StackType_t *pxStack;数据的地址是0x2000DB40!!

再读一把0x2000DB40地址的数据。

J-Link>mem 0x2000DB40,0xff
2000DB40 = 88 DB 00 20 3A 7F 00 20 00 00 00 00 14 1A 01 20
2000DB50 = 01 00 00 00 9D BA 03 00 00 00 00 00 65 BB 03 00
2000DB60 = 6C BB 03 00 00 00 00 61 E0 01 00 00 88 DB 00 20
2000DB70 = E0 01 00 00 25 FF 02 00 00 00 00 00 E0 01 00 00
2000DB80 = E0 01 00 00 65 89 02 00 00 00 00 00 00 00 00 00
2000DB90 = 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
2000DBA0 = 00 00 42 08 6B 2D 6B 2D 6B 2D 6B 2D 6B 2D 6B 2D
2000DBB0 = 6B 2D 6B 2D 21 24 00 00 00 00 A5 14 FF FF FF FF
2000DBC0 = FF FF FF FF FF FF FF FF FF FF FF FF 6B 2D 00 00
2000DBD0 = 00 00 A5 14 FF FF FF FF FF FF FF FF FF FF FF FF
2000DBE0 = FF FF FF FF 6B 2D 00 00 00 00 A5 14 FF FF D6 9A
2000DBF0 = 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
2000DC00 = 00 00 A5 14 FF FF D6 9A 00 00 00 00 00 00 00 00
2000DC10 = 00 00 00 00 00 00 00 00 00 00 A5 14 FF FF D6 9A
2000DC20 = 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
2000DC30 = 00 00 A5 14 FF FF D6 9A A5 14 D6 9A D6 9A BD

结合以上的数据,再配合FreeRTOS计算空闲空间的方法,可以知道对应任务的堆栈已经用完了,换句话说就是任务所需要的堆栈可能已经超出了任务分配的空间了!!!!

上面两张图是FreeRTOS计算空闲空间的方法,其实就是计算数据中有多少个tskSTACK_FILL_BYTE再除以sizeof( StackType_t ),因为FreeRTOS的单位是Word不是字节,所以其实就是要除以4。

这里用同样的方法对一台正常的设备进行测试,最后的数据是

J-Link>mem 0x2000E378,0xff
2000E378 = A5 A5 A5 A5 A5 A5 A5 A5 00 00 00 00 00 1A 01 20
2000E388 = 01 00 00 00 29 BB 03 00 00 90 02 40 01 00 00 00
2000E398 = C8 46 B8 00 00 00 00 00 00 00 00 00 00 1A 01 20
2000E3A8 = 01 00 00 00 29 BB 03 00 00 00 00 00 F1 BB 03 00
2000E3B8 = F8 BB 03 00 00 00 00 61 E0 01 00 00 E0 E3 00 20
2000E3C8 = E0 01 00 00 51 FF 02 00 00 00 00 00 E0 01 00 00
2000E3D8 = E0 01 00 00 69 89 02 00 00 00 00 00 00 00 00 00
2000E3E8 = 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
2000E3F8 = 00 00 00 00 00 00 00 00 42 08 7B CF 94 B2 6B 2D
2000E408 = 21 24 00 00 00 00 00 00 00 00 00 00 00 00 AD 75
2000E418 = FF FF FF FF FF FF FF FF FF FF A5 14 00 00 00 00
2000E428 = 00 00 00 00 BD D7 FF FF FF FF FF FF FF FF FF FF
2000E438 = FF FF FF FF A5 14 00 00 00 00 52 AA FF FF FF FF
2000E448 = BD D7 21 24 00 00 42 08 E7 5C FF FF E7 5C 00 00
2000E458 = 00 00 AD 75 FF FF F7 BE 21 24 00 00 00 00 00 00
2000E468 = A5 14 FF FF FF FF 21 24 00 00 52 AA 6B 2D 52

这里有8个0xA5,所以剩余空间是8/4=2Word,在代码中加入相关函数的打印。

结果证明以上所述与调用UBaseType_t uxTaskGetStackHighWaterMark( TaskHandle_t xTask )函数获取的数据完全一致!!!!

你可能感兴趣的:(【嵌入式】一种FreeRTOS任务堆栈分析方法)