Understanding memory management is a very important aspect of embedded software development. With a broad and simplified generalization, we will divide memory allocation schemes into 2 categories:
- allocation on the stack, and
- allocation on the heap.
For the purpose of this discussion, the current subject will refer to embedded systems with RAM (either SRAM or DRAM) without MMU where C or C++ is used to build the logic implementation.
When little memory is required, one can let it be allocated on the stack (i.e. if the useful lifetime of the object is also limited to the time the function is executed). On the plus side, it is fast, allocation and deallocation is automatic. However, in most embedded systems, the memory reserved for the stack will be small to very small. Depending on the system, a task or interrupt stack could be a couple of tens of bytes. Therefor, it will be mainly used for passing function arguments and return addresses.
void function(void)
{
SomeObject A;
SomeObjectInit(&A);
DoSomething(&A);
return;
}
The second allocation method brings us to malloc() and free() or ‘allocation on the heap’, the solution of all memory allocation problems, but also the bringer of some extra issues which we will address later below:
- possible memory fragmentation
- danger of memory leaks
- non-deterministic timing
A third way – which was not mentioned in the intro – of reserving space or allocating global or static objects, is to define and declare them so that they are placed in the “.data” or “.bss” segment. It is automatic, the memory object is always there, but that also means you cannot get rid of it and it will always be present (resident) in that valuable and scarce memory resource.
static SomeObject A;
void function(void)
{
SomeObjectInit(&A);
DoSomething(&A);
return;
}
Possible problems with dynamic allocation
Memory fragmentation
External memory fragmentation can be addressed by using fixed-size memory pools. On the one hand, this is only the full solution if one always knows beforehand which allocation size will be requested. On the other hand, the danger of internal fragmentation lurks: Potentially, not all the memory in the allocated block is used and thus some space is wasted. If there are several types of allocation blocks that are regularly requested, one can create memory partitions. Some type of structures even can have their own partition. That way, the maximum number of objects can also be limited and, since it is embedded device development we are discussing here, that is a good thing.
Leaks
Everything that is allocated, should eventually also be freed. The exception to the rule, is of course the electronic embedded device. It only allocates, and sometimes it also frees object and memory, but several objects have a lifetime that only ends when the OFF button is pushed (and even then). However, that is no reason not to implement the proper shutdown procedure that also frees all the allocated objects and memory. That is a good way to detect memory leaks and check whether nothing has been left behind. Good advice that is not always followed: We all know that xDSL or Cable router we have to ‘power-disconnect’ every once and a while.
Timing
In order to use some form of dynamic allocation either from a global heap or a memory partition in an embedded device with hard (or even soft) real-time requirements, the allocation and deallocation procedure should also be time deterministic. When the program flow dictates it wants some memory, it should either be returned or not, but we do not want to block or wait for it. There can be no memory defragmentation process running that temporarily blocks task allocations: it would wreak havoc on the deterministic behavior.
Speak Your Mind