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 Class Reference

Variable-size (first-fit) allocator with block coalescing and proper task-blocking semantics. More...

#include <stk_memory_bytepool.h>

Inheritance diagram for stk::memory::BytePool:
Collaboration diagram for stk::memory::BytePool:

Classes

struct  BlockHeader
 Header preceding every block (free or used) inside the pool area. More...

Public Member Functions

 BytePool (uint8_t *storage, size_t storage_size, const char *name=nullptr)
 Construct a pool backed by caller-supplied (external) storage.
 BytePool (size_t storage_size, const char *name=nullptr)
 Construct a pool with heap-allocated storage.
STK_VIRT_DTOR ~BytePool ()
 Destructor.
EWaitResult TimedAllocEx (void **block, size_t size, Timeout timeout_ticks=WAIT_INFINITE)
 Allocate a block, preserving the full wait outcome.
void * TimedAlloc (size_t size, Timeout timeout_ticks=WAIT_INFINITE)
 Allocate a block, blocking until memory becomes available or the timeout expires.
template<typename T>
T * TimedAllocT (Timeout timeout_ticks=WAIT_INFINITE)
 Allocate one typed block, blocking until memory becomes available or the timeout expires.
void * Alloc (size_t size)
 Allocate a block, blocking indefinitely until memory is available.
template<typename T>
T * AllocT ()
 Allocate one typed block, blocking indefinitely until memory is available.
void * TryAlloc (size_t size)
 Non-blocking allocation attempt.
template<typename T>
T * TryAllocT ()
 Non-blocking typed allocation attempt.
bool Free (void *ptr)
 Return a previously allocated block to the pool.
void GetInfo (size_t &available, size_t &largest, size_t &fragments) const
 Collect pool statistics.
bool IsStorageValid () const
 Verify that the backing storage is valid and the pool is ready for use.
size_t GetSize () const
 Get the total size of the managed (aligned) area in bytes, headers included.
size_t GetMaxRequest () const
 Get the largest request that can ever succeed on this pool.
void SetTraceName (const char *name)
 Set name.
const char * GetTraceName () const
 Get name.

Static Public Member Functions

static constexpr size_t AlignSize (size_t raw_size)
 Round a size up to the nearest multiple of the block alignment (8 bytes).
static constexpr size_t RequiredSize (size_t payload_size)
 Compute the pool space consumed by a single allocation of payload_size bytes.

Static Public Attributes

static const size_t HEADER_SIZE = 8U
 Size of the per-block header in bytes.

Private Member Functions

 STK_NONCOPYABLE_CLASS (BytePool)
void Init ()
 Align the area and create the initial single free block.
bool IsSane (const BlockHeader *h) const
 Check that the header h (which must be inside the pool) looks intact.
void Coalesce (BlockHeader *h)
 Merge h (a free block) with the free blocks that directly follow it.
void * FindFree (size_t need)
 Find (and claim) the first free block that can hold need bytes.
bool IsBlockStart (const BlockHeader *h) const
 Check that h is the header of an existing block (walks the list from the start).

Static Private Member Functions

static BlockHeader * Next (const BlockHeader *h)
 Get the header of the block that follows h.
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.

Private Attributes

uint8_t * m_storage
 raw storage as supplied or allocated (before alignment)
BlockHeader * m_first
 first block header (aligned), nullptr if the pool is invalid
BlockHeader * m_end
 one past the last block (aligned), nullptr if the pool is invalid
sync::ConditionVariable m_cv
 signalled by Free() to wake tasks blocked in TimedAlloc()
size_t m_storage_size
 size of the raw storage in bytes
bool m_storage_owned
 true -> storage is heap-allocated; free in destructor

Static Private Attributes

static const size_t BLOCK_ALIGN = 8U
 Payload alignment and block size granularity in bytes.
static const size_t MIN_BLOCK_SIZE = (HEADER_SIZE + BLOCK_ALIGN)
 Smallest block: a header plus the minimum payload (BLOCK_ALIGN).
static const size_t POOL_SIZE_MAX = static_cast<size_t>(0xFFFFFFF8UL)
 Largest managed area: block sizes are stored as 32-bit values.
static const uint32_t STATE_FREE = 0x46524545UL
 "FREE"
static const uint32_t STATE_USED = 0x55534544UL
 "USED"
static const uint32_t STATE_NONE = 0x00000000UL
 stale header absorbed by a merge

Detailed Description

