Stack for task  - Page 1
Forum section: electronics / general

author: lorenrus
author_rank: Regular Contributor
timestamp: May 23, 2022, 08:47:32 pm
content: I'm working on creating my own scheduler. I was thinking about the stack that must be assigned to each task. (Passing the stack value I thought I'd put it as an input parameter when creating the task).All automatic variables that would be created within the task should be saved within their respective stack, correct?Also I believe that to do this surely the various systems like FreeRTOS will work with the assembler to move the automatic variables within the task in the op stack, right?I was thinking if it was possible to do it in another way by saving the variables in the stack to do a simulation on PC.Thank you

author: brucehoult
author_rank: Super Contributor
timestamp: May 24, 2022, 02:52:14 am
content: This is a complex topic with no easy answers!Trying to allocate fixed stacks in a program with a large number of threads is a fraught process, risking either wasting a lot of precious RAM if you over-allocate, or else risking some thread exceeding the stack limits. If you've got virtual memory (especially 64 bit) then it is cheap to over-allocate, but not in an embedded system.If the frequency of thread switching is lowish compared to function calls then you can use a single stack and on a thread switch copy the actual amount that is in-use off to storage somewhere else such as a heap or a single large buffer. And you can compact that buffer as needed, or allocate it in fixed-sized chunks and divide a large copied stack between several chunks -- anything you want, as the task won't use its stack while its in storage.If the frequency of thread switching is very high compared to function calls then you might prefer to not use a stack at all, but have each function malloc() storage for its locals, and either free() them when it returns or else use a garbage collector. This requires programming in assembly language, using a custom compiler, or else being very very careful with how you write functions.

author: lorenrus
author_rank: Regular Contributor
timestamp: May 24, 2022, 05:25:36 am
content: Hello and thanks for the reply. In fact, I imagined that it was a complex topic and that it did not have an explicit answer. yes i also had thought about using malloc. However, the doubt that I had about it was that if a user wants to use my scheduler, for example, when he creates his function he should be using a local evil inside his task himself, right? Because if you wanted to do it automatically you would have to write some code in assembler, right?

author: Doctorandus_P
author_rank: Super Contributor
timestamp: May 24, 2022, 06:55:51 am
content: Ever since C++ 11 multithreaded support is built in.https://isocpp.org/wiki/faq/cpp11-library-concurrencyhttps://en.cppreference.com/w/cpp/thread/thread/thread

author: brucehoult
author_rank: Super Contributor
timestamp: May 24, 2022, 07:12:30 am
content: If you use heap-based activation frames instead of a stack then you pretty much can't use a standard stack-based compiler for anything at all.If you use standard stack code while a thread is running then with a co-operative scheduler (threads are only switched when a specific function such as yield() is called) then you can pretty much write everything relatively portably in C by abusing setjmp & longjmp, both for saving and restoring registers and for adjusting the stack pointer. And take the address of a local variable to figure out how much stack is in use. But that's pretty tricky and the assembly-language code needed is relatively straightforward.If you want to task-switch in response to interrupts then you have no choice but to use assembly language.

author: brucehoult
author_rank: Super Contributor
timestamp: May 24, 2022, 07:20:22 am
content: Quote from: Doctorandus_P on May 24, 2022, 06:55:51 amThat stuff gives you absolutely no visibility or control into either the thread stacks or task switching. That might be ok in a Mac/Windows/Linux environment with VM, but it's hardly likely to be appropriate for a highly constrained microcontroller environment. Or even available.

author: lorenrus
author_rank: Regular Contributor
timestamp: May 24, 2022, 01:32:13 pm
content: Ok Now i read more about the functions.I believe however that the best thing would be to implement it on a microcontroller, in which in the linker file I am going to declare a dedicated memory section which I then recall as an attribute. what do you think about it ? thank you

author: Silenos
author_rank: Regular Contributor
timestamp: May 24, 2022, 04:24:30 pm
content: What architecture?context switch on @cortex-m:- I used to do all static "tasks", so static tasks and stuff, iirc I reserved memory in linker,- you only need writing assembly to directly ride over registers to switch the context, and c-inlined assembly doesn't allow that; the rest "assembly" stuff you could hammer with intristics. Mind you would have a hard time in doing all that without the architecture and asm knowledge anyway.- there are traps in assuring the context switch is done correctly every time no matter what exceptions (including interrupts) state is at any time, heavily depends on exception architecture,- there is a lot of hardware stuff going during exceptions, and mostly you need to just manpilate the msp/psp pointers correctly. Datasheets reading required.- on mcu with fpu there are more fpu registers, much trickery potential,- rememeber to properly initialize stack frames, as slapping the uninitialzied memory/invalid status register will crash the device.- scheduler is a separate thing and can be banged in c, it is not really dependant on architectureNot sure about your question, how or were you want to do this..

