Author Topic: Weird issue with local variables located *outside* a FreeRTOS task stack  (Read 1580 times)

0 Members and 1 Guest are viewing this topic.

Offline peter-hTopic starter

  • Super Contributor
  • ***
  • Posts: 3697
  • Country: gb
  • Doing electronics since the 1960s...
ST 32F417 (arm32). ST Cube IDE.

This is the relevant part of a task called APP:

Code: [Select]
// Simple RTOS thread for testing stuff

void vAPPTask(void *pvParameters)
{
uint32_t count=0;
uint32_t err=0;
uint32_t page=0;
uint8_t buf[512];

// some code to stop buf[] getting optimised out

for (int i=0; i<512; i++)
{
buf[i]=0xfe;
}


In Cube IDE you can get a display of the FreeRTOS tasks (once stopped e.g. with a breakpoint) e.g.



Notice the APP task stack start and end addresses.

Now look at where buf[] is located in the variables list



It is entirely outside the task stack.

The SP is 0x100057e8 i.e. at the address of buf[0].

It looks like the stack allocation declared by FreeRTOS is after all local (stack based) variables within that task have been allocated.
I prefill the whole FreeRTOS stack area with 0xA5 so this memory dump shows (the uppermost part) the unused part of it



It also shows buf[512] filled with 0xFE with my loop, as expected. In between the two is some stuff like initialised variables.

At the very bottom of the memory dump is the unused stack of the next task. And so it goes, all the way up the memory (which is the CCM actually, 0x10000000 and for 64k).

Is this how it is supposed to work?

The APP task is created with
    xTaskCreate(vAPPTask, "APP", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY, NULL);
so it gets 512 words i.e. 2k bytes of stack.

But all these numbers are a bit vague and don't really add up.

The only foolproof way I have found of checking that the allocated stacks aren't overflowing is to look at the memory dump and see there is plenty of 0xA5s left for each task.

FreeRTOS offers a stack check feature which I think check if there is enough each time it switches tasks (only).
« Last Edit: May 18, 2022, 11:12:57 am by peter-h »
Z80 Z180 Z280 Z8 S8 8031 8051 H8/300 H8/500 80x86 90S1200 32F417
 

Offline abyrvalg

  • Frequent Contributor
  • **
  • Posts: 824
  • Country: es
Stack on Thumb grows downwards, so the initial SP of APP is 100051F8 + 2k = 100059F8, so your buf is well within the declared area and “Top of stack” is SP value after grows downwards.
 

Offline peter-hTopic starter

  • Super Contributor
  • ***
  • Posts: 3697
  • Country: gb
  • Doing electronics since the 1960s...
All stacks I have ever seen grow downwards; it is the value being reported in the FreeRTOS debugging feature in Cube (Eclipse) which shows only a part of it.

I think the distance between these two points in the memory dump is actually the stack value specified in the task invocation



It would be good to be able to see the stack remaining for each task during operation. The ST sample code includes an http server in which you can see the stack high water mark e.g.



but those values aren't directly usable either. What is needed is a task which plots the whole RTOS memory (the whole 64k CCM in this case) onto a graphical LCD, a pixel for each byte which is 0xA5 :) A good programming exercise ;)
Z80 Z180 Z280 Z8 S8 8031 8051 H8/300 H8/500 80x86 90S1200 32F417
 

Offline bson

  • Supporter
  • ****
  • Posts: 2270
  • Country: us
Perhaps FreeRTOS allocates stacks on the heap when tasks/threads are created.
 

Offline jc101

  • Frequent Contributor
  • **
  • Posts: 627
  • Country: gb
Perhaps FreeRTOS allocates stacks on the heap when tasks/threads are created.

Yep, a task's stack is allocated from the FreeRTOS heap when the task is created...

https://www.freertos.org/a00125.html
 

Online eutectique

  • Frequent Contributor
  • **
  • Posts: 391
  • Country: be
Which version of FreeRTOS are you using?

What are the addresses of the other three variables?

Place a breakpoint on the line void vAPPTask(void *pvParameters), start, when the breakpoint is hit, check the value of SP. Step into, check SP again.

My version is 10.4.1, I've got one task which has a similar layout, with a buffer of 1KiB which is allocated on task's stack, not outside.

Oh, and the use of configMINIMAL_STACK_SIZE is questionable, I would say. See https://www.freertos.org/a00110.html#configMINIMAL_STACK_SIZE
 

Offline peter-hTopic starter

  • Super Contributor
  • ***
  • Posts: 3697
  • Country: gb
  • Doing electronics since the 1960s...
Yes, you give it a chunk of RAM and as you define tasks it drops them into that one after the other, starting near the bottom (the lowest address). You can call that RAM block a "heap", I guess. It is a kind of "private heap", not one which uses the existing malloc(). Also nothing ever gets freed from this "heap". This product I am doing does not use malloc() because it is too dangerous. Well, it does it use it for larger buffers etc for certain product options which, once enabled, never terminate.

In this case I have given FreeRTOS the whole 64k CCM. It is adequate for the product but not with a massive margin, which is why I've been checking stack usage carefully.

What surprised me is that within the "stack space" specified for each task, it allocates any variables first and then reports as "stack size" what is left after that. This is confusing as hell because the RTOS stack start and end values reported in the Cube FreeRTOS parameter display don't make a lot of sense.

I still think the only sure way to check each task's stack usage is with a special pattern fill and then a memory dump after the system has been running for a while.

I am sure configMINIMAL_STACK_SIZE has confused a lot of people, being in words not bytes :)

