Posts Tagged ‘free’
Dynamic functionality in embedded systems is usually discouraged due to resource constraints.
However, some types of applications inherently require memory allocation.
As an example, protocols in sensor networks typically forward messages through nodes at a non-deterministic rate, given that the number of neighbors and transmission periods can vary.
Hence, many protocols require dynamic memory management to hold receiving messages until they are successfully forwarded.
A simple FIFO queue might not be always optimal because forwarding a message may involve multiple steps with delays (e.g. transmission acknowledgments).
In such scenario, the protocol would rather handle multiple messages at the same time, raising the possibility of a message received later be discarded first.
Unfortunately, out-of-the-box dynamic memory schemes, such as malloc/free, are not suitable for embedded systems which have quite different requirements in comparison to standard desktop systems.
Follows a list of issues concerning memory management schemes in embedded systems:
- Memory corruption
Many embedded systems lack memory protection, and continuous allocations in the heap may end up corrupting the stack (and vice versa).
- Run-time overhead
Memory management requires extra run-time bookkeeping. Also, in the context of embedded systems, a predictable execution model can be even more important than the fastest scheme on the average.
- Metadata overhead
Metadata used by the memory manager can spend precious bytes (e.g. linked lists of free blocks).
- Memory fragmentation
For constrained memory platforms, unusable holes between and inside allocated blocks (external and internal fragmentation, respectively) can waste a big percentage of available memory.
- Unreproducible execution
Successive executions of the same program may allocate memory in different ways, possibly leading to different outcomes (e.g. an allocation fail).
- Deallocation hazards
Properly deallocating memory is far from trivial. A missed deallocation leads to a memory leak that wastes memory, while deallocating a memory block still in use leads to a dangling pointer that will eventually crash the application.
As the C standard is loose about these issues, out-of-the-box malloc/free can perform bad in all items.
Furthermore, deallocation hazards are inherent in schemes that require an explicit free operation.
Garbage collected systems eliminate deallocation hazards, but may incur unacceptable run-time overheads.
Embedded systems usually rely on memory pools to manage dynamic memory.
In the context of sensor networks, both TinyOS and Coniki OSes offer and promote the use of memory pools (through Pool and MEMB, respectively).
A memory pool allocates N predefined fixed-sized blocks of memory that can be used by the application.
Most of the raised concerns are alleviated with this scheme:
- Because the allocation is static, the maximum amount of memory is known at compile time, reducing considerably the risk of memory corruption.
- The run-time overhead is minimal as implementations use simple arrays to hold the memory blocks. For instance, in the TinyOS implementation both allocation and deallocation are O(1).
- TinyOS’ Pools use an auxiliary vector of size N to hold pointers for free blocks.
- Regardless of different allocation patterns in applications, memory pools will always guarantee the minimal N of memory blocks. Hence, external fragmentation is non-existent. Internal fragmentation, however, can be an issue and is discussed below.
- Given that the memory operations are simple and handle fixed-size blocks, the execution is always deterministic and predictable.
- Memory pools are still manipulated through malloc/free-like operations. Hence, all challenges to properly deallocate memory still hold.
Internal fragmentation occurs when an allocated memory block is bigger than the requested size.
Given that memory pools can only handle fixed-size blocks, any allocation that requests smaller blocks will contain internal fragmentation (allocation of bigger blocks always fail).
That said, embedded applications are usually simple and contain only one or two different object units that require dynamic allocation.
This way, an application will use a different memory pool for each kind of object, thus also eliminating internal fragmentation.
Memory pools are the way to go for dynamic allocation in embedded systems.
They offer memory compactness, efficient and small operations, and predictable execution.
However, programming dynamic applications is still hard and error prone, given that missing and wrong deallocations may lead to memory leaks and subtle crashes.
In the next post I will show how Céu offers safer and higher level mechanisms for dynamic applications, while using memory pools transparently under the hoods.