taskmicrocontrollermultitaskingfreertos

FreeRTOS: obtain the stack size (`usStackDepth`) value in words or bytes after calling `xTaskCreate()`


I'm working on a trace module which has to monitor FreeRTOS tasks' heap in order to detect stack overflows. I am wondering whether it is possible to get a task stack size after its creation. Can I get access to this information through the API? Or, is it stored in some internal structure? How do I get access to it?


Solution

  • I am wondering whether it is possible to get a task stack size after its creation.

    Can I get access to this information through the API?

    Or, is it stored in some internal structure? How do I get access to it?

    Great question! Yes, yes you can get this information.

    As of FreeRTOS V10.0.0, you'll see two really important additions in the History.txt file here:

    Changes between FreeRTOS V9.0.1 and FreeRTOS V10.0.0:

    ...

    • Introduced configRECORD_STACK_HIGH_ADDRESS. When set to 1 the stack start address is saved into each task's TCB (assuming stack grows down).

    • Introduced configINCLUDE_FREERTOS_TASK_C_ADDITIONS_H to allow user defined functionality, and user defined initialisation, to be added to FreeRTOS's tasks.c source file. When configINCLUDE_FREERTOS_TASK_C_ADDITIONS_H is set to 1 a user provided header file called freertos_task_c_additions.h will be included at the bottom of tasks.c. Functions defined in that header file can call freertos_tasks_c_additions_init(), which in turn calls a macro called FREERTOS_TASKS_C_ADDITIONS_INIT(), if it is defined. FREERTOS_TASKS_C_ADDITIONS_INIT() can be defined in FreeRTOSConfig.h.

    1. To look for stack overflow risk, get the high stack watermark

    See usStackHighWaterMark in the TaskStatus_t struct in task.h. Even better: use these two functions:

    uxTaskGetStackHighWaterMark();
    uxTaskGetStackHighWaterMark2();
    

    See: https://www.freertos.org/uxTaskGetStackHighWaterMark.html

    There is also a really nice convenience function called vTaskList() and vTaskListTasks() from task.h that prints out a table of all tasks, including their stack high water mark.

    See here: https://www.freertos.org/a00021.html#vTaskList

    2. Write your own taskGetStackSizeWords() and taskGetStackSizeBytes() functions to get stack size/depth in words and bytes

    So, to get the stack depth (size) in words or bytes, you can do the following steps:

    1. In your custom FreeRTOSConfig.h file, add:

      #define configINCLUDE_FREERTOS_TASK_C_ADDITIONS_H 1
      #define configRECORD_STACK_HIGH_ADDRESS 1
      
    2. Create a file called freertos_task_c_additions.h. Since you defined configINCLUDE_FREERTOS_TASK_C_ADDITIONS_H as 1 above, it will be automatically included by FreeRTOS directly into the very bottom of their tasks.c file, giving your file direct access to all of the private members and static content in tasks.c. Defining configRECORD_STACK_HIGH_ADDRESS as 1 above adds pxEndOfStack, which we need in the stack size calculation below, to the task control block (TCB_t) struct in tasks.c. FreeRTOS tells us in tasks.c that pxEndOfStack "points to the highest valid address for the stack", meaning: the end of the stack. pxStack, on the other hand, points to the beginning of the stack. So, the stack size is pxEndOfStack - pxStack + 1.

      In your freertos_task_c_additions.h file, add the following:

      // NB: THIS IS TYPICALLY NOT A HEADER FILE YOU SHOULD INCLUDE IN YOUR CODE. 
      // - Treat this like a .c source file. FreeRTOS includes this file instead. 
      // - The file you should include to access these public functions below is 
      //   `freertos_task_c_additions_include.h`.  
      
      #pragma once
      
      #include "FreeRTOS.h"
      
      
      #if ((configINCLUDE_FREERTOS_TASK_C_ADDITIONS_H == 1) \
          && (configRECORD_STACK_HIGH_ADDRESS == 1))
      
      // Get the stack size in words, not bytes, first passed to `xTaskCreate()` 
      // when the task was created. This is the size of the stack allocated for 
      // the task. 
      // - Note: due to the alignment that FreeRTOS enforces to the top (end) of
      //   the stack via `portBYTE_ALIGNMENT_MASK` in tasks.c, the available stack
      //   size is actually one or two bytes or so less than what you input at
      //   task creation. FreeRTOS adjusts `pxTopOfStack`, which gets assigned to
      //   `pxEndOfStack`, for stack alignment. So, if you passed in 200 words to
      //   `xTaskCreate()` (see: https://www.freertos.org/a00125.html) as the
      //   `usStackDepth` value, this function may return 199 instead, which is
      //   the actual, correct, stack size available. 
      // - Partially learned from:
      //   https://www.freertos.org/FreeRTOS_Support_Forum_Archive/January_2019/freertos_Retrieve_the_size_and_maximum_usage_of_the_stack_per_task_7ab5c6eb05j.html
      uint32_t taskGetStackSizeWords(TaskHandle_t taskHandle)
      {
          uint32_t stackSizeBytes;
      
          TCB_t * tcb = prvGetTCBFromHandle(taskHandle);
          // critical section to access protected TCB (Task Control Block)
          // members from this task's `struct tskTaskControlBlock`
          taskENTER_CRITICAL();
          {
              // +1 to count both the end and start in the calculation
              stackSizeBytes = tcb->pxEndOfStack - tcb->pxStack + 1;
          }
          taskEXIT_CRITICAL();
      
          // Optionally [but not recommended], you may add +1 again to give 
          // us the same value that we input when we created the task; 
          // technically, `portBYTE_ALIGNMENT_MASK` removed one of our bytes 
          // due to alignment, so actually our stack is 1 byte less
          // than our input at creation.
          // stackSizeBytes += 1; 
      
          return stackSizeBytes;
      }
      
      // Get the stack size in bytes. 
      // - See details in the `taskGetStackSizeWords()` function above.
      uint32_t taskGetStackSizeBytes(TaskHandle_t taskHandle)
      {
          uint32_t stackSizeWords = 
              taskGetStackSizeBytes(taskHandle)*sizeof(StackType_t);
          return stackSizeWords;
      }
      
      #endif // ((configINCLUDE_FREERTOS_TASK_C_ADDITIONS_H == 1)
             //   && (configRECORD_STACK_HIGH_ADDRESS == 1))
      
    3. Use the functions above.

      In the source file where you want to use the functions above, you can get access to them by forward declaring them as follows, then using them afterwards:

      #include "FreeRTOS.h"
      #include "task.h"
      
      // Note: `extern` here is optional for functions.
      // See my answer here: https://stackoverflow.com/a/77527374/4561887
      extern uint32_t taskGetStackSizeWords(TaskHandle_t taskHandle);
      extern uint32_t taskGetStackSizeBytes(TaskHandle_t taskHandle);
      
      void in_some_func()
      {
          uint32_t stackSizeBytes = 
              taskGetStackSizeBytes(xTaskGetCurrentTaskHandle());
          uint32_t stackSizeWords = 
              taskGetStackSizeWords(xTaskGetCurrentTaskHandle());
          // etc.
      }
      

      Or, even better, you can create a new header file with the forward declarations inside it. Ex:

      freertos_task_c_additions_include.h:

      # pragma once
      
      #ifdef __cplusplus
      extern "C" {
      #endif
      
      #include "FreeRTOS.h"
      #include "task.h"
      
      #if ((configINCLUDE_FREERTOS_TASK_C_ADDITIONS_H == 1) \
          && (configRECORD_STACK_HIGH_ADDRESS == 1))
      uint32_t taskGetStackSizeWords(TaskHandle_t taskHandle);
      uint32_t taskGetStackSizeBytes(TaskHandle_t taskHandle);
      #endif // ((configINCLUDE_FREERTOS_TASK_C_ADDITIONS_H == 1)
             //   && (configRECORD_STACK_HIGH_ADDRESS == 1))
      
      #ifdef __cplusplus
      }
      #endif
      

      Then use it:

      #include "freertos_task_c_additions_include.h"
      
      void in_some_func()
      {
          uint32_t stackSizeBytes = 
              taskGetStackSizeBytes(xTaskGetCurrentTaskHandle());
          uint32_t stackSizeWords = 
              taskGetStackSizeWords(xTaskGetCurrentTaskHandle());
          // etc.
      }
      

    Explanation

    Since you defined configINCLUDE_FREERTOS_TASK_C_ADDITIONS_H to 1 in your config file, FreeRTOS automatically includes this custom freertos_task_c_additions.h file at the bottom of tasks.c.

    Treat this "header" file like a source code file that gets included in another source code file.

    This way, your code in freertos_task_c_additions.h can access all of the private members in tasks.c. All of your customizations to FreeRTOS should be contained within this file, not inserted into the original FreeRTOS source code.

    For more information:

    References

    1. I relied heavily upon my detailed answer here: How to call a static function in one C source code file from another C source code file?
    2. I also studied the forum post in the link just above, especially the part "posted by mastupristi on January 31, 2019".
    3. The FreeRTOS History.txt file I quoted above
    4. Reminder on how to use extern "C" to prevent name-mangling of C-compiled object files linked into C++ programs: What is the effect of extern "C" in C++?
    5. https://www.freertos.org/uxTaskGetStackHighWaterMark.html
    6. vTaskList() and vTaskListTasks(): https://www.freertos.org/a00021.html#vTaskList
    7. This other answer. It had the link to here: https://www.freertos.org/uxTaskGetStackHighWaterMark.html