Variable-size (first-fit) allocator with block coalescing and proper task-blocking semantics.

BytePool manages a single contiguous memory area and hands out blocks of arbitrary size from it. It is the counterpart of BlockMemoryPool for workloads where allocation sizes vary (message payloads, strings, protocol frames), at the price of a non-constant allocation time and the possibility of fragmentation.

The pool keeps an implicit block list inside the storage area. Every block (free or used) is preceded by an 8-byte header holding the total block size and a state marker. Blocks are laid out back to back, so the next block is always found at header + size and no separate metadata is required.

Memory layout (pool storage, contiguous):

[ hdr | payload ][ hdr | payload (free) ][ hdr | payload ] ...
^                                                          ^
m_first                                                    m_end

Payload pointers returned to the caller are aligned to \c BLOCK_ALIGN (8 bytes).

Allocation policy**: first-fit. While scanning, adjacent free blocks are merged, so a failed scan always leaves the pool fully coalesced. A found block is split when the remainder is large enough to hold a header and a minimum payload.

Freeing**: the block is marked free and merged with the free blocks that follow it. Merging with a free block that precedes it is done lazily by the next scan, so every free run is always reported and allocated as a single block.

Two storage modes** allow fully static (zero-heap) deployments:

Mode Constructor Frees storage?
External storage BytePool(buf, bufsz [, name]) No - caller
Heap storage BytePool(bufsz [, name]) Yes - dtor

Blocking semantics**: TimedAlloc() suspends the calling task via a ConditionVariable until enough contiguous memory is released by Free(), or the timeout expires. All waiters are woken on every Free() (their requests differ in size, so waking only one could leave a satisfiable request sleeping); each re-checks its own request and goes back to sleep if it still does not fit. The original timeout is converted to an absolute deadline so repeated wake-ups never extend it.

ISR safety**: TryAlloc() and Free() may be called from an ISR (guarded by a critical section) but, unlike BlockMemoryPool, their execution time is not constant: it is proportional to the number of blocks scanned. Do not use them from time-critical ISRs on pools with many blocks. Alloc() and TimedAlloc() with a non-zero timeout must only be called from task context.

// -----------------------------------------------------------------------
// Example - zero-heap external storage
// -----------------------------------------------------------------------
alignas(8) static uint8_t g_Heap[4096];
stk::memory::BytePool g_Pool(g_Heap, sizeof(g_Heap));
void Task_Producer() {
char *msg = static_cast<char *>(g_Pool.TimedAlloc(64, 100)); // wait up to 100 ticks
if (msg) {
Format(msg, 64);
g_Queue.Write(msg, NO_WAIT);
}
}
void Task_Consumer() {
void *msg = nullptr;
if (g_Queue.Read(msg)) {
Send(static_cast<const char *>(msg));
g_Pool.Free(msg); // wakes blocked allocators
}
}
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
Variable-size (first-fit) allocator with block coalescing and proper task-blocking semantics.
See also
BlockMemoryPool, sync::ConditionVariable
Note
Blocking alloc paths require the kernel to be compiled with KERNEL_SYNC. TryAlloc() and Free() are always available regardless of kernel mode.
A request larger than GetMaxRequest() can never succeed and fails immediately instead of blocking forever.
Large requests may be starved by a stream of small ones: freed memory is not reserved for the longest-waiting task.

Definition at line 105 of file stk_memory_bytepool.h.

Constructor & Destructor Documentation

◆ BytePool() [1/2]

stk::memory::BytePool::BytePool ( uint8_t * storage,
size_t storage_size,
const char * name = nullptr )
inlineexplicit

Construct a pool backed by caller-supplied (external) storage.

The pool references storage directly without taking ownership. The caller must keep the buffer alive for the entire lifetime of this object. The storage is not freed on destruction. A misaligned start address is corrected internally (the unaligned head and tail bytes are not used).

Parameters
[in]storagePointer to a caller-owned byte buffer. Should be aligned to BLOCK_ALIGN (8 bytes) to avoid wasting bytes.
[in]storage_sizeSize of storage in bytes. Must be large enough to hold at least one header and a minimum payload; otherwise IsStorageValid() returns false.
[in]nameOptional name forwarded to ITraceable::SetTraceName().
Note
ISR-unsafe (must be called from task or init context).

