Allocators in the libc for the embedded use case
This proposal is likely not complete enough. The goal is to gather feedback and
set up a concrete plan/strategy for embedded allocators in the libc.
The libc project uses the Scudo hardened allocator
as the allocator for server applications. This is a proposal to add a light
weight allocator implementation in the libc project which will serve embedded
use cases. The allocator requirements of embedded platforms have a lot of
variance - some use cases require a small lightweight allocator which can be
used in single threaded applications, while few others require an allocator
which can work in multi-threaded contexts also. Likewise, some baremetal
applications only ever allocate and never deallocate, while few others require
a deallocation operation which also does defragmentation. The goal of this
proposal is two main things:
- Identify the variants of embedded allocators that should be supported by the
libc project. - Present the highlevel interface of these allocators and demonstrate how
that interface can be used to implement functions likemalloc
andfree
for the various embedded platforms.
A non-goal is to iron out implementation details - they will be discussed
during code review.
The axes of variations
As mentioned above, there are different variations of allocators that serve
embedded use cases. In this section, we identify the main axes of these
varations highlighting the variants the libc will support along each axes.
Thread safe vs single thread only allocator
An allocator which only needs to work in a single thread context can avoid the
overhead of locking/unlocking operations. This locking/unlocking overhead is not
only a runtime performance overhead, but also a code size overhead for single
threaded baremetal applications. To serve both the multi-threaded embedded use
case as well as the slim no-thread use case, the embedded allocators in the libc
project will provide a thread-safe variant as well as a single thread only
variant.
Platform memory allocation
At a highlevel, an allocator is an agent which manages memory it obtains from
the platform. For example, on Linux, an allocator can be implemented to manage
memory it obtains using the mmap
operation. On few other platforms, allocators
use sbrk
to expand the single block of memory they manage. The embedded
allocators in the libc will support both these methods of obtaining memory from
the underlying platform, namely mmap
style and the sbrk
style. Additionally,
the allocators will also support the case where it is not possible to obtain
additional memory from the platform - which is to say that they will manage a
fixed blob of memory.
No deallocations
The libc project will support use cases where deallocation and defragmentation
are considered code size overheads. It will be up to the application to craft
their allocations carefully and never invoke realloc
. For such applications,
the free
function will be implemented as a no-op (which should ideally never
be called).
Allocator Interface
The allocator will be an instance of the following class template:
template <ExpansionStyle EXPANSION_STYLE, bool DEALLOC>
struct AllocState;
template <typename MutexType , ExpansionStyle EXPANSION_STYLE, bool DEALLOC>
class Allocator {
MutexType mutex;
AllocState<EXPANSION_STYLE, DEALLOC> state;
public:
constexpr Allocator(AllocState<EXPANSION_STYLE, DEALLOC> init_state)
: state(init_state) {}
void *alloc(size_t);
void *realloc(void *, size_t);
void *aligned_alloc(size_t, size_t);
void free(void *);
};
The allocator instance can be a global object, or on platforms with
thread_local
support, it can be a thread_local
object. The allocator methods
will be specialized methods suitable for the corresponding variant. For example,
for no-deallocation allocator, the free
method can be implemented as a specialization:
template <typename MutexType, ExpansionStyle EXPANSION_STYLE>
void Allocator<MutexType, ExpansionStyle, false>::free(void *) {}
Allocator for single-threaded applications
Allocators which are to serve only a single-threaded application should be
instantiated with a passthrough mutex class which the methods named lock
and
unlock
which do nothing.