Dynamic vs static allocation of the tasks

Hmm. So when you dynamically allocate memory for the task, shouldn’t pxTopOfStack - pxStack really be equal to how much you allocated?

So upon allocating 200 stack depth, assuming it’s a 32 bit word ==> 800 bytes. Is my assumption correct?

When do you expect this to be the case? When the task is created, or at any time? You can see how the structure members are used in the code. As per https://github.com/FreeRTOS/FreeRTOS-Kernel/blob/master/tasks.c#L256 pxTopOfStack points to the last item placed onto the stack. Even a newly created task has items on its stack (its initial context), so pxTopOfStack will never point to the extremity of a stack, so pxTopOfStack - pxStack won’t be the entire stack space.

Yes, that is correct, give or take any bytes added or subtracted to ensure the correct byte alignment.

But does the allocated memory even change after task creation though? just debugged and looks like it’s changing after the task is added to the ready list. Sorry I’m trying to understand its internal working, but shouldn’t the stack memory remain the same throughout from the get-go until you realloc?

I see that pxTopStack points to the stackDepth - 1 index of the stack, which in case of memory growing downwards would be the 2nd index from the top of the stack?

Assuming stack grows downwards, shouldn’t pxTopOfStack contain lower address than pxStack?

I have two tasks each with a stack memory of 200 stack depth so 800 bytes, and the following is what I see:

pxTopOfStack = 0x200009bc
pxStack = 0x200006e8
Difference = 724

pxStack will ALWAYS be the lowest address in the stack space. It is the address base of the memory region we call the stack space.

If available, pxEndOfStack, will point to the other end of the Stack, the highest address in the stack space (maybe highest+1).

These values will never change, as the stack will never be ‘moved’ for a running task (It is too likely to have saved pointers into the stack somewhere)

pxTopStack has the value of the stack pointer at the last time the task blocked. At the instant of creation, for a downward growing stack, it will start at the very end of the space (pxEndOfStack), but immediately it will get decremented as initial values get put on the stack to setup the task, at least the register set that task will use when it starts. As the task runs, it will move down from here, and possible retreat a bit back to this point.

Given your example, pxStack was 0x200006e8, which would have been the value from malloc or passed in as the beginning of the static stack space (possibly modified if that wasn’t properly aligned).

pxEndOfStack would have been 0x20000908 (0x320, 800 decimal higher). (or maybe 0x20000904, I forget if it points to the last element, or one past it.)

pxTopOfStack should always be in between, in your case you have 724 bytes (181 32-bit words) of additional space usable on the stack at that point in time. It STARTED at a value of pxEndOfStack, but by the time the task was made, it was smaller. In your case it looks like about 19 words have been put on the stack, that would be the initial register set.

You don’t know how much memory will you task need. You must exercise an educated guess based on the number of local variables you task have, the deep of calling functions, and the number of arguments each function have. It’s hard!.

Then enable the API functions that let you “see” your tasks’ stack and run your application in the worst scenario (if possible taking the deepest calling function path) and as long as possible. Based on the results you might tweak the stack size.

If you’re new to embedded operating systems: DO NOT EVER USE DYNAMIC CREATED TASKS. Stick to static tasks (and static objects: semaphores, flags, etc). Avoid the heap as plague!

Here you have an opinion about malloc from an embedded guru:
https://www.beningo.com/tips-and-tricks-7-tips-for-memory-management/

If you set the parameters for the static task as static, then you’ll be using the stack of the function that is creating the task, and most of the time you don’t want that. So it’s prefered to set those parameters in the global space, so you don’t lose the control of your RAM. In case you run out of memory, it’ll be in compiling time and the compiler will advice you.

If the variables you are using have the attribute static, then they are NOT on the task’s stack but are allocated as some fixed location in memory (determined by the linker). IE

void fun() {
    static int i;
   return i++;
}

This function will get an int sized chunk of memory in the data area (not the stack) which will remember it values between invocations of the function. You could use this to allocate a task, but it is better to use a global (or at least a file scoped) variable because you might forget and call the function twice to make two copies of the task, thinking they will get new memory (since the data is function local).

I do agree that if your tasks/objects are immortal, created at startup, and live forever, then it is better to make them static. The dynamic versions came first, and are sometimes useful for less critical tasks that come and go during operation.

If you don’t mind, I would also like to clarify some details)

If I select a dynamic task, then everything in this task (functions, variables) is stored in the RTOS stack (in RAM) when the task is switched by the scheduler, and if I do not have enough memory, then this will be clear not at the time of compilation, but while the program is running and I need this so that it can calculate the stack size well, do I understand correctly?

If I choose a static task, then I can choose the placement of that task (as written above, using the rules of the C language) and not use the stack owned by the RTOS.

Each task needs 2 blocks of memory:

  1. Task Control Block (TCB) - This memory block is used to store the control information used by the kernel.
  2. Task Stack - This memory block is used as stack when the task is running. As you correctly mentioned, the variables allocated on the stack, function call frames etc. are stored here.

The difference between dynamic and static task is where the memory for TCB and stack comes from:

  1. Dynamic tasks are created using xTaskCreate API. TCB and Stack memory are allocated from heap by calling pvPortMalloc function.
  2. Static tasks are created using xTaskCreateStatic API. TCB and Stack memory are supplied by the application. Look at the puxStackBuffer and pxTaskBuffer parameters of xTaskCreateStatic API.

Heap can run out of memory at run time and as a result, the memory allocations can fail. This means that xTaskCreate can fail to create a task if the heap is out of memory. If you do not want your task creations to fail at run time, you can avoid using heap by using xTaskCreateStatic API:

#define STACK_SIZE 256

static StaticTask_t xTaskBuffer; /* Memory used for TCB. */
static StackType_t xStack[ STACK_SIZE ]; /* Memory used for stack. */

int main( void )
{
    TaskHandle_t xHandle = NULL;

    xHandle = xTaskCreateStatic( vTaskCode,       /* Function that implements the task. */
                                 "NAME",          /* Text name for the task. */
                                 STACK_SIZE,      /* Stack size in words, not bytes. */
                                 NULL,            /* Parameter passed into the task. */
                                 tskIDLE_PRIORITY,/* Priority at which the task is created. */
                                 xStack,          /* Memory to use as the task's stack. */
                                 &xTaskBuffer );  /* Memory to hold the TCB. */
    configASSERT( xHandle != NULL );

    vTaskStartScheduler();
}

In the above example, we do not use heap and therefore, the task creation will not fail at runtime. If program tries to use more memory than available, it will fail at the link step. Hope it clarifies.

1 Like

Very detailed and comprehensive information)
Thank you)