Definition at line 390 of file stk_memory_bytepool.h.

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}
#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
BlockHeader * m_end
one past the last block (aligned), nullptr if the pool is invalid
sync::ConditionVariable m_cv
signalled by Free() to wake tasks blocked in TimedAlloc()
static const size_t MIN_BLOCK_SIZE
Smallest block: a header plus the minimum payload (BLOCK_ALIGN).
size_t m_storage_size
size of the raw storage in bytes
uint8_t * m_storage
raw storage as supplied or allocated (before alignment)
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 Init()
Align the area and create the initial single free block.
void SetTraceName(const char *name)
Set name.
Definition stk_common.h:570

References Init(), m_cv, m_end, m_first, m_storage, m_storage_owned, m_storage_size, MIN_BLOCK_SIZE, stk::ITraceable::SetTraceName(), STK_ASSERT, and STK_UNUSED.

Referenced by STK_NONCOPYABLE_CLASS().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ BytePool() [2/2]

stk::memory::BytePool::BytePool ( size_t storage_size,
const char * name = nullptr )
inlineexplicit

Construct a pool with heap-allocated storage.

Allocates a flat byte buffer of storage_size bytes via MemoryAllocator::AllocateArrayT (non-throwing). When exceptions are disabled (embedded default), check IsStorageValid() immediately after construction to detect allocation failure before first use.

Parameters
[in]storage_sizeSize of the pool area in bytes (headers included).
[in]nameOptional name forwarded to ITraceable::SetTraceName().
Note
ISR-unsafe.

Definition at line 411 of file stk_memory_bytepool.h.

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}
static TElement * AllocateArrayT(size_t count, TArgs &&...args)
Allocate an array of elements and default/copy-construct each one.

References Init(), m_cv, m_end, m_first, m_storage, m_storage_owned, m_storage_size, MIN_BLOCK_SIZE, stk::ITraceable::SetTraceName(), STK_ASSERT, and STK_UNUSED.

Here is the call graph for this function:

◆ ~BytePool()

stk::memory::BytePool::~BytePool ( )
inline

Destructor.

Frees heap-allocated storage if m_storage_owned is true. External storage is never touched.

Note
Destroying the pool while tasks are blocked in Alloc() or TimedAlloc() is a logical error (dangling waiters). An assertion is triggered in debug builds inside the ConditionVariable destructor.
MISRA deviation: [STK-DEV-005] Rule 10-3-2.

Definition at line 432 of file stk_memory_bytepool.h.

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}
static void FreeArrayT(TElement ptr[], size_t count)
Destroy and free an array allocated via AllocateT().

References stk::memory::MemoryAllocator::FreeArrayT(), m_end, m_first, m_storage, m_storage_owned, and m_storage_size.

Here is the call graph for this function:

Member Function Documentation

◆ AccountRun()

void stk::memory::BytePool::AccountRun ( size_t run,
size_t & available,
size_t & largest,
size_t & fragments )
inlinestaticprivate

Add one free run of run bytes (headers included) to the statistics.

Definition at line 802 of file stk_memory_bytepool.h.

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}
static const size_t HEADER_SIZE
Size of the per-block header in bytes.

References HEADER_SIZE.

Referenced by GetInfo().

Here is the caller graph for this function:

◆ AlignSize()

constexpr size_t stk::memory::BytePool::AlignSize ( size_t raw_size)
inlinestaticconstexpr

Round a size up to the nearest multiple of the block alignment (8 bytes).

Parameters
[in]raw_sizeSize in bytes.
Returns
Aligned size in bytes.

Definition at line 157 of file stk_memory_bytepool.h.

158 {
159 return ((raw_size + (BLOCK_ALIGN - 1U)) & ~(BLOCK_ALIGN - 1U));
160 }
static const size_t BLOCK_ALIGN
Payload alignment and block size granularity in bytes.

References BLOCK_ALIGN.

Referenced by Init(), and RequiredSize().

Here is the caller graph for this function:

◆ Alloc()

void * stk::memory::BytePool::Alloc ( size_t size)
inline

Allocate a block, blocking indefinitely until memory is available.

Parameters
[in]sizeRequested payload size in bytes.
Returns
Pointer to an uninitialized block. Returns nullptr only if the wait was cancelled via IKernel::CancelTaskWait() or the request is invalid (never on timeout). Use TimedAllocEx() to tell these apart.
Warning
ISR-unsafe.

Definition at line 566 of file stk_memory_bytepool.h.

