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_bytepool.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_BYTEPOOL_H_
11#define STK_MEMORY_BYTEPOOL_H_
12
14#include "sync/stk_sync_cv.h"
15
19
20namespace stk {
21namespace memory {
22
105class BytePool : public ITraceable
106{
107public:
112 static const size_t HEADER_SIZE = 8U;
113
128 explicit BytePool(uint8_t *storage, size_t storage_size, const char *name = nullptr);
129
139#if STK_MEMORY_PLACEMENT_NEW
140 explicit BytePool(size_t storage_size, const char *name = nullptr);
141#endif
142
152
157 static constexpr size_t AlignSize(size_t raw_size)
158 {
159 return ((raw_size + (BLOCK_ALIGN - 1U)) & ~(BLOCK_ALIGN - 1U));
160 }
161
171 static constexpr size_t RequiredSize(size_t payload_size)
172 {
173 return (AlignSize(payload_size) + HEADER_SIZE);
174 }
175
197 EWaitResult TimedAllocEx(void **block, size_t size, Timeout timeout_ticks = WAIT_INFINITE);
198
207 void *TimedAlloc(size_t size, Timeout timeout_ticks = WAIT_INFINITE);
208
216 template <typename T> T *TimedAllocT(Timeout timeout_ticks = WAIT_INFINITE);
217
225 void *Alloc(size_t size);
226
231 template <typename T> T *AllocT();
232
238 void *TryAlloc(size_t size);
239
244 template <typename T> T *TryAllocT();
245
264 bool Free(void *ptr);
265
276 void GetInfo(size_t &available, size_t &largest, size_t &fragments) const;
277
285 bool IsStorageValid() const { return (m_first != nullptr); }
286
291 size_t GetSize() const
292 {
293 return ((m_first != nullptr) ? static_cast<size_t>(
294 reinterpret_cast<const uint8_t *>(m_end) - reinterpret_cast<const uint8_t *>(m_first)) : 0U);
295 }
296
302 size_t GetMaxRequest() const
303 {
304 const size_t size = GetSize();
305 return ((size >= MIN_BLOCK_SIZE) ? (size - HEADER_SIZE) : 0U);
306 }
307
308private:
310
314 struct alignas(8) BlockHeader
315 {
316 uint32_t size;
317 uint32_t state;
318 };
319
322 static const size_t BLOCK_ALIGN = 8U;
323
327 static const size_t MIN_BLOCK_SIZE = (HEADER_SIZE + BLOCK_ALIGN);
328
331 static const size_t POOL_SIZE_MAX = static_cast<size_t>(0xFFFFFFF8UL);
332
333 static const uint32_t STATE_FREE = 0x46524545UL;
334 static const uint32_t STATE_USED = 0x55534544UL;
335 static const uint32_t STATE_NONE = 0x00000000UL;
336
340 void Init();
341
345 static BlockHeader *Next(const BlockHeader *h)
346 {
347 return reinterpret_cast<BlockHeader *>(const_cast<uint8_t *>(reinterpret_cast<const uint8_t *>(h)) + h->size);
348 }
349
355 bool IsSane(const BlockHeader *h) const;
356
360 void Coalesce(BlockHeader *h);
361
367 void *FindFree(size_t need);
368
372 bool IsBlockStart(const BlockHeader *h) const;
373
376 static void AccountRun(size_t run, size_t &available, size_t &largest, size_t &fragments);
377
378 uint8_t *m_storage;
384};
385
386// ---------------------------------------------------------------------------
387// Constructors / Destructor
388// ---------------------------------------------------------------------------
389
390inline BytePool::BytePool(uint8_t *storage, size_t storage_size, const char *name)
391: m_storage(storage),
392 m_first(nullptr),
393 m_end(nullptr),
394 m_cv(),
395 m_storage_size(storage_size),
396 m_storage_owned(false)
397{
398 STK_ASSERT(storage != nullptr);
399 STK_ASSERT(storage_size >= MIN_BLOCK_SIZE);
400
401#if STK_SYNC_DEBUG_NAMES
402 SetTraceName(name);
403#else
404 STK_UNUSED(name);
405#endif
406
407 Init();
408}
409
410#if STK_MEMORY_PLACEMENT_NEW
411inline BytePool::BytePool(size_t storage_size, const char *name)
412: m_storage(MemoryAllocator::AllocateArrayT<uint8_t>(storage_size)),
413 m_first(nullptr),
414 m_end(nullptr),
415 m_cv(),
416 m_storage_size(storage_size),
417 m_storage_owned(true)
418{
419 STK_ASSERT(storage_size >= MIN_BLOCK_SIZE);
420
421#if STK_SYNC_DEBUG_NAMES
422 SetTraceName(name);
423#else
424 STK_UNUSED(name);
425#endif
426
427 // else: m_first remains nullptr; caller must check IsStorageValid()
428 Init();
429}
430#endif
431
433{
434 // ConditionVariable destructor asserts the wait list is empty
435#if STK_MEMORY_PLACEMENT_NEW
436 if (m_storage_owned && (m_storage != nullptr))
437 {
439 m_storage = nullptr;
440 }
441#endif
442
443 m_first = nullptr;
444 m_end = nullptr;
445}
446
447// ---------------------------------------------------------------------------
448// Alloc / TimedAlloc / TryAlloc
449// ---------------------------------------------------------------------------
450
451inline EWaitResult BytePool::TimedAllocEx(void **block, size_t size, Timeout timeout_ticks)
452{
454
455 STK_ASSERT(block != nullptr); // API contract: output pointer must be valid
456
457 if (block == nullptr)
458 {
459 // result is WAIT_RESULT_FAIL already, so noop
460 }
461 else
462 {
463 *block = nullptr;
464
465 if (hw::IsInsideISR() && (timeout_ticks != NO_WAIT))
466 {
467 STK_ASSERT(false); // API contract: ISR callers must pass NO_WAIT / use TryAlloc()
468 }
469 else if ((size == 0U) || (size > GetMaxRequest()))
470 {
471 // result is WAIT_RESULT_FAIL: such a request can never succeed, so do not block on it
472 }
473 else
474 {
476
477 // safe from overflow: size <= GetMaxRequest() <= POOL_SIZE_MAX - HEADER_SIZE
478 const size_t need = RequiredSize(size);
479
480 const bool timed_wait = (timeout_ticks != WAIT_INFINITE) && (timeout_ticks != NO_WAIT);
481
482 // capture an absolute deadline once, before entering the wait loop; every Free()
483 // wakes all waiters (Mesa-style, not a direct handoff) and a woken task may have
484 // to wait again, so re-passing the original timeout_ticks on each retry would
485 // silently restart it; kept as 64-bit Ticks (not truncated to Timeout) so it
486 // can't wrap on long uptimes
487 const Ticks deadline = (timed_wait ? (GetTicks() + static_cast<Ticks>(timeout_ticks)) : 0);
488
489 void *ptr = FindFree(need); // fast path: a suitable block is already available
490 result = WAIT_RESULT_SIGNAL;
491
492 while (ptr == nullptr)
493 {
494 Timeout remaining = timeout_ticks;
495
496 if (timeout_ticks == NO_WAIT)
497 {
498 result = WAIT_RESULT_TIMEOUT; // no waiting requested (ISR-safe path)
499 break;
500 }
501
502 if (timed_wait)
503 {
504 const Ticks left = (deadline - GetTicks());
505 if (left <= 0)
506 {
507 result = WAIT_RESULT_TIMEOUT; // budget is spent and a scan was just made
508 break;
509 }
510 remaining = static_cast<Timeout>(left);
511 }
512
513 // Atomically release the critical section, suspend the task, and
514 // re-acquire before returning - no CPU cycles wasted while waiting.
515 result = m_cv.WaitEx(cs_, remaining);
516 if ((result != WAIT_RESULT_SIGNAL) && (result != WAIT_RESULT_TIMEOUT))
517 {
518 break; // cancellation or kernel failure: report as is
519 }
520
521 // the lock was re-acquired: re-scan regardless of the wake reason, because the memory
522 // may have been released at the very moment the timeout expired (honor it instead of
523 // failing), or may have been taken by someone else after the wake-up (wait again)
524 ptr = FindFree(need);
525 if (ptr != nullptr)
526 {
527 result = WAIT_RESULT_SIGNAL;
528 }
529 else if (result == WAIT_RESULT_TIMEOUT)
530 {
531 break;
532 }
533 else
534 {
535 // spurious or stolen wake-up: continue waiting within the remaining budget
536 }
537 }
538
539 *block = ptr;
540 }
541 }
542
543 return result;
544}
545
546inline void *BytePool::TimedAlloc(size_t size, Timeout timeout_ticks)
547{
548 void *block = nullptr;
549
550 if (TimedAllocEx(&block, size, timeout_ticks) != WAIT_RESULT_SIGNAL)
551 {
552 block = nullptr;
553 }
554
555 return block;
556}
557
558template <typename T>
559inline T *BytePool::TimedAllocT(Timeout timeout_ticks)
560{
561 STK_ASSERT(alignof(T) <= BLOCK_ALIGN); // API contract: payload is aligned to BLOCK_ALIGN only
562
563 return static_cast<T *>(TimedAlloc(sizeof(T), timeout_ticks));
564}
565
566inline void *BytePool::Alloc(size_t size)
567{
568 return TimedAlloc(size, WAIT_INFINITE);
569}
570
571template <typename T>
573{
575}
576
577inline void *BytePool::TryAlloc(size_t size)
578{
579 return TimedAlloc(size, NO_WAIT);
580}
581
582template <typename T>
584{
585 return TimedAllocT<T>(NO_WAIT);
586}
587
588// ---------------------------------------------------------------------------
589// Free
590// ---------------------------------------------------------------------------
591
592inline bool BytePool::Free(void *ptr)
593{
594 bool success = false;
595
596 if ((ptr != nullptr) && (m_first != nullptr))
597 {
598 // bounds check: payload must be in range [m_first + HEADER_SIZE, m_end)
599 const Word pt = hw::PtrToWord(ptr);
600 const Word lo = hw::PtrToWord(m_first);
601 const Word hi = hw::PtrToWord(m_end);
602
603 if ((pt < (lo + HEADER_SIZE)) || (pt >= hi))
604 {
605 STK_ASSERT(false); // API contract: ptr does not belong to this pool
606 }
607 // alignment check: payloads always start on a BLOCK_ALIGN boundary relative to the area start
608 else if ((static_cast<size_t>(pt - lo) % BLOCK_ALIGN) != 0U)
609 {
610 STK_ASSERT(false); // API contract: ptr is misaligned (not a payload start)
611 }
612 else
613 {
615
616 BlockHeader *const h = (reinterpret_cast<BlockHeader *>(ptr) - 1);
617
618 if (h->state != STATE_USED)
619 {
620 STK_ASSERT(false); // block is not in use - double-free or foreign pointer
621 }
622 else
623 {
624 bool is_block = true;
625
626 #if defined(_DEBUG) || defined(DEBUG)
627 // O(n) validation that ptr really is the start of a block (and not an address
628 // inside some payload which happens to look like a header), only active in
629 // debug builds - compiles away completely in release
630 is_block = IsBlockStart(h);
631 STK_ASSERT(is_block);
632 #endif
633
634 if (is_block && IsSane(h))
635 {
636 h->state = STATE_FREE;
637
638 // merge with the free blocks that follow; the ones that precede are merged by the next scan
639 Coalesce(h);
640
641 // memory became available: waiters have different request sizes, so all of
642 // them must re-evaluate (waking only one could leave a satisfiable request asleep)
643 m_cv.NotifyAll_CS();
644
645 success = true;
646 }
647 }
648 }
649 }
650
651 return success;
652}
653
654// ---------------------------------------------------------------------------
655// Statistics
656// ---------------------------------------------------------------------------
657
658inline void BytePool::GetInfo(size_t &available, size_t &largest, size_t &fragments) const
659{
660 available = 0U;
661 largest = 0U;
662 fragments = 0U;
663
664 if (m_first != nullptr)
665 {
667
668 size_t run = 0U; // length of the current run of adjacent free blocks
669
670 for (const BlockHeader *h = m_first; (h < m_end); h = Next(h))
671 {
672 if (!IsSane(h))
673 {
674 STK_ASSERT(false); // pool metadata is corrupted (payload overrun?)
675 break;
676 }
677
678 if (h->state == STATE_FREE)
679 {
680 run += h->size;
681 }
682 else
683 {
684 AccountRun(run, available, largest, fragments);
685 run = 0U;
686 }
687 }
688
689 AccountRun(run, available, largest, fragments);
690 }
691}
692
693// ---------------------------------------------------------------------------
694// Private helpers
695// ---------------------------------------------------------------------------
696
697inline void BytePool::Init()
698{
699 static_assert(sizeof(BlockHeader) == HEADER_SIZE, "BlockHeader must be exactly HEADER_SIZE bytes");
700
701 if (m_storage != nullptr)
702 {
703 // align the area start to BLOCK_ALIGN and trim its size to a multiple of BLOCK_ALIGN
704 const size_t addr = static_cast<size_t>(hw::PtrToWord(m_storage));
705 const size_t shift = (AlignSize(addr) - addr);
706 size_t usable = ((m_storage_size > shift) ? ((m_storage_size - shift) & ~(BLOCK_ALIGN - 1U)) : 0U);
707
708 // block sizes are 32-bit: a larger area would be wasted beyond this limit
709 if (usable > POOL_SIZE_MAX)
710 {
711 usable = POOL_SIZE_MAX;
712 }
713
714 if (usable >= MIN_BLOCK_SIZE)
715 {
718
719 m_first->size = static_cast<uint32_t>(usable);
720 m_first->state = STATE_FREE;
721 }
722 }
723}
724
725inline bool BytePool::IsSane(const BlockHeader *h) const
726{
727 const bool state_ok = ((h->state == STATE_FREE) || (h->state == STATE_USED));
728
729 return (state_ok &&
730 (h->size >= MIN_BLOCK_SIZE) &&
731 ((h->size % BLOCK_ALIGN) == 0U) &&
732 (static_cast<size_t>(reinterpret_cast<const uint8_t *>(m_end) - reinterpret_cast<const uint8_t *>(h)) >= h->size));
733}
734
736{
737 for (BlockHeader *n = Next(h); (n < m_end) && (n->state == STATE_FREE) && IsSane(n); n = Next(h))
738 {
739 h->size += n->size;
740
741 // wipe the absorbed header so it can never be mistaken for a live block
742 n->state = STATE_NONE;
743 n->size = 0U;
744 }
745}
746
747inline void *BytePool::FindFree(size_t need)
748{
749 void *result = nullptr;
750
751 for (BlockHeader *h = m_first; (h != nullptr) && (h < m_end) && (result == nullptr); h = Next(h))
752 {
753 if (!IsSane(h))
754 {
755 STK_ASSERT(false); // pool metadata is corrupted (payload overrun?)
756 break;
757 }
758
759 if (h->state == STATE_FREE)
760 {
761 Coalesce(h);
762
763 if (h->size >= need)
764 {
765 const size_t rest = (h->size - need);
766
767 // split off the tail if it can form a valid block of its own
768 if (rest >= MIN_BLOCK_SIZE)
769 {
770 BlockHeader *const tail = reinterpret_cast<BlockHeader *>(reinterpret_cast<uint8_t *>(h) + need);
771 tail->size = static_cast<uint32_t>(rest);
772 tail->state = STATE_FREE;
773
774 h->size = static_cast<uint32_t>(need);
775 }
776
777 h->state = STATE_USED;
778 result = (h + 1);
779 }
780 }
781 }
782
783 return result;
784}
785
786inline bool BytePool::IsBlockStart(const BlockHeader *h) const
787{
788 bool found = false;
789
790 for (const BlockHeader *b = m_first; (b != nullptr) && (b <= h) && (b < m_end) && IsSane(b); b = Next(b))
791 {
792 if (b == h)
793 {
794 found = true;
795 break;
796 }
797 }
798
799 return found;
800}
801
802inline void BytePool::AccountRun(size_t run, size_t &available, size_t &largest, size_t &fragments)
803{
804 if (run != 0U)
805 {
806 const size_t payload = (run - HEADER_SIZE);
807
808 available += payload;
809 ++fragments;
810
811 if (payload > largest)
812 {
813 largest = payload;
814 }
815 }
816}
817
818} // namespace memory
819} // namespace stk
820
821#endif /* STK_MEMORY_BYTEPOOL_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 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().
static const uint32_t STATE_USED
"USED"
BlockHeader * m_end
one past the last block (aligned), nullptr if the pool is invalid
static const uint32_t STATE_FREE
"FREE"
static const uint32_t STATE_NONE
stale header absorbed by a merge
bool Free(void *ptr)
Return a previously allocated block to the pool.
bool IsStorageValid() const
Verify that the backing storage is valid and the pool is ready for use.
sync::ConditionVariable m_cv
signalled by Free() to wake tasks blocked in TimedAlloc()
bool IsBlockStart(const BlockHeader *h) const
Check that h is the header of an existing block (walks the list from the start).
bool IsSane(const BlockHeader *h) const
Check that the header h (which must be inside the pool) looks intact.
size_t GetMaxRequest() const
Get the largest request that can ever succeed on this pool.
STK_NONCOPYABLE_CLASS(BytePool)
T * AllocT()
Allocate one typed block, blocking indefinitely until memory is available.
static const size_t HEADER_SIZE
Size of the per-block header in bytes.
T * TryAllocT()
Non-blocking typed allocation attempt.
static const size_t MIN_BLOCK_SIZE
Smallest block: a header plus the minimum payload (BLOCK_ALIGN).
static const size_t POOL_SIZE_MAX
Largest managed area: block sizes are stored as 32-bit values.
EWaitResult TimedAllocEx(void **block, size_t size, Timeout timeout_ticks=WAIT_INFINITE)
Allocate a block, preserving the full wait outcome.
size_t m_storage_size
size of the raw storage in bytes
uint8_t * m_storage
raw storage as supplied or allocated (before alignment)
static BlockHeader * Next(const BlockHeader *h)
Get the header of the block that follows h.
void * FindFree(size_t need)
Find (and claim) the first free block that can hold need bytes.
size_t GetSize() const
Get the total size of the managed (aligned) area in bytes, headers included.
void * Alloc(size_t size)
Allocate a block, blocking indefinitely until memory is available.
void GetInfo(size_t &available, size_t &largest, size_t &fragments) const
Collect pool statistics.
static constexpr size_t RequiredSize(size_t payload_size)
Compute the pool space consumed by a single allocation of payload_size bytes.
static constexpr size_t AlignSize(size_t raw_size)
Round a size up to the nearest multiple of the block alignment (8 bytes).
static const size_t BLOCK_ALIGN
Payload alignment and block size granularity in bytes.
STK_VIRT_DTOR ~BytePool()
Destructor.
static void AccountRun(size_t run, size_t &available, size_t &largest, size_t &fragments)
Add one free run of run bytes (headers included) to the statistics.
T * TimedAllocT(Timeout timeout_ticks=WAIT_INFINITE)
Allocate one typed block, blocking until memory becomes available or the timeout expires.
BytePool(uint8_t *storage, size_t storage_size, const char *name=nullptr)
Construct a pool backed by caller-supplied (external) storage.
bool m_storage_owned
true -> storage is heap-allocated; free in destructor
BlockHeader * m_first
first block header (aligned), nullptr if the pool is invalid
void * TryAlloc(size_t size)
Non-blocking allocation attempt.
void * TimedAlloc(size_t size, Timeout timeout_ticks=WAIT_INFINITE)
Allocate a block, blocking until memory becomes available or the timeout expires.
void Init()
Align the area and create the initial single free block.
void Coalesce(BlockHeader *h)
Merge h (a free block) with the free blocks that directly follow it.
Header preceding every block (free or used) inside the pool area.
uint32_t state
STATE_FREE / STATE_USED, anything else is treated as corruption.
uint32_t size
total block size including this header (multiple of BLOCK_ALIGN)
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