SuperTinyKernel™ RTOS 1.06.x
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
14#include "sync/stk_sync_cv.h"
15
19
20namespace stk {
21namespace memory {
22
97{
98public:
101 static const size_t CAPACITY_MAX = 0xFFFEU;
102
119 explicit BlockMemoryPool(size_t capacity, size_t raw_block_size, uint8_t *storage,
120 size_t storage_size, const char *name = nullptr);
121
134#if STK_MEMORY_PLACEMENT_NEW
135 explicit BlockMemoryPool(size_t capacity, size_t raw_block_size, const char *name = nullptr);
136#endif
137
147
158 static constexpr size_t AlignBlockSize(size_t raw_size)
159 {
160 return Max(BLOCK_ALIGN, (raw_size + (BLOCK_ALIGN - 1U)) & ~(BLOCK_ALIGN - 1U));
161 }
162
175 void *TimedAlloc(Timeout timeout_ticks = WAIT_INFINITE);
176
184 template <typename T> T *TimedAllocT(Timeout timeout_ticks = WAIT_INFINITE);
185
190 void *Alloc();
191
196 template <typename T> T *AllocT();
197
204 void *TryAlloc();
205
212 template <typename T> T *TryAllocT();
213
229 bool Free(void *ptr);
230
239 bool IsStorageValid() const { return (m_storage != nullptr); }
240
246 size_t GetCapacity() const { return m_capacity; }
247
254 size_t GetBlockSize() const { return m_block_size; }
255
261 size_t GetUsedCount() const { return m_used_count; }
262
268 size_t GetFreeCount() const { return (m_capacity - m_used_count); }
269
274 bool IsFull() const { return (GetFreeCount() == 0U); }
275
280 bool IsEmpty() const { return (m_used_count == 0U); }
281
282private:
284
291 {
293 };
294
299 static const uint32_t BLOCK_ALIGN = static_cast<uint32_t>(sizeof(MemoryBlock));
300
307 void BuildFreeList();
308
312 void *PopFreeList();
313
314 uint8_t *m_storage;
318 size_t m_capacity;
321};
322
323// ---------------------------------------------------------------------------
324// Constructors / Destructor
325// ---------------------------------------------------------------------------
326
327inline BlockMemoryPool::BlockMemoryPool(size_t capacity, size_t raw_block_size, uint8_t *storage,
328 size_t storage_size, const char *name)
329: m_storage(storage),
330 m_free_list(nullptr),
331 m_block_size(AlignBlockSize(raw_block_size)),
332 m_capacity(capacity),
333 m_used_count(0U),
334 m_storage_owned(false)
335{
336 STK_ASSERT(capacity > 0U);
337 STK_ASSERT(capacity <= CAPACITY_MAX);
338 STK_ASSERT(raw_block_size > 0U);
339 STK_ASSERT(storage != nullptr);
340
341 // API contract: caller-supplied buffer must be large enough
342 STK_ASSERT(storage_size >= (capacity * m_block_size));
343
344 // in Release builds we ensure capacity which fits storage size, in Debug build the assertion above will be hit
345 if ((capacity * m_block_size) > storage_size)
346 {
347 m_capacity = storage_size / m_block_size;
348 }
349
350#if STK_SYNC_DEBUG_NAMES
351 SetTraceName(name);
352#else
353 STK_UNUSED(name);
354#endif
355
357}
358
359#if STK_MEMORY_PLACEMENT_NEW
360inline BlockMemoryPool::BlockMemoryPool(size_t capacity, size_t raw_block_size, const char *name)
361: m_storage(MemoryAllocator::AllocateArrayT<uint8_t>(capacity * AlignBlockSize(raw_block_size))),
362 m_free_list(nullptr),
363 m_block_size(AlignBlockSize(raw_block_size)),
364 m_capacity(capacity),
365 m_used_count(0U),
366 m_storage_owned(true)
367{
368 STK_ASSERT(capacity > 0U);
369 STK_ASSERT(capacity <= CAPACITY_MAX);
370 STK_ASSERT(raw_block_size > 0U);
371
372#if STK_SYNC_DEBUG_NAMES
373 SetTraceName(name);
374#else
375 STK_UNUSED(name);
376#endif
377
378 if (m_storage != nullptr)
379 {
381 }
382 // else: m_free_list remains nullptr; caller must check IsStorageValid()
383}
384#endif
385
387{
388 // ConditionVariable destructor asserts the wait list is empty
389#if STK_MEMORY_PLACEMENT_NEW
390 if (m_storage_owned)
391 {
393 m_storage = nullptr;
394 }
395#endif
396}
397
398// ---------------------------------------------------------------------------
399// Alloc / TimedAlloc / TryAlloc
400// ---------------------------------------------------------------------------
401
402inline void *BlockMemoryPool::TimedAlloc(Timeout timeout_ticks)
403{
404 void *block = nullptr;
405
406 if (!hw::IsInsideISR() || (timeout_ticks == NO_WAIT))
407 {
409 bool is_timeout = false;
410
411 while (m_free_list == nullptr)
412 {
413 // Atomically release the critical section, suspend the task, and
414 // re-acquire before returning - no CPU cycles wasted while waiting.
415 if (!m_cv.Wait(cs_, timeout_ticks))
416 {
417 is_timeout = true; // timeout expired
418 break;
419 }
420 }
421
422 // only allocate a block if we didn't time out
423 if (!is_timeout)
424 {
425 block = PopFreeList();
426 }
427 }
428 else
429 {
430 STK_ASSERT(false); // API contract: ISR callers must pass NO_WAIT / use TryAlloc()
431 }
432
433 return block;
434}
435
436template <typename T>
437inline T *BlockMemoryPool::TimedAllocT(Timeout timeout_ticks)
438{
439 STK_ASSERT(sizeof(T) <= m_block_size); // API contract: block size should larger or equal to object's size
440
441 return static_cast<T *>(TimedAlloc(timeout_ticks));
442}
443
445{
447}
448
449template <typename T>
451{
453}
454
456{
457 return TimedAlloc(NO_WAIT);
458}
459
460template <typename T>
462{
463 return TimedAllocT<T>(NO_WAIT);
464}
465
466// ---------------------------------------------------------------------------
467// Free
468// ---------------------------------------------------------------------------
469
470inline bool BlockMemoryPool::Free(void *ptr)
471{
472 bool success = false;
473
474 if (ptr != nullptr)
475 {
476 // bounds check: ptr must be in range [m_storage, m_storage + capacity * block_size)
477 const Word pt = hw::PtrToWord(ptr);
478 const Word lo = hw::PtrToWord(m_storage);
479 const Word hi = lo + (m_capacity * m_block_size);
480
481 if ((pt < lo) || (pt >= hi))
482 {
483 STK_ASSERT(false); // API contract: ptr does not belong to this pool
484 }
485 // alignment check: ptr must be at the start of a block boundary
486 else if ((static_cast<size_t>(pt - lo) % m_block_size) != 0U)
487 {
488 STK_ASSERT(false); // API contract: ptr is misaligned (not a block start)
489 }
490 else
491 {
493
494 if (m_used_count == 0U)
495 {
496 STK_ASSERT(false); // pool is already fully free - definite double-free
497 }
498 else
499 {
500 #if defined(_DEBUG) || defined(DEBUG)
501 bool is_double_free = false;
502
503 // O(n) double-free detection: walk the free-list and check if ptr is already on it,
504 // only active in debug builds - compiles away completely in release
505 for (const MemoryBlock *node = m_free_list; (node != nullptr); node = node->next)
506 {
507 if (node == reinterpret_cast<const MemoryBlock *>(ptr))
508 {
509 STK_ASSERT(false); // double-free: ptr is already on the free-list
510 is_double_free = true;
511 break;
512 }
513 }
514
515 if (!is_double_free)
516 #endif
517 {
518 // push block onto free-list head (O(1))
519 auto *const blk = reinterpret_cast<MemoryBlock *>(ptr);
520 blk->next = m_free_list;
521 m_free_list = blk;
522 m_used_count = static_cast<uint16_t>(m_used_count - 1U);
523
524 // wake one blocked allocator - true scheduler wait, no spin-yield
525 m_cv.NotifyOne_CS();
526
527 success = true;
528 }
529 }
530 }
531 }
532
533 return success;
534}
535
536// ---------------------------------------------------------------------------
537// Private helpers
538// ---------------------------------------------------------------------------
539
541{
543
544 m_free_list = nullptr;
545 m_used_count = 0U;
546
547 // Link blocks in reverse order so m_free_list ends up pointing at block_0
548 // (lowest address), giving ascending allocation order.
549 for (size_t i = m_capacity; i-- > 0U; )
550 {
552
553 blk->next = m_free_list;
554 m_free_list = blk;
555 }
556}
557
559{
561
562 MemoryBlock *const blk = m_free_list;
563
564 m_free_list = blk->next;
565 m_used_count = static_cast<uint16_t>(m_used_count + 1U);
566
567 return blk;
568}
569
570} // namespace memory
571} // namespace stk
572
573#endif /* STK_MEMORY_BLOCKPOOL_H_ */
Weak declaration of memory allocator: stk::memory::MemoryAllocator.
#define STK_UNUSED(X)
Explicitly marks a variable as unused to suppress compiler warnings.
Definition stk_defs.h:608
#define STK_ASSERT(e)
Runtime assertion. Halts execution if the expression e evaluates to false.
Definition stk_defs.h:409
#define STK_VIRT_DTOR
Makes destructors virtual and compliant to strict rules if STK_STRICT_COMPLIANCY=0.
Definition stk_defs.h:159
Implementation of synchronization primitive: stk::sync::ConditionVariable.
Namespace of STK package.
uintptr_t Word
Native processor word type.
Definition stk_common.h:136
constexpr Timeout NO_WAIT
Timeout value: return immediately if the synchronization object is not yet signaled (non-blocking pol...
Definition stk_common.h:210
int32_t Timeout
Timeout time (ticks).
Definition stk_common.h:146
static constexpr T Max(T a, T b)
Compile-time maximum of two values.
Definition stk_defs.h:645
constexpr Timeout WAIT_INFINITE
Timeout value: block indefinitely until the synchronization object is signaled.
Definition stk_common.h:204
Memory-related primitives.
static constexpr T * WordToPtr(Word value) noexcept
Cast a CPU register-width integer back to a pointer.
Definition stk_arch.h:123
static constexpr Word PtrToWord(T *const ptr) noexcept
Cast a pointer to a CPU register-width integer.
Definition stk_arch.h:106
bool IsInsideISR()
Check whether the CPU is currently executing inside a hardware interrupt service routine (ISR).
Memory allocator for allocating dynamic memory.
static void FreeArrayT(TElement ptr[], size_t count)
Destroy and free an array allocated via AllocateT().
size_t GetUsedCount() const
Get the number of currently allocated (outstanding) blocks.
static const uint32_t BLOCK_ALIGN
Minimum block alignment in bytes: sizeof(MemoryBlock).
size_t GetFreeCount() const
Get the number of free (available) blocks.
uint8_t * m_storage
flat byte array holding all N blocks (owned or external)
void * PopFreeList()
Pop the head block from the free-list, increment m_used_count, and return it.
static const size_t CAPACITY_MAX
Max capacity supported (number of blocks).
size_t m_capacity
total number of blocks
size_t GetCapacity() const
Get the total block capacity of the pool.
STK_NONCOPYABLE_CLASS(BlockMemoryPool)
T * TryAllocT()
Non-blocking typed allocation attempt.
bool IsFull() const
Check whether all blocks are currently allocated (pool exhausted).
bool IsStorageValid() const
Verify that the backing storage is valid and the pool is ready for use.
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.
bool Free(void *ptr)
Return a previously allocated block to the pool.
STK_VIRT_DTOR ~BlockMemoryPool()
Destructor.
void BuildFreeList()
Initialise the intrusive free-list across m_storage.
void * TryAlloc()
Non-blocking allocation attempt.
static constexpr size_t AlignBlockSize(size_t raw_size)
Round a raw block size up to the nearest multiple of BLOCK_ALIGN.
MemoryBlock * m_free_list
head of the intrusive free-list (nullptr when pool is empty)
void * TimedAlloc(Timeout timeout_ticks=WAIT_INFINITE)
Allocate one block, blocking until one becomes available or the timeout expires.
sync::ConditionVariable m_cv
signalled by Free() to wake one task blocked in TimedAlloc()
size_t GetBlockSize() const
Get the aligned block size used internally by the allocator.
void * Alloc()
Allocate one block, blocking indefinitely until one is available.
T * TimedAllocT(Timeout timeout_ticks=WAIT_INFINITE)
Allocate one typed block, blocking until one becomes available or the timeout expires.
bool IsEmpty() const
Check whether all blocks are free (no outstanding allocations).
size_t m_used_count
number of blocks currently allocated (outstanding)
T * AllocT()
Allocate one typed block, blocking indefinitely until one is available.
size_t m_block_size
aligned block size in bytes (>= BLOCK_ALIGN)
bool m_storage_owned
true -> storage is heap-allocated; free in destructor
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
Traceable object.
Definition stk_common.h:410
void SetTraceName(const char *name)
Set name.
Definition stk_common.h:421
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