567{
568 return TimedAlloc(size, WAIT_INFINITE);
569}
static constexpr Timeout WAIT_INFINITE
Timeout value: block indefinitely until the synchronization object is signaled.
Definition stk_common.h:228
void * TimedAlloc(size_t size, Timeout timeout_ticks=WAIT_INFINITE)
Allocate a block, blocking until memory becomes available or the timeout expires.

References TimedAlloc(), and stk::WAIT_INFINITE.

Here is the call graph for this function:

◆ AllocT()

template<typename T>
T * stk::memory::BytePool::AllocT ( )
inline

Allocate one typed block, blocking indefinitely until memory is available.

Returns
Typed pointer, or nullptr (see Alloc()).
Warning
ISR-unsafe.

Definition at line 572 of file stk_memory_bytepool.h.

573{
575}
T * TimedAllocT(Timeout timeout_ticks=WAIT_INFINITE)
Allocate one typed block, blocking until memory becomes available or the timeout expires.

References TimedAllocT(), and stk::WAIT_INFINITE.

Here is the call graph for this function:

◆ Coalesce()

void stk::memory::BytePool::Coalesce ( BlockHeader * h)
inlineprivate

Merge h (a free block) with the free blocks that directly follow it.

Note
Critical section must be held.

Definition at line 735 of file stk_memory_bytepool.h.

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}
static const uint32_t STATE_FREE
"FREE"
static const uint32_t STATE_NONE
stale header absorbed by a merge
bool IsSane(const BlockHeader *h) const
Check that the header h (which must be inside the pool) looks intact.
static BlockHeader * Next(const BlockHeader *h)
Get the header of the block that follows h.
Header preceding every block (free or used) inside the pool area.

References IsSane(), m_end, Next(), stk::memory::BytePool::BlockHeader::size, STATE_FREE, and STATE_NONE.

Referenced by FindFree(), and Free().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ FindFree()

void * stk::memory::BytePool::FindFree ( size_t need)
inlineprivate

Find (and claim) the first free block that can hold need bytes.

Parameters
[in]needTotal block size including header (already aligned).
Returns
Payload pointer, or nullptr if no free block is large enough.
Note
Critical section must be held. Coalesces every free run it passes.

Definition at line 747 of file stk_memory_bytepool.h.

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}
static const uint32_t STATE_USED
"USED"
void Coalesce(BlockHeader *h)
Merge h (a free block) with the free blocks that directly follow it.
uint32_t size
total block size including this header (multiple of BLOCK_ALIGN)

References Coalesce(), IsSane(), m_end, m_first, MIN_BLOCK_SIZE, Next(), stk::memory::BytePool::BlockHeader::size, stk::memory::BytePool::BlockHeader::state, STATE_FREE, STATE_USED, and STK_ASSERT.

Referenced by TimedAllocEx().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ Free()

bool stk::memory::BytePool::Free ( void * ptr)
inline

Return a previously allocated block to the pool.

Marks the block free, merges it with the free blocks that follow it and wakes all tasks blocked inside Alloc() or TimedAlloc() so each can re-check its own request. A woken task may find that the memory was taken by a racing TryAlloc() and continue waiting.

Parameters
[in]ptrPointer previously returned by an allocation function of this pool instance. Must not be nullptr. Range, alignment and the block state marker are validated; failures trigger an assertion in debug builds and return false in release builds. In debug builds the pointer is additionally verified to be the start of a real block.
Returns
true - block successfully returned. false - ptr is nullptr, does not belong to this pool, is misaligned or the block is not in use (double-free); each case indicates a caller logic error.
Note
ISR-safe (see TryAlloc()).
Warning
Double-free detection relies on the block header and is reliable only until the freed memory is handed out again. Null the pointer after Free().

Definition at line 592 of file stk_memory_bytepool.h.

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 {
614 const sync::ScopedCriticalSection cs_;
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}
uintptr_t Word
Native processor word type.
Definition stk_common.h:160
static constexpr Word PtrToWord(T *const ptr) noexcept
Cast a pointer to a CPU register-width integer.
Definition stk_arch.h:214
bool IsBlockStart(const BlockHeader *h) const
Check that h is the header of an existing block (walks the list from the start).

References BLOCK_ALIGN, Coalesce(), HEADER_SIZE, IsBlockStart(), IsSane(), m_cv, m_end, m_first, stk::hw::PtrToWord(), stk::memory::BytePool::BlockHeader::state, STATE_FREE, STATE_USED, and STK_ASSERT.

Here is the call graph for this function:

◆ GetInfo()

void stk::memory::BytePool::GetInfo ( size_t & available,
size_t & largest,
size_t & fragments ) const
inline

Collect pool statistics.

Walks the block list under a critical section, so the three values are mutually consistent. Adjacent free blocks are counted as one fragment.

Parameters
[out]availableTotal payload bytes that can still be allocated, summed over all free fragments (a single request can use at most largest of them).
[out]largestPayload size of the largest free fragment, i.e. the biggest request that would succeed right now.
[out]fragmentsNumber of free fragments (1 means no external fragmentation).
Note
ISR-safe, but O(n) in the number of blocks.

Definition at line 658 of file stk_memory_bytepool.h.

659{
660 available = 0U;
661 largest = 0U;
662 fragments = 0U;
663
664 if (m_first != nullptr)
665 {
666 const sync::ScopedCriticalSection cs_;
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}
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.

References AccountRun(), IsSane(), m_end, m_first, Next(), STATE_FREE, and STK_ASSERT.

Referenced by tx_byte_pool_info_get().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ GetMaxRequest()

size_t stk::memory::BytePool::GetMaxRequest ( ) const
inline

Get the largest request that can ever succeed on this pool.

Equals the payload of a single block spanning the entire pool.

Returns
Maximum payload size in bytes, or 0 if the storage is invalid.
Note
ISR-safe.

Definition at line 302 of file stk_memory_bytepool.h.

303 {
304 const size_t size = GetSize();
305 return ((size >= MIN_BLOCK_SIZE) ? (size - HEADER_SIZE) : 0U);
306 }
size_t GetSize() const
Get the total size of the managed (aligned) area in bytes, headers included.

References GetSize(), HEADER_SIZE, and MIN_BLOCK_SIZE.

Referenced by TimedAllocEx().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ GetSize()

size_t stk::memory::BytePool::GetSize ( ) const
inline

Get the total size of the managed (aligned) area in bytes, headers included.

Returns
Managed area size in bytes, or 0 if the storage is invalid.
Note
ISR-safe.

Definition at line 291 of file stk_memory_bytepool.h.

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 }

References m_end, and m_first.

Referenced by GetMaxRequest().

Here is the caller graph for this function:

◆ GetTraceName()

const char * stk::ITraceable::GetTraceName ( ) const
inlineinherited

Get name.

Returns
Name string, or NULL if not set or if STK_SYNC_DEBUG_NAMES is 0.

Definition at line 582 of file stk_common.h.

583 {
584 #if STK_SYNC_DEBUG_NAMES
585 return m_trace_name;
586 #else
587 return nullptr;
588 #endif
589 }

◆ Init()

void stk::memory::BytePool::Init ( )
inlineprivate

Align the area and create the initial single free block.

Note
Leaves m_first and m_end as nullptr if the area is unusable.

Definition at line 697 of file stk_memory_bytepool.h.

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}
static constexpr T * WordToPtr(Word value) noexcept
Cast a CPU register-width integer back to a pointer.
Definition stk_arch.h:231
static const size_t POOL_SIZE_MAX
Largest managed area: block sizes are stored as 32-bit values.
static constexpr size_t AlignSize(size_t raw_size)
Round a size up to the nearest multiple of the block alignment (8 bytes).

References AlignSize(), BLOCK_ALIGN, HEADER_SIZE, m_end, m_first, m_storage, m_storage_size, MIN_BLOCK_SIZE, POOL_SIZE_MAX, stk::hw::PtrToWord(), STATE_FREE, and stk::hw::WordToPtr().

Referenced by BytePool(), and BytePool().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ IsBlockStart()

bool stk::memory::BytePool::IsBlockStart ( const BlockHeader * h) const
inlineprivate

Check that h is the header of an existing block (walks the list from the start).

Note
Critical section must be held. O(n), used by debug builds only.

Definition at line 786 of file stk_memory_bytepool.h.

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}

References IsSane(), m_end, m_first, and Next().

Referenced by Free().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ IsSane()

bool stk::memory::BytePool::IsSane ( const BlockHeader * h) const
inlineprivate

Check that the header h (which must be inside the pool) looks intact.

Verifies the state marker, the size granularity and that the block does not extend beyond the pool end. Protects the list walkers from endless loops and out-of-bounds reads if a caller overruns a payload.

Definition at line 725 of file stk_memory_bytepool.h.

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}

References BLOCK_ALIGN, m_end, MIN_BLOCK_SIZE, stk::memory::BytePool::BlockHeader::size, stk::memory::BytePool::BlockHeader::state, STATE_FREE, and STATE_USED.

Referenced by Coalesce(), FindFree(), Free(), GetInfo(), and IsBlockStart().

Here is the caller graph for this function:

◆ IsStorageValid()

bool stk::memory::BytePool::IsStorageValid ( ) const
inline

Verify that the backing storage is valid and the pool is ready for use.

Always true for pools constructed with sufficiently large external storage. For heap-constructed pools, false if the allocation failed. false as well if the supplied area is too small to hold even a single block.

Returns
true if the pool is ready for use.
Note
ISR-safe.

Definition at line 285 of file stk_memory_bytepool.h.

285{ return (m_first != nullptr); }

References m_first.

◆ Next()

BlockHeader * stk::memory::BytePool::Next ( const BlockHeader * h)
inlinestaticprivate

Get the header of the block that follows h.

Note
The result may equal m_end, which must not be dereferenced.

Definition at line 345 of file stk_memory_bytepool.h.

346 {
347 return reinterpret_cast<BlockHeader *>(const_cast<uint8_t *>(reinterpret_cast<const uint8_t *>(h)) + h->size);
348 }

References stk::memory::BytePool::BlockHeader::size.

Referenced by Coalesce(), FindFree(), GetInfo(), and IsBlockStart().

Here is the caller graph for this function:

◆ RequiredSize()

constexpr size_t stk::memory::BytePool::RequiredSize ( size_t payload_size)
inlinestaticconstexpr

Compute the pool space consumed by a single allocation of payload_size bytes.

Equals the aligned payload plus the block header. Use it to size external storage, adding the value for every block that must be alive at the same time:

alignas(8) uint8_t buf[BytePool::RequiredSize(128) * 4 + BytePool::RequiredSize(512)];
static constexpr size_t RequiredSize(size_t payload_size)
Compute the pool space consumed by a single allocation of payload_size bytes.
Parameters
[in]payload_sizeRequested payload size in bytes.
Returns
Space in bytes taken from the pool.

Definition at line 171 of file stk_memory_bytepool.h.

172 {
173 return (AlignSize(payload_size) + HEADER_SIZE);
174 }

References AlignSize(), and HEADER_SIZE.

Referenced by TimedAllocEx(), and tx_byte_pool_create().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ SetTraceName()

void stk::ITraceable::SetTraceName ( const char * name)
inlineinherited

Set name.

Parameters
[in]nameNull-terminated string or NULL.
Note
If STK_SYNC_DEBUG_NAMES is 0 then calling this function has no effect.

Definition at line 570 of file stk_common.h.

571 {
572 #if STK_SYNC_DEBUG_NAMES
573 m_trace_name = name;
574 #else
575 STK_UNUSED(name);
576 #endif
577 }

References STK_UNUSED.

Referenced by stk::memory::BlockMemoryPool::BlockMemoryPool(), stk::memory::BlockMemoryPool::BlockMemoryPool(), stk::memory::BytePool::BytePool(), stk::memory::BytePool::BytePool(), tx_block_pool_create(), tx_event_flags_create(), tx_mutex_create(), tx_queue_create(), and tx_semaphore_create().

Here is the caller graph for this function:

◆ STK_NONCOPYABLE_CLASS()

stk::memory::BytePool::STK_NONCOPYABLE_CLASS ( BytePool )
private

References BytePool().

Here is the call graph for this function:

◆ TimedAlloc()

void * stk::memory::BytePool::TimedAlloc ( size_t size,
Timeout timeout_ticks = WAIT_INFINITE )
inline

Allocate a block, blocking until memory becomes available or the timeout expires.

Parameters
[in]sizeRequested payload size in bytes.
[in]timeout_ticksMaximum time to wait (ticks). Same semantics as TimedAllocEx().
Returns
Pointer to an uninitialized block of at least size bytes aligned to 8 bytes, or nullptr on timeout, cancellation or invalid arguments.
Note
Collapses timeout and cancellation. Use TimedAllocEx() to tell them apart.
Warning
ISR-safe only when timeout_ticks = NO_WAIT; ISR-unsafe otherwise.

Definition at line 546 of file stk_memory_bytepool.h.

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}
@ WAIT_RESULT_SIGNAL
The wake was caused by a signal.
Definition stk_common.h:139
EWaitResult TimedAllocEx(void **block, size_t size, Timeout timeout_ticks=WAIT_INFINITE)
Allocate a block, preserving the full wait outcome.

References TimedAllocEx(), and stk::WAIT_RESULT_SIGNAL.

Referenced by Alloc(), TimedAllocT(), and TryAlloc().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ TimedAllocEx()

EWaitResult stk::memory::BytePool::TimedAllocEx ( void ** block,
size_t size,
Timeout timeout_ticks = WAIT_INFINITE )
inline

Allocate a block, preserving the full wait outcome.

Searches for a free block of at least size bytes. If none exists the calling task is suspended via the internal ConditionVariable and re-tries after every Free() until the request is satisfied, the timeout expires or the wait is cancelled. The timeout is a total budget, not per wake-up.

Parameters
[out]blockReceives the allocated block on WAIT_RESULT_SIGNAL, nullptr on any other result. Must not be nullptr.
[in]sizeRequested payload size in bytes (must be > 0 and <= GetMaxRequest()).
[in]timeout_ticksMaximum time to wait (ticks). WAIT_INFINITE - block indefinitely (default). NO_WAIT - fail immediately if no suitable block exists (ISR-safe).
Returns
WAIT_RESULT_SIGNAL - a block was allocated (immediately or after waiting), WAIT_RESULT_TIMEOUT - no suitable block became available before the timeout (or NO_WAIT was passed); a block freed at the very moment the timeout expired is still returned as SIGNAL, WAIT_RESULT_CANCELED - the wait was cancelled via IKernel::CancelTaskWait(), WAIT_RESULT_FAIL - invalid arguments (block is nullptr, size is 0 or exceeds GetMaxRequest(), no valid storage) or the kernel rejected the wait.
Warning
ISR-safe only when timeout_ticks = NO_WAIT; ISR-unsafe otherwise.

Definition at line 451 of file stk_memory_bytepool.h.

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 {
475 sync::ScopedCriticalSection cs_;
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}
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
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
bool IsInsideISR()
Check whether the CPU is currently executing inside a hardware interrupt service routine (ISR).
size_t GetMaxRequest() const
Get the largest request that can ever succeed on this pool.
void * FindFree(size_t need)
Find (and claim) the first free block that can hold need bytes.

References FindFree(), GetMaxRequest(), stk::GetTicks(), stk::hw::IsInsideISR(), m_cv, stk::NO_WAIT, RequiredSize(), STK_ASSERT, stk::WAIT_INFINITE, stk::WAIT_RESULT_FAIL, stk::WAIT_RESULT_SIGNAL, and stk::WAIT_RESULT_TIMEOUT.

Referenced by TimedAlloc().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ TimedAllocT()

template<typename T>
T * stk::memory::BytePool::TimedAllocT ( Timeout timeout_ticks = WAIT_INFINITE)
inline

Allocate one typed block, blocking until memory becomes available or the timeout expires.

Thin typed wrapper around TimedAlloc() for sizeof(T) bytes. The memory is uninitialized; no constructor is run. T must not require alignment above 8 bytes.

Parameters
[in]timeout_ticksMaximum time to wait (ticks).
Returns
Typed pointer, or nullptr if the timeout expired.
Warning
ISR-safe only when timeout_ticks = NO_WAIT; ISR-unsafe otherwise.

Definition at line 559 of file stk_memory_bytepool.h.

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}

References BLOCK_ALIGN, STK_ASSERT, and TimedAlloc().

Referenced by AllocT(), and TryAllocT().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ TryAlloc()

void * stk::memory::BytePool::TryAlloc ( size_t size)
inline

Non-blocking allocation attempt.

Parameters
[in]sizeRequested payload size in bytes.
Returns
Pointer to an uninitialized block, or nullptr if no suitable free block exists.
Note
ISR-safe (execution time is proportional to the number of blocks scanned).

Definition at line 577 of file stk_memory_bytepool.h.

578{
579 return TimedAlloc(size, NO_WAIT);
580}

References stk::NO_WAIT, and TimedAlloc().

Here is the call graph for this function:

◆ TryAllocT()

template<typename T>
T * stk::memory::BytePool::TryAllocT ( )
inline

Non-blocking typed allocation attempt.

Returns
Typed pointer, or nullptr if no suitable free block exists.
Note
ISR-safe (see TryAlloc()).

Definition at line 583 of file stk_memory_bytepool.h.

