mem thing is a memory allocator that sets on top of a fixed sized memory allocated by the caller. It is designed for apps that uses configuration or control-plan data and would want to stash it in a shared memory map that outlives the processes that either produce, consume, or both this data. It can do:
- Fast - in most cases - arbitrary size memory allocation.
- Cached memory allocation. Memory is allocated once by caller. Freed memory is cached and is not returned to system. This is not designed as a buddy allocator though it shares some properties with it.
- Ability to commit memory segments. For applications that needs to cache this data to survive system reboots they will have to persist this data on disk. The allocator has automatic persistence for its own internal data and offers an external
commit
interface for callers. The commit interface is a function provided by caller. - Automatic detection of memory corruptions (build using
__BAD_MEM__
macro) via memory poisoning. - The allocator allocates in o(1) in most cases. However if the entire memory was allocated then arbitrary freed max allocation time is o(n) where n is the number of objects allocated.
- An allocator that sits on top of a shared memory object mapped into proc memory. The example uses no persistence run
make example-things-mem
. - An allocator that sits on a memory mapped (with file backing) the application provides its own persistence func to commit memory via
msync(2)
calls runmake example-things-mem-persisted
unit test code should provide enough details on the ins and outs of the fixed memory allocator.
The allocator does not support memory moves. Memory moves is a situation where an application creates a memory map at address xyz
then restarts, then remap the same memory to a different address xxz
essentially invalidating all the pointers in any in memory object graph including allocator own. For now we assume that apps will always remap at the same address. If and only if we find that apps are unable to always do that then we can find a solution but that will require offering adjust(..)
interface for caller to fix the memory shift that has happened. For that reason we always used fixed maps in our examples
The code is deliberately heavy on unit testing (we use munit
https://nemequ.github.io/munit/ - Thanks!). You can run make unit-tests
to go through all tests. Additional make targets exist for selective module specific unit tests.
struct some {
// some user specific data
};
void *m = // {memory mapped location or memory mapped with file backing}
size m_length = <whatever>;
// create the fixed allocator
struct fmem* fm = fmem_create_new(m, m_length, 0 /* defaults each allocation to whatever allocator have */ );
if (fm < 0) /* error */ all our errors are returned as negative values
// start allocating
void * my_mem = fmem_alloc(fm, sizeof(struct some));
if(m <0) /* error */
struct some *s = (struct some) my_mem;
// if s is the root index to the object graph then i can store it
fm->user1 = s;
/* process restarts and comes back with memory mapped at the same location
struct fmem *fm = fmem_from_existing(m); // if mem checks enabled and memory wsa corrupted the entire process will exit.
// get reference to our stashed index
struct some *s = (struct some *) fm->user1;
// memory can be freed by
fmem_free(fm, my_mem);