SuperTinyKernel™ RTOS 1.05.3
Lightweight, high-performance, deterministic, bare-metal C++ RTOS for resource-constrained embedded systems. MIT Open Source License.
Loading...
Searching...
No Matches
stk_memory_blockpool.h
Go to the documentation of this file.
1/*
2 * SuperTinyKernel(TM) RTOS: Lightweight High-Performance Deterministic C++ RTOS for Embedded Systems.
3 *
4 * Source: https://github.com/SuperTinyKernel-RTOS
5 *
6 * Copyright (c) 2022-2026 Neutron Code Limited <stk@neutroncode.com>. All Rights Reserved.
7 * License: MIT License, see LICENSE for a full text.
8 */
9
10#ifndef STK_MEMORY_BLOCKPOOL_H_
11#define STK_MEMORY_BLOCKPOOL_H_
12
13#include <stdint.h>
14#include <stddef.h>
15#include <new>
16
17#include "stk_common.h"
18#include "stk_helper.h"
19#include "sync/stk_sync_cv.h"
20
24
25namespace stk {
26namespace memory {
27
99class BlockMemoryPool : public ITraceable
100{
101public:
104 static const size_t CAPACITY_MAX = 0xFFFEU;
105
122 explicit BlockMemoryPool(size_t capacity, size_t raw_block_size, uint8_t *storage,
123 size_t storage_size, const char *name = nullptr);
124
137 explicit BlockMemoryPool(size_t capacity, size_t raw_block_size, const char *name = nullptr);
138
148
159 static constexpr size_t AlignBlockSize(size_t raw_size)
160 {
161 return Max(BLOCK_ALIGN, (raw_size + (BLOCK_ALIGN - 1U)) & ~(BLOCK_ALIGN - 1U));
162 }
163
177
185 template <typename T> T *TimedAllocT(Timeout timeout = WAIT_INFINITE);
186
191 void *Alloc();
192
197 template <typename T> T *AllocT();
198
205 void *TryAlloc();
206
213 template <typename T> T *TryAllocT();
214
230 bool Free(void *ptr);
231
240 bool IsStorageValid() const { return (m_storage != nullptr); }
241
247 size_t GetCapacity() const { return m_capacity; }
248
255 size_t GetBlockSize() const { return m_block_size; }
256
262 size_t GetUsedCount() const { return m_used_count; }
263
269 size_t GetFreeCount() const { return (m_capacity - m_used_count); }
270
275 bool IsFull() const { return (GetFreeCount() == 0U); }
276
281 bool IsEmpty() const { return (m_used_count == 0U); }
282
283private:
285
292 {
294 };
295
300 static const uint32_t BLOCK_ALIGN = static_cast<uint32_t>(sizeof(MemoryBlock));
301
309
313 void *PopFreeList();
314
315 uint8_t *m_storage;
319 size_t m_capacity;
320 uint16_t m_used_count;
322};
323
324// ---------------------------------------------------------------------------
325// Constructors / Destructor
326// ---------------------------------------------------------------------------
327
328inline BlockMemoryPool::BlockMemoryPool(size_t capacity, size_t raw_block_size, uint8_t *storage,
329 size_t storage_size, const char *name)
330 : m_storage(storage),
331 m_free_list(nullptr),
332 m_block_size(AlignBlockSize(raw_block_size)),
333 m_capacity(capacity),
334 m_used_count(0U),
335 m_storage_owned(false)
336{
337 STK_ASSERT(capacity > 0U);
338 STK_ASSERT(capacity <= CAPACITY_MAX);
339 STK_ASSERT(raw_block_size > 0U);
340 STK_ASSERT(storage != nullptr);
341 // API contract: caller-supplied buffer must be large enough
342 STK_ASSERT(storage_size >= (capacity * m_block_size));
343
344#if STK_SYNC_DEBUG_NAMES
345 SetTraceName(name);
346#else
347 STK_UNUSED(name);
348#endif
349
351}
352
353inline BlockMemoryPool::BlockMemoryPool(size_t capacity, size_t raw_block_size, const char *name)
354 : m_storage(new (std::nothrow) uint8_t[capacity * AlignBlockSize(raw_block_size)]),
355 m_free_list(nullptr),
356 m_block_size(AlignBlockSize(raw_block_size)),
357 m_capacity(capacity),
358 m_used_count(0U),
359 m_storage_owned(true)
360{
361 STK_ASSERT(capacity > 0U);
362 STK_ASSERT(capacity <= CAPACITY_MAX);
363 STK_ASSERT(raw_block_size > 0U);
364
365#if STK_SYNC_DEBUG_NAMES
366 SetTraceName(name);
367#else
368 STK_UNUSED(name);
369#endif
370
371 if (m_storage != nullptr)
373 // else: m_free_list remains nullptr; caller must check IsStorageValid()
374}
375
376inline BlockMemoryPool::~BlockMemoryPool()
377{
378 // ConditionVariable destructor asserts the wait list is empty
379 if (m_storage_owned)
380 {
381 delete[] m_storage;
382 m_storage = nullptr;
383 }
384}
385
386// ---------------------------------------------------------------------------
387// Alloc / TimedAlloc / TryAlloc
388// ---------------------------------------------------------------------------
389
390inline void *BlockMemoryPool::TimedAlloc(Timeout timeout)
391{
392 if (hw::IsInsideISR() && (timeout != NO_WAIT))
393 {
394 STK_ASSERT(false); // API contract: ISR callers must pass NO_WAIT / use TryAlloc()
395 return nullptr;
396 }
397
399
400 while (m_free_list == nullptr)
401 {
402 // Atomically release the critical section, suspend the task, and
403 // re-acquire before returning - no CPU cycles wasted while waiting.
404 if (!m_cv.Wait(cs_, timeout))
405 return nullptr; // timeout expired
406 }
407
408 return PopFreeList();
409}
410
411template <typename T>
412inline T *BlockMemoryPool::TimedAllocT(Timeout timeout)
413{
414 STK_ASSERT(sizeof(T) <= m_block_size); // API contract: block size should larger or equal to object's size
415
416 return static_cast<T *>(TimedAlloc(timeout));
417}
418
419inline void *BlockMemoryPool::Alloc()
420{
422}
423
424template <typename T>
425inline T *BlockMemoryPool::AllocT()
426{
427 return TimedAllocT<T>(WAIT_INFINITE);
428}
429
430inline void *BlockMemoryPool::TryAlloc()
431{
432 return TimedAlloc(NO_WAIT);
433}
434
435template <typename T>
436inline T *BlockMemoryPool::TryAllocT()
437{
438 return TimedAllocT<T>(NO_WAIT);
439}
440
441// ---------------------------------------------------------------------------
442// Free
443// ---------------------------------------------------------------------------
444
445inline bool BlockMemoryPool::Free(void *ptr)
446{
447 if (ptr == nullptr)
448 return false;
449
450 // bounds check: ptr must be in range [m_storage, m_storage + capacity * block_size)
451 const uint8_t *p8 = static_cast<uint8_t *>(ptr);
452 const uint8_t *lo = m_storage;
453 const uint8_t *hi = m_storage + (m_capacity * m_block_size);
454
455 if ((p8 < lo) || (p8 >= hi))
456 {
457 STK_ASSERT(false); // API contract: ptr does not belong to this pool
458 return false;
459 }
460
461 // alignment check: ptr must be at the start of a block boundary
462 if ((static_cast<size_t>(p8 - lo) % m_block_size) != 0U)
463 {
464 STK_ASSERT(false); // API contract: ptr is misaligned (not a block start)
465 return false;
466 }
467
469
470 if (m_used_count == 0U)
471 {
472 STK_ASSERT(false); // pool is already fully free - definite double-free
473 return false;
474 }
475
476#if defined(_DEBUG) || defined(DEBUG)
477 // O(n) double-free detection: walk the free-list and check if ptr is already on it,
478 // only active in debug builds - compiles away completely in release
479 for (const MemoryBlock *node = m_free_list; (node != nullptr); node = node->next)
480 {
481 if (node == reinterpret_cast<const MemoryBlock *>(ptr))
482 {
483 STK_ASSERT(false); // double-free: ptr is already on the free-list
484 return false;
485 }
486 }
487#endif
488
489 // push block onto free-list head (O(1))
490 auto *blk = reinterpret_cast<MemoryBlock *>(ptr);
491 blk->next = m_free_list;
492 m_free_list = blk;
493 m_used_count = static_cast<uint16_t>(m_used_count - 1U);
494
495 // wake one blocked allocator - true scheduler wait, no spin-yield
497
498 return true;
499}
500
501// ---------------------------------------------------------------------------
502// Private helpers
503// ---------------------------------------------------------------------------
504
505inline void BlockMemoryPool::BuildFreeList()
506{
507 STK_ASSERT((hw::PtrToWord(m_storage) % BLOCK_ALIGN) == 0U);
508
509 m_free_list = nullptr;
510 m_used_count = 0U;
511
512 // Link blocks in reverse order so m_free_list ends up pointing at block_0
513 // (lowest address), giving ascending allocation order.
514 for (size_t i = m_capacity; i-- > 0U; )
515 {
516 MemoryBlock *blk = reinterpret_cast<MemoryBlock *>(m_storage + (i * m_block_size));
517
518 blk->next = m_free_list;
519 m_free_list = blk;
520 }
521}
522
523inline void *BlockMemoryPool::PopFreeList()
524{
525 STK_ASSERT(m_used_count < m_capacity);
526
527 MemoryBlock *blk = m_free_list;
528
529 m_free_list = blk->next;
530 m_used_count = static_cast<uint16_t>(m_used_count + 1U);
531
532 return blk;
533}
534
535} // namespace memory
536} // namespace stk
537
538#endif /* STK_MEMORY_BLOCKPOOL_H_ */
Contains interface definitions of the library.
#define STK_UNUSED(X)
Explicitly marks a variable as unused to suppress compiler warnings.
Definition stk_defs.h:524
#define STK_NONCOPYABLE_CLASS(TYPE)
Disables copy construction and assignment for a class.
Definition stk_defs.h:517
#define STK_ASSERT(e)
Runtime assertion. Halts execution if the expression e evaluates to false.
Definition stk_defs.h:330
Contains helper implementations which simplify user-side code.
Implementation of synchronization primitive: stk::sync::ConditionVariable.
Namespace of STK package.
static constexpr Timeout NO_WAIT
Timeout value: return immediately if the synchronization object is not yet signaled (non-blocking pol...
Definition stk_common.h:177
int32_t Timeout
Timeout time (ticks).
Definition stk_common.h:123
static constexpr T Max(T a, T b)
Compile-time maximum of two values.
Definition stk_defs.h:541
static constexpr Timeout WAIT_INFINITE
Timeout value: block indefinitely until the synchronization object is signaled.
Definition stk_common.h:171
Memory-related primitives.
bool m_storage_owned
true -> storage is heap-allocated; free in destructor
uint8_t * m_storage
flat byte array holding all N blocks (owned or external)
void * Alloc()
Allocate one block, blocking indefinitely until one is available.
sync::ConditionVariable m_cv
signalled by Free() to wake one task blocked in TimedAlloc()
static constexpr size_t AlignBlockSize(size_t raw_size)
Round a raw block size up to the nearest multiple of BLOCK_ALIGN.
uint16_t m_used_count
number of blocks currently allocated (outstanding)
T * AllocT()
Allocate one typed block, blocking indefinitely until one is available.
void * PopFreeList()
Pop the head block from the free-list, increment m_used_count, and return it.
T * TimedAllocT(Timeout timeout=WAIT_INFINITE)
Allocate one typed block, blocking until one becomes available or the timeout expires.
~BlockMemoryPool()
Destructor.
size_t m_capacity
total number of blocks
MemoryBlock * m_free_list
head of the intrusive free-list (nullptr when pool is empty)
size_t GetFreeCount() const
Get the number of free (available) blocks.
void BuildFreeList()
Initialise the intrusive free-list across m_storage.
BlockMemoryPool(size_t capacity, size_t raw_block_size, uint8_t *storage, size_t storage_size, const char *name=nullptr)
Construct a pool backed by caller-supplied (external) storage.
static const size_t CAPACITY_MAX
bool Free(void *ptr)
Return a previously allocated block to the pool.
T * TryAllocT()
Non-blocking typed allocation attempt.
void * TimedAlloc(Timeout timeout=WAIT_INFINITE)
Allocate one block, blocking until one becomes available or the timeout expires.
size_t GetBlockSize() const
Get the aligned block size used internally by the allocator.
bool IsEmpty() const
Check whether all blocks are free (no outstanding allocations).
size_t GetCapacity() const
Get the total block capacity of the pool.
static const uint32_t BLOCK_ALIGN
Minimum block alignment in bytes: sizeof(MemoryBlock).
size_t GetUsedCount() const
Get the number of currently allocated (outstanding) blocks.
bool IsFull() const
Check whether all blocks are currently allocated (pool exhausted).
size_t m_block_size
aligned block size in bytes (>= BLOCK_ALIGN)
bool IsStorageValid() const
Verify that the backing storage is valid and the pool is ready for use.
void * TryAlloc()
Non-blocking allocation attempt.
__stk_forceinline Word PtrToWord(T *ptr) noexcept
Cast a pointer to a CPU register-width integer.
Definition stk_arch.h:94
bool IsInsideISR()
Check whether the CPU is currently executing inside a hardware interrupt service routine (ISR).
Definition stktest.cpp:103
Intrusive free-list node overlaid on the first word of every free block.
MemoryBlock * next
next free block in the list, or nullptr if this is the last one
RAII-style low-level synchronization primitive for atomic code execution. Used as building brick for ...
Definition stk_sync_cs.h:54
Condition Variable primitive for signaling between tasks based on specific predicates.
Definition stk_sync_cv.h:68
void NotifyOne_CS()
Wake one waiting task.
bool Wait(IMutex &mutex, Timeout timeout=WAIT_INFINITE)
Wait for a signal.
Fixed-size block allocator with O(1) alloc/free and proper task-blocking semantics.