Skip to content

Memory Pools

Fixed-size block allocator with optional blocking alloc.

What it is

A memory pool hands out N blocks of the same fixed size from a preallocated buffer. Unlike the general-purpose heap, allocation and release are O(1) (free-list pop / push) and never fragment — once the pool is created the worst-case behaviour is fully known.

When to use a pool instead of /

  • Producer/consumer pipelines that recycle objects of identical shape (network packets, DMA buffers, dispatch messages).
  • Code paths that must succeed deterministically or block until a slot is free, instead of failing on a fragmented heap.
  • Builds with configSUPPORT_DYNAMIC_ALLOCATION == 0 — pools can run entirely on caller-provided storage, so the whole feature works without a heap at all (see Example 2).

Semantics at a glance

  • osMemoryPoolAlloc is blocking with timeout. It waits on a counting semaphore for a free slot, so when the pool is empty the calling task sleeps instead of spinning or returning NULL immediately. Pass 0 for non-blocking, osWaitForever to wait indefinitely. Returns NULL only on timeout or invalid input.
  • Block addresses are aligned to sizeof(void*); the requested block_size is rounded up to that alignment internally, so osMemoryPoolGetBlockSize may report a value larger than what you passed to osMemoryPoolNew.
  • osMemoryPoolNew, osMemoryPoolAlloc, osMemoryPoolFree and osMemoryPoolDeletecannot be called from ISR context. The getters (GetCapacity / GetBlockSize / GetCount / GetSpace) may be called from an ISR; the snapshot they return is unlocked and therefore best-effort.
  • osMemoryPoolDelete fails with osErrorResource if any block is still checked out — the caller is responsible for returning all blocks first.

Example 1 — Dynamic pool (heap-backed)

Quickest to set up; control block and backing memory are allocated from the heap. Suitable when configSUPPORT_DYNAMIC_ALLOCATION is on and the pool is created during normal startup.

cpp
#include <kernel/cmsis_os2_ext.h>

typedef struct { uint8_t payload[64]; uint16_t len; } packet_t;

static osMemoryPoolId_t g_pkt_pool;

void packets_init(void) {
    osMemoryPoolAttr_t a = { .name = "pkts" };
    g_pkt_pool = osMemoryPoolNew(32, sizeof(packet_t), &a);   // 32 blocks
}

void producer_task(void) {
    packet_t* p = osMemoryPoolAlloc(g_pkt_pool, osWaitForever); // blocks
    fill_packet(p);
    enqueue_to_consumer(p);
}

void consumer_task(packet_t* p) {
    handle(p);
    osMemoryPoolFree(g_pkt_pool, p);
}

Example 2 — Static pool (no heap required)

Provide both the control block storage (cb_mem) and the backing buffer (mp_mem). The pool will never call pvPortMalloc, so this works on a build with configSUPPORT_DYNAMIC_ALLOCATION == 0 and is the right choice for safety-critical paths where allocation failure at startup is unacceptable.

cpp
#define BLOCK_COUNT  16
#define BLOCK_SIZE   128

// Backing buffer must be at least BLOCK_COUNT * aligned(BLOCK_SIZE) bytes.
// Aligning the buffer itself avoids extra padding inside the pool.
static uint8_t s_pool_mem[BLOCK_COUNT * BLOCK_SIZE]
    __attribute__((aligned(sizeof(void*))));
static uint8_t s_pool_cb[64];   // opaque; sized generously for the control block

static osMemoryPoolId_t g_static_pool;

void static_pool_init(void) {
    osMemoryPoolAttr_t a = {
        .name    = "static",
        .cb_mem  = s_pool_cb,    .cb_size = sizeof(s_pool_cb),
        .mp_mem  = s_pool_mem,   .mp_size = sizeof(s_pool_mem),
    };
    g_static_pool = osMemoryPoolNew(BLOCK_COUNT, BLOCK_SIZE, &a);
}

If cb_size is too small for the internal control block, or mp_size is smaller than block_count * aligned(block_size), osMemoryPoolNew returns NULL.

Types

Name
structosMemoryPoolAttr_t
Optional attributes passed to osMemoryPoolNew.
typedef void *osMemoryPoolId_t
Opaque handle for a memory pool instance.

Functions Overview