author: rstofer
author_rank: Super Contributor
timestamp: May 24, 2022, 04:34:52 pm
content: I suspect that FreeRTOS is the most common RTOS for embedded systems. Ports and examples abound!If that is true, my first question would be: How does FreeRTOS do it? My second question would be: Why don't I simply use FreeRTOS?

author: SiliconWizard
author_rank: Super Contributor
timestamp: May 24, 2022, 06:05:36 pm
content: The most common approach is to allocate a dedicated stack for each task. Each stack doesn't have to be of the same size. The stack size can be set when you create the task, for instance, which is also a very common approach.The different stacks are usually allocated in a common pool.Then it's just a matter of setting the corresponding stack pointer upon task switch, which is just part of the context you're going to restore, along with all other saved registers. It's just going to be transparent as part of context switching.

author: newbrain
author_rank: Super Contributor
timestamp: May 24, 2022, 06:40:43 pm
content: Quote from: SiliconWizard on May 24, 2022, 06:05:36 pmAnd that, as also suggested by rstofer, is exactly what FreeRTOS and e.g. AzureRTOS (ThreadX) do.For FreeRTOS, the assembly code needed to switch in a new task is about a dozen of instructions (in a Cortex M7 Arm port), some critical section is peppered here and there, but 99% is written in C.OTOH ThreadX has a lot more assembly code, but I suspect that's more a design decision, than real necessity.The advantages of such approach are many:Tasks are simple C functions. No strange attributes or magic code to insert.The compiler does not need to be aware of compiling an RTOS program: the stack frames, as part of a task context, are switched in and out by the RTOS scheduler but are in other respects just regular ones.The scheduler can take advantage of HW features such as stack limit enforcing on Cortex-M33.etc. etc.

author: hans
author_rank: Super Contributor
timestamp: May 24, 2022, 08:25:28 pm
content: My 2cts..I've written a RTOS in the past as a learning experience and the least amount of assembly required is that of a stack switch. It will typically look something like:Code:[Save all registers][Store SP]CALL osSwitchStacks[Load SP][Load all registers][Resume task]The switchContext function has a prototype like void* osSwitchStacks(void*). It's called with the old SP, saves it to the active task object (to know what the last top of stack was), and then switches out for the new SP. To prepare a task, you would simply fill all initial values for the registers in the order they are pushed/popped, where the entry code is used for PC.This is really a minimal example. There are lots of details to get right, like FP registers, coprocessor (if relevant to architecture), stacks for kernel/tasks w.r.t. interrupt handling, etc. By no means am I a RTOS expert nor have dealt recently with them to recall all details completely.Note that the above code doesn't run the scheduler itself. In my view, the scheduler could be triggered at a (fixed) timer interval to see if it has to change the active task based on the current conditions (e.g. perhaps a timer has passed, or you have a round-robin scheduler). You would also need to run the scheduler when a task blocks on some call (e.g. a delay, a wait on some resource, etc.).In my experience working with a RTOS, you'll find that most tasks end up in a blocked state until something triggers a change in the system. You could also have more direct context switches without running the scheduler. For example, say you have a low priority task currently running and a high priority task is waiting for an event flag to be set. The function that sets the flag is called, and can look something like:Code:void osSignalEvent(OsTask_t* pTask, uint32_t flag) {pTask->signal_flags |= flag;if (pTask->signal_mask & pTask->signal_flags) {pTask->state = READY;if (osActiveTask->priority < pTask->priority) {osPendingTask = pTask;osPendContextSwitch();}}Finally, to estimate how much stack you need for a given task.. with the GCC compiler it can output a stack consumption estimation by passing -fstack-usage during compilation. I'm not sure how accurate it is depending on the code you use (I have no idea how good it is at tracking down function pointer calls).Other methods include filling the stack with a predictable pattern, and then investigating after sufficient system runtime up till which point that pattern has been touched.

author: TomS_
author_rank: Frequent Contributor
timestamp: May 25, 2022, 06:08:51 am
content: Quote from: lorenrus on May 23, 2022, 08:47:32 pmFreeRTOS doesn't do anything regarding where variables get placed. Tasks are just functions, so the way variables get treated is just like any other function - the compiler/assembler/linker will determine where things go.Unless you make your variables static then they are probably going to end up on the stack.