Files
moslab-code/src/l4/pkg/l4re-core/cxx/lib/tl/include/list_alloc
2025-09-12 15:55:45 +02:00

525 lines
13 KiB
C++

// vim:set ft=cpp: -*- Mode: C++ -*-
/*
* (c) 2008-2009 Alexander Warg <warg@os.inf.tu-dresden.de>,
* Torsten Frenzel <frenzel@os.inf.tu-dresden.de>
* economic rights: Technische Universität Dresden (Germany)
*
* License: see LICENSE.spdx (in this directory or the directories above)
*/
#pragma once
#include <l4/cxx/arith>
#include <l4/cxx/minmax>
#include <l4/sys/consts.h>
namespace cxx {
/**
* Standard list-based allocator.
*/
class List_alloc
{
private:
friend class List_alloc_sanity_guard;
struct Mem_block
{
Mem_block *next;
unsigned long size;
};
Mem_block *_first;
inline void check_overlap(void *, unsigned long );
inline void sanity_check_list(char const *, char const *);
inline void merge();
public:
/**
* Initializes an empty list allocator.
*
* \note To initialize the allocator with available memory
* use the #free() function.
*/
List_alloc() : _first(0) {}
/**
* Return a free memory block to the allocator.
*
* \param block Pointer to memory block.
* \param size Size of memory block.
* \param initial_free Set to true for putting fresh memory
* to the allocator. This will enforce alignment on that
* memory.
*
* \pre `block` must not be NULL.
* \pre `2 * sizeof(void *)` <= `size` <= `~0UL - 32`.
*/
inline void free(void *block, unsigned long size, bool initial_free = false);
/**
* Allocate a memory block.
*
* \param size Size of the memory block.
* \param align Alignment constraint.
* \param lower Lower bound of the physical region the memory block should be
* allocated from.
* \param upper Upper bound of the physical region the memory block should be
* allocated from, value is inclusive.
*
* \return Pointer to memory block
*
* \pre 0 < `size` <= `~0UL - 32`.
*/
inline void *alloc(unsigned long size, unsigned long align,
unsigned long lower = 0, unsigned long upper = ~0UL);
/**
* Allocate a memory block of `min` <= size <= `max`.
*
* \param min Minimal size to allocate (in bytes).
* \param[in,out] max Maximum size to allocate (in bytes). The actual
* allocated size is returned here.
* \param align Alignment constraint.
* \param granularity Granularity to use for the allocation (power
* of 2).
* \param lower Lower bound of the physical region the memory
* block should be allocated from.
* \param upper Upper bound of the physical region the memory
* block should be allocated from, value is
* inclusive.
*
* \return Pointer to memory block
*
* \pre 0 < `min` <= `~0UL - 32`.
* \pre 0 < `max`.
*/
inline void *alloc_max(unsigned long min, unsigned long *max,
unsigned long align, unsigned granularity,
unsigned long lower = 0, unsigned long upper = ~0UL);
/**
* Get the amount of available memory.
*
* \return Available memory in bytes
*/
inline unsigned long avail();
template <typename DBG>
void dump_free_list(DBG &out);
};
#if !defined (CXX_LIST_ALLOC_SANITY)
class List_alloc_sanity_guard
{
public:
List_alloc_sanity_guard(List_alloc *, char const *)
{}
};
void
List_alloc::check_overlap(void *, unsigned long )
{}
void
List_alloc::sanity_check_list(char const *, char const *)
{}
#else
class List_alloc_sanity_guard
{
private:
List_alloc *a;
char const *func;
public:
List_alloc_sanity_guard(List_alloc *a, char const *func)
: a(a), func(func)
{ a->sanity_check_list(func, "entry"); }
~List_alloc_sanity_guard()
{ a->sanity_check_list(func, "exit"); }
};
void
List_alloc::check_overlap(void *b, unsigned long s)
{
unsigned long const mb_align = (1UL << arith::Ld<sizeof(Mem_block)>::value) - 1;
if ((unsigned long)b & mb_align)
{
L4::cerr << "List_alloc(FATAL): trying to free unaligned memory: "
<< b << " align=" << arith::Ld<sizeof(Mem_block)>::value << "\n";
}
Mem_block *c = _first;
for (;c ; c = c->next)
{
unsigned long x_s = (unsigned long)b;
unsigned long x_e = x_s + s;
unsigned long b_s = (unsigned long)c;
unsigned long b_e = b_s + c->size;
if ((x_s >= b_s && x_s < b_e)
|| (x_e > b_s && x_e <= b_e)
|| (b_s >= x_s && b_s < x_e)
|| (b_e > x_s && b_e <= x_e))
{
L4::cerr << "List_alloc(FATAL): trying to free memory that "
"is already free: \n ["
<< (void*)x_s << '-' << (void*)x_e << ") overlaps ["
<< (void*)b_s << '-' << (void*)b_e << ")\n";
}
}
}
void
List_alloc::sanity_check_list(char const *func, char const *info)
{
Mem_block *c = _first;
for (;c ; c = c->next)
{
if (c->next)
{
if (c >= c->next)
{
L4::cerr << "List_alloc(FATAL): " << func << '(' << info
<< "): list order violation\n";
}
if (((unsigned long)c) + c->size > (unsigned long)c->next)
{
L4::cerr << "List_alloc(FATAL): " << func << '(' << info
<< "): list order violation\n";
}
}
}
}
#endif
void
List_alloc::merge()
{
List_alloc_sanity_guard __attribute__((unused)) guard(this, __func__);
Mem_block *c = _first;
while (c && c->next)
{
unsigned long f_start = reinterpret_cast<unsigned long>(c);
unsigned long f_end = f_start + c->size;
unsigned long n_start = reinterpret_cast<unsigned long>(c->next);
if (f_end == n_start)
{
c->size += c->next->size;
c->next = c->next->next;
continue;
}
c = c->next;
}
}
void
List_alloc::free(void *block, unsigned long size, bool initial_free)
{
List_alloc_sanity_guard __attribute__((unused)) guard(this, __func__);
unsigned long const mb_align = (1UL << arith::Ld<sizeof(Mem_block)>::value) - 1;
if (initial_free)
{
// enforce alignment constraint on initial memory
unsigned long nblock = (reinterpret_cast<unsigned long>(block) + mb_align)
& ~mb_align;
size = (size - (nblock - reinterpret_cast<unsigned long>(block)))
& ~mb_align;
block = reinterpret_cast<void*>(nblock);
}
else
// blow up size to the minimum aligned size
size = (size + mb_align) & ~mb_align;
check_overlap(block, size);
Mem_block **c = &_first;
Mem_block *next = 0;
if (*c)
{
while (*c && *c < block)
c = &(*c)->next;
next = *c;
}
*c = reinterpret_cast<Mem_block*>(block);
(*c)->next = next;
(*c)->size = size;
merge();
}
void *
List_alloc::alloc_max(unsigned long min, unsigned long *max, unsigned long align,
unsigned granularity, unsigned long lower,
unsigned long upper)
{
List_alloc_sanity_guard __attribute__((unused)) guard(this, __func__);
unsigned char const mb_bits = arith::Ld<sizeof(Mem_block)>::value;
unsigned long const mb_align = (1UL << mb_bits) - 1;
// blow minimum up to at least the minimum aligned size of a Mem_block
min = l4_round_size(min, mb_bits);
// truncate maximum to at least the size of a Mem_block
*max = l4_trunc_size(*max, mb_bits);
// truncate maximum size according to granularity
*max = *max & ~(granularity - 1UL);
if (min > *max)
return 0;
unsigned long almask = align ? (align - 1UL) : 0;
// minimum alignment is given by the size of a Mem_block
if (almask < mb_align)
almask = mb_align;
Mem_block **c = &_first;
Mem_block **fit = 0;
unsigned long max_fit = 0;
unsigned long a_lower = (lower + almask) & ~almask;
for (; *c; c = &(*c)->next)
{
// address of free memory block
unsigned long n_start = reinterpret_cast<unsigned long>(*c);
// block too small, next
// XXX: maybe we can skip this and just do the test below
if ((*c)->size < min)
continue;
// block outside region, next
if (upper < n_start || a_lower > n_start + (*c)->size)
continue;
// aligned start address within the free block
unsigned long a_start = (n_start + almask) & ~almask;
// check if aligned start address is behind the block, next
if (a_start - n_start >= (*c)->size)
continue;
a_start = a_start < a_lower ? a_lower : a_start;
// end address would overflow, next
if (min > ~0UL - a_start)
continue;
// block outside region, next
if (a_start + min - 1UL > upper)
continue;
// remaining size after subtracting the padding for the alignment
unsigned long r_size = (*c)->size - a_start + n_start;
// upper limit can limit maximum size
if (a_start + r_size - 1UL > upper)
r_size = upper - a_start + 1UL;
// round down according to granularity
r_size &= ~(granularity - 1UL);
// block too small
if (r_size < min)
continue;
if (r_size >= *max)
{
fit = c;
max_fit = *max;
break;
}
if (r_size > max_fit)
{
max_fit = r_size;
fit = c;
}
}
if (fit)
{
unsigned long n_start = reinterpret_cast<unsigned long>(*fit);
unsigned long a_lower = (lower + almask) & ~almask;
unsigned long a_start = (n_start + almask) & ~almask;
a_start = a_start < a_lower ? a_lower : a_start;
unsigned long r_size = (*fit)->size - a_start + n_start;
if (a_start > n_start)
{
(*fit)->size -= r_size;
fit = &(*fit)->next;
}
else
*fit = (*fit)->next;
*max = max_fit;
if (r_size == max_fit)
return reinterpret_cast<void *>(a_start);
Mem_block *m = reinterpret_cast<Mem_block*>(a_start + max_fit);
m->next = *fit;
m->size = r_size - max_fit;
*fit = m;
return reinterpret_cast<void *>(a_start);
}
return 0;
}
void *
List_alloc::alloc(unsigned long size, unsigned long align, unsigned long lower,
unsigned long upper)
{
List_alloc_sanity_guard __attribute__((unused)) guard(this, __func__);
unsigned long const mb_align
= (1UL << arith::Ld<sizeof(Mem_block)>::value) - 1;
// blow up size to the minimum aligned size
size = (size + mb_align) & ~mb_align;
unsigned long almask = align ? (align - 1UL) : 0;
// minimum alignment is given by the size of a Mem_block
if (almask < mb_align)
almask = mb_align;
Mem_block **c = &_first;
unsigned long a_lower = (lower + almask) & ~almask;
for (; *c; c=&(*c)->next)
{
// address of free memory block
unsigned long n_start = reinterpret_cast<unsigned long>(*c);
// block too small, next
// XXX: maybe we can skip this and just do the test below
if ((*c)->size < size)
continue;
// block outside region, next
if (upper < n_start || a_lower > n_start + (*c)->size)
continue;
// aligned start address within the free block
unsigned long a_start = (n_start + almask) & ~almask;
// block too small after alignment, next
if (a_start - n_start >= (*c)->size)
continue;
a_start = a_start < a_lower ? a_lower : a_start;
// end address would overflow, next
if (size > ~0UL - a_start)
continue;
// block outside region, next
if (a_start + size - 1UL > upper)
continue;
// remaining size after subtracting the padding
// for the alignment
unsigned long r_size = (*c)->size - a_start + n_start;
// block too small
if (r_size < size)
continue;
if (a_start > n_start)
{
// have free space before the allocated block
// shrink the block and set c to the next pointer of that
// block
(*c)->size -= r_size;
c = &(*c)->next;
}
else
// drop the block, c remains the next pointer of the
// previous block
*c = (*c)->next;
// allocated the whole remaining space
if (r_size == size)
return reinterpret_cast<void*>(a_start);
// add a new free block behind the allocated block
Mem_block *m = reinterpret_cast<Mem_block*>(a_start + size);
m->next = *c;
m->size = r_size - size;
*c = m;
return reinterpret_cast<void *>(a_start);
}
return 0;
}
unsigned long
List_alloc::avail()
{
List_alloc_sanity_guard __attribute__((unused)) guard(this, __FUNCTION__);
Mem_block *c = _first;
unsigned long a = 0;
while (c)
{
a += c->size;
c = c->next;
}
return a;
}
template <typename DBG>
void
List_alloc::dump_free_list(DBG &out)
{
Mem_block *c = _first;
while (c)
{
unsigned sz;
const char *unit;
if (c->size < 1024)
{
sz = c->size;
unit = "Byte";
}
else if (c->size < 1 << 20)
{
sz = c->size >> 10;
unit = "KB";
}
else
{
sz = c->size >> 20;
unit = "MB";
}
out.printf("%12p - %12p (%u %s)\n", c,
reinterpret_cast<char *>(c) + c->size - 1, sz, unit);
c = c->next;
}
}
}