Name
osMemoryPoolId_tosMemoryPoolNew(uint32_t block_count, uint32_t block_size, const osMemoryPoolAttr_t * attr)
Creates a new fixed-size memory pool.
void *osMemoryPoolAlloc(osMemoryPoolId_t mp_id, uint32_t timeout)
Allocates one block from the pool, optionally blocking.
osStatus_tosMemoryPoolFree(osMemoryPoolId_t mp_id, void * block)
Returns a previously allocated block to the pool.
uint32_tosMemoryPoolGetCapacity(osMemoryPoolId_t mp_id)
Returns the total number of blocks the pool was created with.
uint32_tosMemoryPoolGetBlockSize(osMemoryPoolId_t mp_id)
Returns the actual block size in bytes (after alignment).
uint32_tosMemoryPoolGetCount(osMemoryPoolId_t mp_id)
Returns the number of blocks currently checked out (in use).
uint32_tosMemoryPoolGetSpace(osMemoryPoolId_t mp_id)
Returns the number of blocks currently free.
osStatus_tosMemoryPoolDelete(osMemoryPoolId_t mp_id)
Destroys a pool and releases any storage it owns.

Types Documentation

typedef osMemoryPoolId_t

cpp
typedef void* osMemoryPoolId_t;

Opaque handle for a memory pool instance.

Function Details

function osMemoryPoolNew

cpp
osMemoryPoolId_t osMemoryPoolNew(
    uint32_t block_count,
    uint32_t block_size,
    const osMemoryPoolAttr_t * attr
)

Creates a new fixed-size memory pool.

Parameters:

  • block_count Number of blocks the pool will hold. Must be > 0.
  • block_size Size of each block in bytes (will be aligned up). Must be > 0.
  • attr Optional attributes; see osMemoryPoolAttr_t. May be NULL for an entirely heap-backed pool.

Return: Pool handle on success, NULL on failure (bad parameters, caller-supplied storage too small, allocation failure, or called from ISR).

Note: Must not be called from ISR context.

The internal block_size is rounded up to sizeof(void*) so the returned blocks are pointer-aligned. The pool maintains a free list plus a counting semaphore that drives the blocking behaviour of osMemoryPoolAlloc.

function osMemoryPoolAlloc

cpp
void * osMemoryPoolAlloc(
    osMemoryPoolId_t mp_id,
    uint32_t timeout
)

Allocates one block from the pool, optionally blocking.

Parameters:

  • mp_id Pool handle from osMemoryPoolNew.
  • timeout Maximum wait in milliseconds. Use 0 for a non-blocking try, osWaitForever to wait indefinitely.

Return: Pointer to a block_size-sized block, or NULL on timeout, invalid handle, or call from ISR context.

Note: Must not be called from ISR context.

If a free block is available it is returned immediately. If the pool is empty the calling thread sleeps on the pool's counting semaphore until a block is released by another thread, or until timeout ticks elapse. The returned memory is uninitialised — treat it like the result of malloc.

function osMemoryPoolFree

cpp
osStatus_t osMemoryPoolFree(
    osMemoryPoolId_t mp_id,
    void * block
)

Returns a previously allocated block to the pool.

Parameters:

Returns:

  • osOK Block successfully returned.
  • osErrorParameter mp_id or block is NULL, or block is not a valid block address for this pool.
  • osErrorISR Called from ISR context.
  • osErrorResource Internal mutex could not be acquired.

The pointer must have come from osMemoryPoolAlloc on the same pool. The implementation validates that block lies inside the pool's backing buffer and is aligned on a block boundary; it does not detect double-free of a block that is currently free — make sure each Alloc is paired with exactly one Free.

function osMemoryPoolGetCapacity

cpp
uint32_t osMemoryPoolGetCapacity(
    osMemoryPoolId_t mp_id
)

Returns the total number of blocks the pool was created with.

function osMemoryPoolGetBlockSize

cpp
uint32_t osMemoryPoolGetBlockSize(
    osMemoryPoolId_t mp_id
)

Returns the actual block size in bytes (after alignment).

This may be larger than the block_size passed to osMemoryPoolNew because of the internal sizeof(void*) rounding.

function osMemoryPoolGetCount

cpp
uint32_t osMemoryPoolGetCount(
    osMemoryPoolId_t mp_id
)

Returns the number of blocks currently checked out (in use).

Safe to call from ISR context; the value is a best-effort unlocked snapshot in that case.

function osMemoryPoolGetSpace

cpp
uint32_t osMemoryPoolGetSpace(
    osMemoryPoolId_t mp_id
)

Returns the number of blocks currently free.

Equivalent to GetCapacity() - GetCount(). Safe to call from ISR context; the value is a best-effort unlocked snapshot in that case.

function osMemoryPoolDelete

cpp
osStatus_t osMemoryPoolDelete(
    osMemoryPoolId_t mp_id
)

Destroys a pool and releases any storage it owns.

Parameters:

  • mp_id Pool handle.

Returns:

  • osOK Pool deleted.
  • osErrorParameter mp_id is NULL.
  • osErrorISR Called from ISR context.
  • osErrorResource One or more blocks are still in use.

Frees the control block and backing buffer only for fields that were originally allocated from the heap. Caller-supplied cb_mem / mp_mem are left untouched. All blocks must have been returned via osMemoryPoolFree before calling this.