SuperTinyKernel™ RTOS 1.08.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
177 void *TimedAlloc(Timeout timeout_ticks = WAIT_INFINITE);
178
194 EWaitResult TimedAllocEx(void **block, Timeout timeout_ticks = WAIT_INFINITE);
195
203 template <typename T> T *TimedAllocT(Timeout timeout_ticks = WAIT_INFINITE);
204
211 void *Alloc();
212
218 template <typename T> T *AllocT();
219
226 void *TryAlloc();
227
234 template <typename T> T *TryAllocT();
235
254 bool Free(void *ptr);
255
264 bool IsStorageValid() const { return (m_storage != nullptr); }
265
271 size_t GetCapacity() const { return m_capacity; }
272
279 size_t GetBlockSize() const { return m_block_size; }
280
286 size_t GetUsedCount() const { return m_used_count; }
287
293 size_t GetFreeCount() const { return (m_capacity - m_used_count); }
294
299 bool IsFull() const { return (GetFreeCount() == 0U); }
300
305 bool IsEmpty() const { return (m_used_count == 0U); }
306
307private:
309
316 {
318 };
319
324 static const size_t BLOCK_ALIGN = static_cast<size_t>(sizeof(MemoryBlock));
325
332 void BuildFreeList();
333
337 void *PopFreeList();
338
339 uint8_t *m_storage;
343 size_t m_capacity;
346};
347
348// ---------------------------------------------------------------------------
349// Constructors / Destructor
350// ---------------------------------------------------------------------------
351
352inline BlockMemoryPool::BlockMemoryPool(size_t capacity, size_t raw_block_size, uint8_t *storage,
353 size_t storage_size, const char *name)
354: m_storage(storage),
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(false)
360{
361 STK_ASSERT(capacity > 0U);
362 STK_ASSERT(capacity <= CAPACITY_MAX);
363 STK_ASSERT(raw_block_size > 0U);
364 STK_ASSERT(storage != nullptr);
365
366 // API contract: caller-supplied buffer must be large enough
367 STK_ASSERT(storage_size >= (capacity * m_block_size));
368
369 // in Release builds we ensure capacity which fits storage size, in Debug build the assertion above will be hit
370 if ((capacity * m_block_size) > storage_size)
371 {
372 m_capacity = storage_size / m_block_size;
373 }
374
375#if STK_SYNC_DEBUG_NAMES
376 SetTraceName(name);
377#else
378 STK_UNUSED(name);
379#endif
380
382}
383
384#if STK_MEMORY_PLACEMENT_NEW
385inline BlockMemoryPool::BlockMemoryPool(size_t capacity, size_t raw_block_size, const char *name)
386: m_storage(MemoryAllocator::AllocateArrayT<uint8_t>(capacity * AlignBlockSize(raw_block_size))),
387 m_free_list(nullptr),
388 m_block_size(AlignBlockSize(raw_block_size)),
389 m_capacity(capacity),
390 m_used_count(0U),
391 m_storage_owned(true)
392{
393 STK_ASSERT(capacity > 0U);
394 STK_ASSERT(capacity <= CAPACITY_MAX);
395 STK_ASSERT(raw_block_size > 0U);
396
397#if STK_SYNC_DEBUG_NAMES
398 SetTraceName(name);
399#else
400 STK_UNUSED(name);
401#endif
402
403 if (m_storage != nullptr)
404 {
406 }
407 // else: m_free_list remains nullptr; caller must check IsStorageValid()
408}
409#endif
410
412{
413 // ConditionVariable destructor asserts the wait list is empty
414#if STK_MEMORY_PLACEMENT_NEW
415 if (m_storage_owned)
416 {
418 m_storage = nullptr;
419 }
420#endif
421}
422
423// ---------------------------------------------------------------------------
424// Alloc / TimedAlloc / TryAlloc
425// ---------------------------------------------------------------------------
426
427inline EWaitResult BlockMemoryPool::TimedAllocEx(void **block, Timeout timeout_ticks)
428{
430
431 STK_ASSERT(block != nullptr); // API contract: output pointer must be valid
432
433 if (block == nullptr)
434 {
435 // result is WAIT_RESULT_FAIL already, so noop
436 }
437 else if (hw::IsInsideISR() && (timeout_ticks != NO_WAIT))
438 {
439 STK_ASSERT(false); // API contract: ISR callers must pass NO_WAIT / use TryAlloc()
440 *block = nullptr;
441 }
442 else
443 {
445
446 const bool timed_wait = (timeout_ticks != WAIT_INFINITE) && (timeout_ticks != NO_WAIT);
447
448 // capture an absolute deadline once, before entering the wait loop; a different
449 // allocator (TryAlloc() from another task or an ISR) can steal the just-freed
450 // block between NotifyOne_CS() and this task re-acquiring the critical section
451 // (Mesa-style wakeup, not a direct handoff - see Free()'s note), so re-passing
452 // the original timeout_ticks on each retry would silently restart it;
453 // kept as 64-bit Ticks (not truncated to Timeout) so it can't wrap on long uptimes
454 const Ticks deadline = (timed_wait ? (GetTicks() + static_cast<Ticks>(timeout_ticks)) : 0);
455
456 *block = nullptr;
457 result = WAIT_RESULT_SIGNAL; // fast path: a block is already available
458
459 while (m_free_list == nullptr)
460 {
461 Timeout remaining = timeout_ticks;
462 if (timed_wait)
463 {
464 const Ticks left = (deadline - GetTicks());
465 remaining = ((left > 0) ? static_cast<Timeout>(left) : NO_WAIT);
466 }
467
468 // Atomically release the critical section, suspend the task, and
469 // re-acquire before returning - no CPU cycles wasted while waiting.
470 result = m_cv.WaitEx(cs_, remaining);
471 if (result != WAIT_RESULT_SIGNAL)
472 {
473 break; // timeout, cancellation or kernel failure: report as is
474 }
475 }
476
477 // timeout raced with a Free(): the lock was re-acquired and a block is available,
478 // so honor it instead of reporting a failure (cancellation is never overridden)
479 if ((result == WAIT_RESULT_TIMEOUT) && (m_free_list != nullptr))
480 {
481 result = WAIT_RESULT_SIGNAL;
482 }
483
484 // only allocate a block if the wait ended in a signal (or none was needed)
485 if (result == WAIT_RESULT_SIGNAL)
486 {
487 *block = PopFreeList();
488 }
489 }
490
491 return result;
492}
493
494inline void *BlockMemoryPool::TimedAlloc(Timeout timeout_ticks)
495{
496 void *block = nullptr;
497
498 if (TimedAllocEx(&block, timeout_ticks) != WAIT_RESULT_SIGNAL)
499 {
500 block = nullptr;
501 }
502
503 return block;
504}
505
506template <typename T>
507inline T *BlockMemoryPool::TimedAllocT(Timeout timeout_ticks)
508{
509 STK_ASSERT(sizeof(T) <= m_block_size); // API contract: block size should larger or equal to object's size
510
511 return static_cast<T *>(TimedAlloc(timeout_ticks));
512}
513
515{
517}
518
519template <typename T>
521{
523}
524
526{
527 return TimedAlloc(NO_WAIT);
528}
529
530template <typename T>
532{
533 return TimedAllocT<T>(NO_WAIT);
534}
535
536// ---------------------------------------------------------------------------
537// Free
538// ---------------------------------------------------------------------------
539
540inline bool BlockMemoryPool::Free(void *ptr)
541{
542 bool success = false;
543
544 if (ptr != nullptr)
545 {
546 // bounds check: ptr must be in range [m_storage, m_storage + capacity * block_size)
547 const Word pt = hw::PtrToWord(ptr);
548 const Word lo = hw::PtrToWord(m_storage);
549 const Word hi = lo + (m_capacity * m_block_size);
550
551 if ((pt < lo) || (pt >= hi))
552 {
553 STK_ASSERT(false); // API contract: ptr does not belong to this pool
554 }
555 // alignment check: ptr must be at the start of a block boundary
556 else if ((static_cast<size_t>(pt - lo) % m_block_size) != 0U)
557 {
558 STK_ASSERT(false); // API contract: ptr is misaligned (not a block start)
559 }
560 else
561 {
563
564 if (m_used_count == 0U)
565 {
566 STK_ASSERT(false); // pool is already fully free - definite double-free
567 }
568 else
569 {
570 #if defined(_DEBUG) || defined(DEBUG)
571 bool is_double_free = false;
572
573 // O(n) double-free detection: walk the free-list and check if ptr is already on it,
574 // only active in debug builds - compiles away completely in release
575 for (const MemoryBlock *node = m_free_list; (node != nullptr); node = node->next)
576 {
577 if (node == reinterpret_cast<const MemoryBlock *>(ptr))
578 {
579 STK_ASSERT(false); // double-free: ptr is already on the free-list
580 is_double_free = true;
581 break;
582 }
583 }
584
585 if (!is_double_free)
586 #endif
587 {
588 // push block onto free-list head (O(1))
589 auto *const blk = reinterpret_cast<MemoryBlock *>(ptr);
590 blk->next = m_free_list;
591 m_free_list = blk;
592 m_used_count = (m_used_count - 1U);
593
594 // wake one blocked allocator - true scheduler wait, no spin-yield
595 m_cv.NotifyOne_CS();
596
597 success = true;
598 }
599 }
600 }
601 }
602
603 return success;
604}
605
606// ---------------------------------------------------------------------------
607// Private helpers
608// ---------------------------------------------------------------------------
609
611{
613
614 m_free_list = nullptr;
615 m_used_count = 0U;
616
617 // Link blocks in reverse order so m_free_list ends up pointing at block_0
618 // (lowest address), giving ascending allocation order.
619 for (size_t i = m_capacity; i-- > 0U; )
620 {
622
623 blk->next = m_free_list;
624 m_free_list = blk;
625 }
626}
627
629{
631
632 MemoryBlock *const blk = m_free_list;
633
634 m_free_list = blk->next;
635 m_used_count = (m_used_count + 1U);
636
637 return blk;
638}
639
640} // namespace memory
641} // namespace stk
642
643#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:715
#define STK_ASSERT(e)
Runtime assertion. Halts execution if the expression e evaluates to false.
Definition stk_defs.h:516
#define STK_VIRT_DTOR
Makes destructors virtual and compliant to strict rules if STK_STRICT_COMPLIANCY=0.
Definition stk_defs.h:261
Implementation of synchronization primitive: stk::sync::ConditionVariable.
Namespace of STK package.
uintptr_t Word
Native processor word type.
Definition stk_common.h:160
static constexpr Timeout NO_WAIT
Timeout value: return immediately if the synchronization object is not yet signaled (non-blocking pol...
Definition stk_common.h:234
EWaitResult
Wait result (see IKernelService::Wait).
Definition stk_common.h:137
@ WAIT_RESULT_FAIL
IKernelService::Wait returned with error without waiting.
Definition stk_common.h:138
@ WAIT_RESULT_TIMEOUT
The wake was caused by a timeout expiry.
Definition stk_common.h:140
@ WAIT_RESULT_SIGNAL
The wake was caused by a signal.
Definition stk_common.h:139
int64_t Ticks
Ticks value.
Definition stk_common.h:175
int32_t Timeout
Timeout time (ticks).
Definition stk_common.h:170
static constexpr T Max(T a, T b) noexcept
Compile-time maximum of two values.
Definition stk_defs.h:759
static Ticks GetTicks()
Get number of ticks elapsed since kernel start.
Definition stk_helper.h:452
static constexpr Timeout WAIT_INFINITE
Timeout value: block indefinitely until the synchronization object is signaled.
Definition stk_common.h:228
static constexpr T * WordToPtr(Word value) noexcept
Cast a CPU register-width integer back to a pointer.
Definition stk_arch.h:231
static constexpr Word PtrToWord(T *const ptr) noexcept
Cast a pointer to a CPU register-width integer.
Definition stk_arch.h:214
bool IsInsideISR()
Check whether the CPU is currently executing inside a hardware interrupt service routine (ISR).
Memory-related primitives.
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 size_t BLOCK_ALIGN
Minimum block alignment in bytes: sizeof(MemoryBlock).
EWaitResult TimedAllocEx(void **block, Timeout timeout_ticks=WAIT_INFINITE)
Allocate one block, preserving the full wait outcome.
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:559
void SetTraceName(const char *name)
Set name.
Definition stk_common.h:570
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