584{
585 return TimedAllocT<T>(NO_WAIT);
586}

References stk::NO_WAIT, and TimedAllocT().

Here is the call graph for this function:

Member Data Documentation

◆ BLOCK_ALIGN

const size_t stk::memory::BytePool::BLOCK_ALIGN = 8U
staticprivate

Payload alignment and block size granularity in bytes.

Definition at line 322 of file stk_memory_bytepool.h.

Referenced by AlignSize(), Free(), Init(), IsSane(), and TimedAllocT().

◆ HEADER_SIZE

const size_t stk::memory::BytePool::HEADER_SIZE = 8U
static

Size of the per-block header in bytes.

Every allocation consumes HEADER_SIZE bytes of the pool in addition to its (aligned) payload.

Definition at line 112 of file stk_memory_bytepool.h.

Referenced by AccountRun(), Free(), GetMaxRequest(), Init(), and RequiredSize().

◆ m_cv

sync::ConditionVariable stk::memory::BytePool::m_cv
private

signalled by Free() to wake tasks blocked in TimedAlloc()

Definition at line 381 of file stk_memory_bytepool.h.

Referenced by BytePool(), BytePool(), Free(), and TimedAllocEx().

◆ m_end

BlockHeader* stk::memory::BytePool::m_end
private

one past the last block (aligned), nullptr if the pool is invalid

Definition at line 380 of file stk_memory_bytepool.h.

Referenced by BytePool(), BytePool(), Coalesce(), FindFree(), Free(), GetInfo(), GetSize(), Init(), IsBlockStart(), IsSane(), and ~BytePool().

◆ m_first

BlockHeader* stk::memory::BytePool::m_first
private

first block header (aligned), nullptr if the pool is invalid

Definition at line 379 of file stk_memory_bytepool.h.

Referenced by BytePool(), BytePool(), FindFree(), Free(), GetInfo(), GetSize(), Init(), IsBlockStart(), IsStorageValid(), and ~BytePool().

◆ m_storage

uint8_t* stk::memory::BytePool::m_storage
private

raw storage as supplied or allocated (before alignment)

Definition at line 378 of file stk_memory_bytepool.h.

Referenced by BytePool(), BytePool(), Init(), and ~BytePool().

◆ m_storage_owned

bool stk::memory::BytePool::m_storage_owned
private

true -> storage is heap-allocated; free in destructor

Definition at line 383 of file stk_memory_bytepool.h.

Referenced by BytePool(), BytePool(), and ~BytePool().

◆ m_storage_size

size_t stk::memory::BytePool::m_storage_size
private

size of the raw storage in bytes

Definition at line 382 of file stk_memory_bytepool.h.

Referenced by BytePool(), BytePool(), Init(), and ~BytePool().

◆ MIN_BLOCK_SIZE

const size_t stk::memory::BytePool::MIN_BLOCK_SIZE = (HEADER_SIZE + BLOCK_ALIGN)
staticprivate

Smallest block: a header plus the minimum payload (BLOCK_ALIGN).

Also the smallest remainder worth splitting off.

Definition at line 327 of file stk_memory_bytepool.h.

Referenced by BytePool(), BytePool(), FindFree(), GetMaxRequest(), Init(), and IsSane().

◆ POOL_SIZE_MAX

const size_t stk::memory::BytePool::POOL_SIZE_MAX = static_cast<size_t>(0xFFFFFFF8UL)
staticprivate

Largest managed area: block sizes are stored as 32-bit values.

Definition at line 331 of file stk_memory_bytepool.h.

Referenced by Init().

◆ STATE_FREE

const uint32_t stk::memory::BytePool::STATE_FREE = 0x46524545UL
staticprivate

"FREE"

Definition at line 333 of file stk_memory_bytepool.h.

Referenced by Coalesce(), FindFree(), Free(), GetInfo(), Init(), and IsSane().

◆ STATE_NONE

const uint32_t stk::memory::BytePool::STATE_NONE = 0x00000000UL
staticprivate

stale header absorbed by a merge

Definition at line 335 of file stk_memory_bytepool.h.

Referenced by Coalesce().

◆ STATE_USED

const uint32_t stk::memory::BytePool::STATE_USED = 0x55534544UL
staticprivate

"USED"

Definition at line 334 of file stk_memory_bytepool.h.

Referenced by FindFree(), Free(), and IsSane().


The documentation for this class was generated from the following file: