Memory Blocks Allocator

The Memory Blocks Allocator allows memory blocks to be dynamically allocated from a designated memory region, where:

  • All memory blocks have a single fixed size.

  • Multiple blocks can be allocated or freed at the same time.

  • A group of blocks allocated together may not be contiguous. This is useful for operations such as scatter-gather DMA transfers.

  • Bookkeeping of allocated blocks is done outside of the associated buffer (unlike memory slab). This allows the buffer to reside in memory regions where these can be powered down to conserve energy.

Concepts

Any number of Memory Blocks Allocator can be defined (limited only by available RAM). Each allocator is referenced by its memory address.

A memory blocks allocator has the following key properties:

  • The block size of each block, measured in bytes. It must be at least 4N bytes long, where N is greater than 0.

  • The number of blocks available for allocation. It must be greater than zero.

  • A buffer that provides the memory for the memory slab’s blocks. It must be at least “block size” times “number of blocks” bytes long.

  • A blocks bitmap to keep track of which block has been allocated.

The buffer must be aligned to an N-byte boundary, where N is a power of 2 larger than 2 (i.e. 4, 8, 16, …). To ensure that all memory blocks in the buffer are similarly aligned to this boundary, the block size must also be a multiple of N.

Due to the use of internal bookkeeping structures and their creation, each memory blocks allocator must be declared and defined at compile time.

Internal Operation

Each buffer associated with an allocator is an array of fixed-size blocks, with no wasted space between the blocks.

The memory blocks allocator keeps track of unallocated blocks using a bitmap.

Memory Blocks Allocator

Internally, the memory blocks allocator uses a bitmap to keep track of which blocks have been allocated. Each allocator, utilizing the sys_bitarray interface, gets memory blocks one by one from the backing buffer up to the requested number of blocks. All the metadata about an allocator is stored outside of the backing buffer. This allows the memory region of the backing buffer to be powered down to conserve energy, as the allocator code never touches the content of the buffer.

Multi Memory Blocks Allocator Group

The Multi Memory Blocks Allocator Group utility functions provide a convenient to manage a group of allocators. A custom allocator choosing function is used to choose which allocator to use among this group.

An allocator group should be initialized at runtime via sys_multi_mem_blocks_init(). Each allocator can then be added via sys_multi_mem_blocks_add_allocator().

To allocate memory blocks from group, sys_multi_mem_blocks_alloc() is called with an opaque “configuration” parameter. This parameter is passed directly to the allocator choosing function so that an appropriate allocator can be chosen. After an allocator is chosen, memory blocks are allocated via sys_mem_blocks_alloc().

Allocated memory blocks can be freed via sys_multi_mem_blocks_free(). The caller does not need to pass a configuration parameter. The allocator code matches the passed in memory addresses to find the correct allocator and then memory blocks are freed via sys_mem_blocks_free().

Usage

Defining a Memory Blocks Allocator

A memory blocks allocator is defined using a variable of type sys_mem_blocks_t. It needs to be defined and initialized at compile time by calling SYS_MEM_BLOCKS_DEFINE.

The following code defines and initializes a memory blocks allocator which has 4 blocks that are 64 bytes long, each of which is aligned to a 4-byte boundary:

SYS_MEM_BLOCKS_DEFINE(allocator, 64, 4, 4);

Similarly, you can define a memory blocks allocator in private scope:

SYS_MEM_BLOCKS_DEFINE_STATIC(static_allocator, 64, 4, 4);

A pre-defined buffer can also be provided to the allocator where the buffer can be placed separately. Note that the alignment of the buffer needs to be done at its definition.

uint8_t __aligned(4) backing_buffer[64 * 4];
SYS_MEM_BLOCKS_DEFINE_WITH_EXT_BUF(allocator, 64, 4, backing_buffer);

Allocating Memory Blocks

Memory blocks can be allocated by calling sys_mem_blocks_alloc().

int ret;
uintptr_t blocks[2];

ret = sys_mem_blocks_alloc(allocator, 2, blocks);

If ret == 0, the array blocks will contain an array of memory addresses pointing to the allocated blocks.

Releasing a Memory Block

Memory blocks are released by calling sys_mem_blocks_free().

The following code builds on the example above which allocates 2 memory blocks, then releases them once they are no longer needed.

int ret;
uintptr_t blocks[2];

ret = sys_mem_blocks_alloc(allocator, 2, blocks);
... /* perform some operations on the allocated memory blocks */
ret = sys_mem_blocks_free(allocator, 2, blocks);

Using Multi Memory Blocks Allocator Group