FreeRTOS Kernel V10.0.1

I ought to check the other three variables but a) right now they are getting optimised out and b) I am sure they will be just before or just after the 512 byte buffer.
« Last Edit: May 18, 2022, 06:09:29 pm by peter-h »
Z80 Z180 Z280 Z8 S8 8031 8051 H8/300 H8/500 80x86 90S1200 32F417
 

Online eutectique

  • Frequent Contributor
  • **
  • Posts: 391
  • Country: be
My point about configMINIMAL_STACK_SIZE, is it enough to accommodate the buffer?

And one more thing -- do you have by any chance a global variable of the same name? It could shadow the local variable.
 

Offline peter-hTopic starter

  • Super Contributor
  • ***
  • Posts: 3697
  • Country: gb
  • Doing electronics since the 1960s...
Yes, it was. I have been going through all the task stacks and checking there is enough left on each one.
Globals and locals should not conflict, surely, unless the linker is broken? Interesting point though. I tend to prefix deliberate globals with g_
Z80 Z180 Z280 Z8 S8 8031 8051 H8/300 H8/500 80x86 90S1200 32F417
 

Offline TomS_

  • Frequent Contributor
  • **
  • Posts: 834
  • Country: gb
Your stack is 0x100051f8 (start) + 2048 (size, 512 * 4) = 0x100059f7 (end)

Your buffer occupies 0x100057e8 through 0x100059e7.

Looks like it is entirely within the task stack to me.

Also worth reiterating, but FreeRTOS does not decide where variables go - that will be up to the compiler/linker. As jc101 said, FreeRTOS will allocate stack from its own managed heap (you define the size of this in FreeRTOSConfig.h), but this is only true if you have enabled dynamic allocation. If dynamic allocation is disabled then you must statically allocate an area of memory and provide that to the task during creation. This allows you to build fully static applications which must fit into available memory at compile time, meaning you cant hit a situation where you dont have enough available memory at run time.
« Last Edit: May 19, 2022, 06:27:03 am by TomS_ »
 
The following users thanked this post: newbrain, harerod

Offline peter-hTopic starter

  • Super Contributor
  • ***
  • Posts: 3697
  • Country: gb
  • Doing electronics since the 1960s...
Interesting - thanks.

We allocate the whole CCM to the RTOS - 64k. It was a decision between using a bit of the main RAM (128k) but the CCM is good for this, you can't run code from it anyway, and the only issue is that you cannot DMA in it which made fast SPI feeding a little more complicated (you have to use a static buffer).

AIUI, stack based variables (basically everything declared within the RTOS task, which for the purpose of local variables is a function like any other) end up within the task's area - as my dumps above show, with that 512 byte buffer. And if you don't want it there, just put 'static' in front of it and it ends up in the main RAM. I've been doing this for the last year and a half that I've been working on this.

The key point is that these stack variables end up on the RTOS stack, not on the general stack which is now used only for ISRs (and all stack usage before the RTOS starts). One has to watch that because there isn't a huge amount of it.

That's AIUI, and it does mean the locations of stack variables are decided when the RTOS task starts, not by the linker, but that's true for all stack based variables. The linker fixes only statics.
Z80 Z180 Z280 Z8 S8 8031 8051 H8/300 H8/500 80x86 90S1200 32F417
 

Online newbrain

  • Super Contributor
  • ***
  • Posts: 1719
  • Country: se
And if you don't want it there, just put 'static' in front of it and it ends up in the main RAM.
Just remember that if you have more than one instance of the same task, they'll all share the same static declared variables.
FreeRTOS provides thread local storage pointers (five by default, IIRC) that can be used to give each instance its (static or malloced) memory area, or directly to hold  correctly sized integers.

Nandemo wa shiranai wa yo, shitteru koto dake.
 
The following users thanked this post: peter-h, TomS_

Offline abyrvalg

  • Frequent Contributor
  • **
  • Posts: 824
  • Country: es
Looks like you’re confusing “Top of stack” in that IDE screen for “end of stack”, but it looks more like “current SP” and the real end (calculated as start+2k) is not shown there at all (and that’s quite understandable, normally you care much more about stack overflows, watching the diff between start and current SP, underflows are rare). Is there some help page for that screen with explanation of each param?
 
The following users thanked this post: peter-h

Offline TomS_

  • Frequent Contributor
  • **
  • Posts: 834
  • Country: gb
You can use a stack based variable as a DMA source/destination as long as you never return far enough to take it out of scope.

In a task, which is not supposed to end, a stack based variable will never go out of scope because the task never ends, so you can use it for DMA becuase that memory will never be repurposed.

If your task calls a function that has a stack based variable in it, then you must be careful because that variable goes out of scope when that function returns.

Otherwise, yes, if you want to remove something from the stack you can make it static. I do the same in my own projects. Just be mindful of what newbrain mentioned if you are starting multiple copies of a task that uses static variables.

Also be mindful of storing pointers to dynamically allocated memory in thread local storage within a task - you will have to free that memory yourself before killing off a task.
 

Offline peter-hTopic starter

  • Super Contributor
  • ***
  • Posts: 3697
  • Country: gb
  • Doing electronics since the 1960s...
Quote
You can use a stack based variable as a DMA source/destination

Sure - except in the CCM.
Z80 Z180 Z280 Z8 S8 8031 8051 H8/300 H8/500 80x86 90S1200 32F417
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf