Many users have asked me how they handle Memory Management when they use the Willert RXF as Framework.
There are 3 types of memory usage in C/C++:
- Compile time (or static)
this means that you declare something and the compiler/linker will take care of locating and allocating memory for it. (e.g. char MyArr; will declare a 20 character array)
You can find variables like this in the linker map file.
This is what malloc uses and what many RTOSes use when allocating memory for their structures.You cannot find it in the map file specifically, you will find the heap size there. But the variables that use it are located in run-time.
The most dangerous one… local variables are located on the stack as are function calls. compilers and or RTOSes offer stack check routines but they can be not reliable. Mostly they work with a standard pattern that is written on the stack (0xAA or 0x55) and compare that after each function call.
The RXF uses its own memory management. If you select the <<RXF Component>> stereotype for your current component and generate code for it, there will be „malloc()“ statements in the generated code.
But… this does not call malloc. Instead these calls are mapped (using a macro in WSTmodelling.h) to a function called WST_MEM_getMemory().
This function uses predefined and pre-allocated memory blocks. This shifts the use from heap to compile time.Why are they there and why do we not use malloc?
Easy. We do not use malloc for two main reasons:
- Memory de-fragmentation
- Malloc is non-reentrant.
Using our own memory allocation routines solve both problems. Our routines are protected using semaphores and because we only have pre-defined sizes, fragmentation cannot occur. How does this work?
There are a couple of properties that define size and number of these predefined blocks. Here we can define the exact size (in bytes) of each block type.There are 5 different block types:
– Tiny, Small, Medium, Large and Huge
The properties where you can define the number of blocks and the size are called:
- C_CG::Configuration::WSTTinyBufferSize - C_CG::Configuration::WSTInitialTinyBuffers - C_CG::Configuration::WSTSmallBufferSize - C_CG::Configuration::WSTInitialSmallBuffers - C_CG::Configuration::WSTMediumBufferSize - C_CG::Configuration::WSTInitialMediumBuffers - C_CG::Configuration::WSTLargeBufferSize - C_CG::Configuration::WSTInitialLargeBuffers - C_CG::Configuration::WSTHugeBufferSize - C_CG::Configuration::WSTInitialHugeBuffers
Setting the number of a certain type of blocks to 0 will remove every bit of code used for that specific block type.
NOTE!: make block sizes sequential increasing. So Tiny MUST be the smallest and Huge MUST be the largest. Otherwise the getMemory function will not work properly.
Taming the RXF
The properties for setting these sizes are available in Component and Configuration, we recommend setting them in your configurations. In that way you can easily create other configurations with different sizes to experiment.
Can I check how many blocks I have used and if they have the right size?
Yes you can check how many blocks you have used, check the HighWaterMark section. The size is a bit more difficult.You need to check the map file for the sizes of the elements that you allocate. You can also enter your debugger, set a breakpoint to the WST_MEM_getMemory routine. Each time you enter the breakpoint your application is requesting memory. Check the „requested_size“ variable and write them down.The call stack will reveal which routine requested memory.
As a rule of thumb: use one block type for events, one for events with parameter, and one for small classes. Set the other two to 0 so they won’t be there. Only use them if you want to use a finer use of your available memory. For larger classes use static allocation. If you use tasks, you will need memory for the allocated message queues, calculate their size and use another block type for that.
The best way to check how many blocks you have used is off course to use the Embedded UML Target Debugger, it shows you these numbers directly!
What to do if I run out of free memory blocks?
The allocator tries to use the best fitting block. So if you have blocks of 10, 20,30, 40 and 50 bytes, allocation 8 bytes will normally give you a 10 bytes block.When these are not there, you get a 20, then a 30 and so on.
If there is no fitting block left there are 2 options depending on the defines you set when compiling.
– The program will call the error routine with the Error:WST_ERR_ALLOC_TOO_LARGE.
You can (or actually should) change the error routine and adapt it to your own needs. (e.g write in a logfile or something like that)
– The original malloc routine is called.
This will only succeed if you reserved enough heap space so that malloc can give you the memory that you need. If not you will inevitably run into some kind of trap for using illegal memory.There are RXF versions that run into the error handler (Error:WST_ERR_ALLOC_FAILED) when malloc returns NULL.
What Defines and Properties influence the memory usage?
Using Tasks will also use your predefined memory blocks. Every task has a message queue, depending on the used RTOS there will be memory allocated for that.There are properties for tasks that influence the used memory.
CG::Class::ActiveMessageQueueSize will set the message queue size.There is a default size, this is what the RXF uses if you do not define your own.The size of the message queue is defined as „number of messages“.A message in UML is an event and it is sent by reference so there is only one pointer per message.The size of the pointer depends on the used CPU/Memory model. So on an ARM Cortex with the Keil compiler this means that a message queue size of 20 will use 80 bytes of blocks.
The Timer Array is static, it is depending on the property C_CG::Configuration::WSTMaxTimeouts. The array is allocated at compile time.
WST_CFG_HIGHWATERMARKS.When set to true, extra code is used to keep the largest number of reserved blocks.You can check the number in theWillert Embedded UMLTarget
Debugger or in your own source code debugger by typing „highwater“ in a watch window.This is an array that will show you the actually largest number of used blocks.
NO_MALLOC.When set, this will cause the RXF to NEVER use malloc. Rhapsody generated code can still use malloc, but the framework will not.
WST_FORCE_ALLOC_TOO_LARGE. Causes the RXF to use malloc when it runs out of memory blocks.
That’s it for today! Happy modeling with Rhapsody (and the RXF of course!)
Walter van der Heiden (email@example.com)