The following code demonstrates how to initialize an allocator group:

sys_mem_blocks_t *choice_fn(struct sys_multi_mem_blocks *group, void *cfg)
{
    ...
}

SYS_MEM_BLOCKS_DEFINE(allocator0, 64, 4, 4);
SYS_MEM_BLOCKS_DEFINE(allocator1, 64, 4, 4);

static sys_multi_mem_blocks_t alloc_group;

sys_multi_mem_blocks_init(&alloc_group, choice_fn);
sys_multi_mem_blocks_add_allocator(&alloc_group, &allocator0);
sys_multi_mem_blocks_add_allocator(&alloc_group, &allocator1);

To allocate and free memory blocks from the group:

int ret;
uintptr_t blocks[1];
size_t blk_size;

ret = sys_multi_mem_blocks_alloc(&alloc_group, UINT_TO_POINTER(0),
                                 1, blocks, &blk_size);

ret = sys_multi_mem_blocks_free(&alloc_group, 1, blocks);

API Reference

group mem_blocks_apis

Defines

SYS_MEM_BLOCKS_DEFINE(name, blk_sz, num_blks, buf_align)

Create a memory block object with a new backing buffer.

Parameters:
  • name – Name of the memory block object.

  • blk_sz – Size of each memory block (in bytes).

  • num_blks – Total number of memory blocks.

  • buf_align – Alignment of the memory block buffer (power of 2).

SYS_MEM_BLOCKS_DEFINE_STATIC(name, blk_sz, num_blks, buf_align)

Create a static memory block object with a new backing buffer.

Parameters:
  • name – Name of the memory block object.

  • blk_sz – Size of each memory block (in bytes).

  • num_blks – Total number of memory blocks.

  • buf_align – Alignment of the memory block buffer (power of 2).

SYS_MEM_BLOCKS_DEFINE_WITH_EXT_BUF(name, blk_sz, num_blks, buf)

Create a memory block object with a providing backing buffer.

Parameters:
  • name – Name of the memory block object.

  • blk_sz – Size of each memory block (in bytes).

  • num_blks – Total number of memory blocks.

  • buf – Backing buffer of type uint8_t.

SYS_MEM_BLOCKS_DEFINE_STATIC_WITH_EXT_BUF(name, blk_sz, num_blks, buf)

Create a static memory block object with a providing backing buffer.

Parameters:
  • name – Name of the memory block object.

  • blk_sz – Size of each memory block (in bytes).

  • num_blks – Total number of memory blocks.

  • buf – Backing buffer of type uint8_t.

Typedefs

typedef struct sys_mem_blocks sys_mem_blocks_t

Memory Blocks Allocator.

typedef struct sys_multi_mem_blocks sys_multi_mem_blocks_t

Multi Memory Blocks Allocator.

typedef sys_mem_blocks_t *(*sys_multi_mem_blocks_choice_fn_t)(struct sys_multi_mem_blocks *group, void *cfg)

Multi memory blocks allocator choice function.

This is a user-provided functions whose responsibility is selecting a specific memory blocks allocator based on the opaque cfg value, which is specified by the user as an argument to sys_multi_mem_blocks_alloc(). The callback returns a pointer to the chosen allocator where the allocation is performed.

NULL may be returned, which will cause the allocation to fail and a -EINVAL reported to the calling code.

Param group:

Multi memory blocks allocator structure.

Param cfg:

An opaque user-provided value. It may be interpreted in any way by the application.

Return:

A pointer to the chosen allocator, or NULL if none is chosen.

Functions

int sys_mem_blocks_alloc(sys_mem_blocks_t *mem_block, size_t count, void **out_blocks)

Allocate multiple memory blocks.

Allocate multiple memory blocks, and place their pointers into the output array.

Parameters:
  • mem_block – Pointer to memory block object.

  • count[in] Number of blocks to allocate.

  • out_blocks[out] Output array to be populated by pointers to the memory blocks. It must have at least count elements.

Return values:
  • 0 – Successful

  • -EINVAL – Invalid argument supplied.

  • -ENOMEM – Not enough blocks for allocation.

int sys_mem_blocks_alloc_contiguous(sys_mem_blocks_t *mem_block, size_t count, void **out_block)

Allocate a contiguous set of memory blocks.

Allocate multiple memory blocks, and place their pointers into the output array.

Parameters:
  • mem_block – Pointer to memory block object.

  • count[in] Number of blocks to allocate.

  • out_block[out] Output pointer to the start of the allocated block set

Return values:
  • 0 – Successful

  • -EINVAL – Invalid argument supplied.

  • -ENOMEM – Not enough contiguous blocks for allocation.

int sys_mem_blocks_get(sys_mem_blocks_t *mem_block, void *in_block, size_t count)

Force allocation of a specified blocks in a memory block object.

Allocate a specified blocks in a memory block object. Note: use caution when mixing sys_mem_blocks_get and sys_mem_blocks_alloc, allocation may take any of the free memory space

Parameters:
  • mem_block – Pointer to memory block object.

  • in_block[in] Address of the first required block to allocate

  • count[in] Number of blocks to allocate.

Return values:
  • 0 – Successful

  • -EINVAL – Invalid argument supplied.

  • -ENOMEM – Some of blocks are taken and cannot be allocated

int sys_mem_blocks_is_region_free(sys_mem_blocks_t *mem_block, void *in_block, size_t count)

check if the region is free

Parameters:
  • mem_block – Pointer to memory block object.

  • in_block[in] Address of the first block to check

  • count[in] Number of blocks to check.

Return values:
  • 1 – All memory blocks are free

  • 0 – At least one of the memory blocks is taken

int sys_mem_blocks_free(sys_mem_blocks_t *mem_block, size_t count, void **in_blocks)

Free multiple memory blocks.

Free multiple memory blocks according to the array of memory block pointers.

Parameters:
  • mem_block – Pointer to memory block object.

  • count[in] Number of blocks to free.

  • in_blocks[in] Input array of pointers to the memory blocks.

Return values:
  • 0 – Successful

  • -EINVAL – Invalid argument supplied.

  • -EFAULT – Invalid pointers supplied.

int sys_mem_blocks_free_contiguous(sys_mem_blocks_t *mem_block, void *block, size_t count)

Free contiguous multiple memory blocks.

Free contiguous multiple memory blocks

Parameters:
  • mem_block – Pointer to memory block object.

  • block[in] Pointer to the first memory block

  • count[in] Number of blocks to free.

Return values:
  • 0 – Successful

  • -EINVAL – Invalid argument supplied.

  • -EFAULT – Invalid pointer supplied.

void sys_multi_mem_blocks_init(sys_multi_mem_blocks_t *group, sys_multi_mem_blocks_choice_fn_t choice_fn)

Initialize multi memory blocks allocator group.

Initialize a sys_multi_mem_block struct with the specified choice function. Note that individual allocator must be added later with sys_multi_mem_blocks_add_allocator.

Parameters:
  • group – Multi memory blocks allocator structure.

  • choice_fn – A sys_multi_mem_blocks_choice_fn_t callback used to select the allocator to be used at allocation time

void sys_multi_mem_blocks_add_allocator(sys_multi_mem_blocks_t *group, sys_mem_blocks_t *alloc)

Add an allocator to an allocator group.

This adds a known allocator to an existing multi memory blocks allocator group.

Parameters:
  • group – Multi memory blocks allocator structure.

  • alloc – Allocator to add

int sys_multi_mem_blocks_alloc(sys_multi_mem_blocks_t *group, void *cfg, size_t count, void **out_blocks, size_t *blk_size)

Allocate memory from multi memory blocks allocator group.

Just as for sys_mem_blocks_alloc(), allocates multiple blocks of memory. Takes an opaque configuration pointer passed to the choice function, which is used by integration code to choose an allocator.

Parameters:
  • group[in] Multi memory blocks allocator structure.

  • cfg[in] Opaque configuration parameter, as for sys_multi_mem_blocks_choice_fn_t

  • count[in] Number of blocks to allocate

  • out_blocks[out] Output array to be populated by pointers to the memory blocks. It must have at least count elements.

  • blk_size[out] If not NULL, output the block size of the chosen allocator.

Return values:
  • 0 – Successful

  • -EINVAL – Invalid argument supplied, or no allocator chosen.

  • -ENOMEM – Not enough blocks for allocation.

int sys_multi_mem_blocks_free(sys_multi_mem_blocks_t *group, size_t count, void **in_blocks)

Free memory allocated from multi memory blocks allocator group.

Free previous allocated memory blocks from sys_multi_mem_blocks_alloc().

Note that all blocks in in_blocks must be from the same allocator.

Parameters:
  • group[in] Multi memory blocks allocator structure.

  • count[in] Number of blocks to free.

  • in_blocks[in] Input array of pointers to the memory blocks.

Return values:
  • 0 – Successful

  • -EINVAL – Invalid argument supplied, or no allocator chosen.

  • -EFAULT – Invalid pointer(s) supplied.