C question about stacks, not sure if FreeRTOS has a role to play

Hello

The local variables that I’m creating in a task, are not getting stored on the task’s stack. Here is my task creation:

    xTaskCreateStatic(dummy, "dummy", 200, &data, 3, xStack, &xTaskBuffer);
    

Here is my data struct that I’ve passed to the task:

typedef struct Data_t
{
    uint32_t arr[40];
    int start;
    int len;
} GenericData_t;

Here is the dummy task:

static void dummy(void *xStruct) {
    int i = 500;
    GenericData_t * data = (GenericData_t *) xStruct;

    printf("\naddr of parameters = %x\n", xStruct);
    printf("\naddr of i = %x\n", &i);

    /*To get hold of pxStackBase*/
    TaskHandle_t xHandle;
    TaskStatus_t xTaskDetails;
    /* Obtain the handle of a task from its name. */
    /* Use the handle to obtain further information about the task. */
    vTaskGetInfo( /* The handle of the task being queried. */
                  NULL,
                  /* The TaskStatus_t structure to complete with information
                  on xTask. */
                  &xTaskDetails,
                  /* Include the stack high water mark value in the
                  TaskStatus_t structure. */
                  pdTRUE,
                  /* Include the task state in the TaskStatus_t structure. */
                  eInvalid );

    for(int i = 0; i < 150; i++) {
        printf("\n stack addr = %x", (xTaskDetails.pxStackBase-i));
        printf("\n stack value = %d", *((uint32_t*)xTaskDetails.pxStackBase-i));
    }
    while(1) {}
}

My observation in output:

addr of parameters = 143b4090

addr of i = 309a5e60


And my stack address starts from 143b42a0 (as printed by (xTaskDetails.pxStackBase-i) i.e. the very 1st value in the loop that got printed).

My question:
Why is the local variable showing such an off address? Wasn’t it supposed to show up on the task’s stack too? (Just like the parameter).

I used the following code -


/* Memory for task stack. */
StackType_t xStack[ 512 ];

/* Memory for task TCB. */
StaticTask_t xTaskBuffer;
/*-----------------------------------------------------------*/

static void prvTaskT1( void *pvParams )
{
    int i;

    for( ;; )
    {
        fprintf( stderr, "Address of i = 0x%x. \r\n", ( unsigned int ) &i) ;
        vTaskDelay( pdMS_TO_TICKS( 1000 ) );
    }
}
/*-----------------------------------------------------------*/

void app_main( void )
{
    fprintf( stderr, "Stack lowest address = 0x%x. \r\n", ( unsigned int ) &( xStack[ 0 ] ) );
    fprintf( stderr, "Stack highest address = 0x%x. \r\n", ( unsigned int ) &( xStack[ 511 ] ) );

    xTaskCreateStatic( prvTaskT1,
                       "t1",
                       512,
                       NULL,
                       1,
                       &( xStack[ 0 ] ),
                       &( xTaskBuffer ) );

    vTaskStartScheduler();
}

Here are the results I got:

Stack lowest address = 0x24000158.
Stack highest address = 0x24000954.
Address of i = 0x24000944.

Clearly, the variable i lies on the stack region. Note that I used Cortex-M7 on which stack grows downwards.

Which hardware platform are you using? Can you try the above code and check the results?

Hi Gaurav

Thank you for your detailed example. I am using the POSIX simulation layer. I used the exact same code as you wrote and I got results as below:

Stack lowest address = 0x360dcbc0.
Stack highest address = 0x360ddbb8.
Address of i = 0xb9064eb4.

Is there an explanation for this?

The POSIX port uses pthread_attr_setstack to set the memory you supply to xTaskCreateStatic to be used as the stack. As mentioned on this page, pthread_attr_setstack fails with EINVAL if stack size is less than PTHREAD_STACK_MIN (16384) bytes. This is what is happening I guess. Can you try the following:

  1. Check the return value of pthread_attr_setstack on this line.
  2. Increase the stack size to PTHREAD_STACK_MIN.

Thanks.

1 Like

Thanks a lot Gaurav! This makes a lot of sense. Is this mentioned in the FreeRTOS documentation somewhere?

Another follow-up question:
If the stack is created using pthread_attr_setstack, what is the role of the stack we pass as an argument to the xTaskCreateStatic?

Not yet. We need to confirm if the above is right. Would you please do the 2 things that I suggested and let us know what you find?

The stack is NOT created using pthread_attr_setstack. pthread_attr_setstack sets the memory you pass to xTaskCreateStatic to be used as the task stack.

Yes, I did. Here are my observations:

  1. Check the return value of pthread_attr_setstack on this line.
    Value turned out to be: 22

  2. Increase the stack size to PTHREAD_STACK_MIN.
    It gave me segmentation fault in the output.

Is there an explanation for segfault in 2?
Edit: I increased the puxStackBuffer to also PTHREAD_STACK_MIN*2
Then I got print values from on this line as:
0
22
22

It was invoked 3 times with your code.

When you say- " pthread_attr_setstack sets the memory you pass to xTaskCreateStatic to be used as the task stack.", the address of the local variable as we saw is still not on the task stack. So, I’m a bit confused now. The address on which I’m seeing my local variables is so far off from my task stack. Can you please give me some more logical explanation to this?

@aggarg Can you please check the edited 7/8th answer? Thank you!

The other 2 times it is getting invoked for idle task and timer task. You need to adjust configMINIMAL_STACK_SIZE in your FreeRTOSConfig.h so that the stacks for these two tasks are large enough.

Once I do that, pthread_attr_setstack returns 0 (i.e. success) in all three cases. Here is what the output looks like -

Stack lowest address = 0x55705ca0. 
Stack highest address = 0x55745c98. 
pthread_attr_setstack returned 0 
pthread_attr_setstack returned 0 
pthread_attr_setstack returned 0 
Address of i = 0x55744b04.

And you can see that the variable i now lies in the stack region.

Thanks.

1 Like

Ohh! Understood. So if the stack overflows, it never actually gets passed to the task. And thus, it doesn’t show up in the task’s stack. Right?

One suggestion from this conversation, the configMINIMAL_STACK_SIZE should by default be equal to PTHREAD_STACK_MIN in POSIX demos. I want to raise a PR with this fix in the POSIX demos. Do you also agree?
Also, it should be properly documented in the official documentation as well. Or some error message can be displayed when the stack is overflowing in the Posix layer. Can I help there too? @aggarg Suggestions?

Right, for this to work, a minimum size of PTHREAD_STACK_MIN has to be passed to the all the static functions which is probably larger than what you really want in the field.
In that case using the normal dynamic functions (without static), only when using the posix port would be more accurate.

changing the configMINIMAL_STACK_SIZE would also change it for the dynamic allocation functions, which is probably too large, as they can use a small stack size.
The posix demos don’t use the static functions, we should just add a note in the Readme mentioning that behavior.

Hi @gedeonag Dynamic allocation also requires PTHREAD_STACK_MIN according to this this page it mentions…

A limitation exists for the minimal stack size that can be created. This limitation is platform dependent and is equal to PTHREAD_STACK_MIN…As a workaround, always pass a stack of size PTHREAD_STACK_MIN if the desired stack is less than PTHREAD_STACK_MIN when running on a Posix port. ...

So, in my understanding, for POSIX layer, the default stack size should be changed to PTHREAD_STACK_MIN
This will avoid a lot of confusion.

ah yeah I wrote this some time ago
This kind of only applies to static, as you don’t pass anything to the dynamic stack allocation, and you won’t notice any inconsistencies with the location of variables, and the stack will be the one set and created by pthread_create and should be equal to PTHREAD_STACK_MIN.

I guess changing the default to PTHREAD_STACK_MIN is a good idea.
Waiting for your PR!

1 Like

Hi @gedeonag to add the PR, do I make a new branch? (I don’t have the right to do that). How do I go about it?

Contributing guidelines are found here - click on the link for the full document including how to make a pull request.

Hi @cobusve @gedeonag @aggarg
Here is my PR:

Thank you!

You will need to fork the repository, add your changes, then create a pull request

Done. Waiting for acceptance. Thank you.

1 Like

It is more accurate to say - If the supplied memory region for stack is not large enough (i.e. smaller than PTHREAD_STACK_MIN), then it is not set as the task stack. As a result, the supplied memory is not used as the task stack. Instead, the default stack assigned by the pthread_create is used as the task stack.

Yes, I guess it is a good idea to add a warning message. Probably something like the following at this line:

    iRet = pthread_attr_setstack( &xThreadAttributes, pxEndOfStack, ulStackSize );
    if( iRet != 0 )
    {
        fprintf( stderr, "[WARN] pthread_attr_setstack failed with return value: %d. Default stack will be used.\n", iRet );
        fprintf( stderr, "[WARN] Increase the stack size to PTHREAD_STACK_MIN.\n" );
    }

Would you also like to raise a PR for this?

1 Like