diff --git a/include/ucore/spool.h b/include/ucore/spool.h new file mode 100644 index 0000000..4c9a2f4 --- /dev/null +++ b/include/ucore/spool.h @@ -0,0 +1,60 @@ +#ifndef UC_SPOOL_H_ +#define UC_SPOOL_H + +#include +#include +#ifdef __cplusplus +extern "C" { +#endif + +/** @file + * Static pool allocator. + * + * An UCPool allocates fixed size memory chunks from a provided buffer, + * and memory allocations can be free'd in any order. + * + * The backing array should be suitable aligned to the types + * that will be allocated from it. Use .e.g + * @code + * _Alignas(16) char mem[1024]; + * @endcode + * as the backing buffer. + * +*/ +typedef struct UCPool UCPool; +struct UCPool { + unsigned char *free_list; // intrusive free list + size_t block_size; // aligned size of each block + size_t capacity; // number of blocks + size_t align; // alignment + unsigned char *start; // start of user buffer + unsigned char *end; // one past end of user buffer +}; + +/** Initialize the pool. + * + * Block size will be rounded up by alignment, or to size of a pointer + * + * @param buffer backing buffer for the pool + * @param buffer_size size of @buffer + * @param block_size size of each allocation. + * @param alignment alignment of each block (must be power of 2) + */ +void uc_pool_init(UCPool *pool, + void *buffer, + size_t buffer_size, + size_t block_size, + size_t alignment); + +// Allocate a block from the pool. Returns NULL if no more space +void *uc_pool_alloc(UCPool *pool); + +// Free @p . Returns memory to the pool +void uc_pool_free(UCPool *pool, void *p); +void uc_pool_reset(UCPool *pool); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/spool.c b/src/spool.c new file mode 100644 index 0000000..de20831 --- /dev/null +++ b/src/spool.c @@ -0,0 +1,111 @@ +#include +#include "ucore/spool.h" +#include "ucore/utils.h" +#include + +void uc_pool_init(UCPool *pool, void *buffer, size_t buffer_size, size_t block_size, size_t align) +{ + assert(align > 0); + assert((align & (align - 1)) == 0); // power of 2 + + + // enforce minimum for storing free list pointers + + if (align < _Alignof(unsigned char *)) { + align = _Alignof(unsigned char *); + } + + if (block_size < sizeof(unsigned char *)) { + block_size = sizeof(unsigned char *); + } + // ensure initial alignment + size_t stride = UC_ALIGN(block_size, align); + + // align first usable block + uintptr_t start_addr = (uintptr_t)buffer; + uintptr_t first_addr = UC_ALIGN(start_addr, align); + size_t padding = first_addr - start_addr; + + if (padding + stride > buffer_size) { + pool->capacity = 0; + pool->free_list = NULL; + } else { + size_t usable = buffer_size - padding; + pool->capacity = usable / stride; + + // build intrusive free list + unsigned char *p = (unsigned char *)first_addr; + for (size_t i = 0; i < pool->capacity - 1; ++i) { + *(unsigned char **)p = p + stride; + p += stride; + } + *(unsigned char **)p = NULL; // last element + pool->free_list = (unsigned char *)first_addr; + } + + pool->block_size = stride; + pool->align = align; + pool->start = (unsigned char *)buffer; + pool->end = (unsigned char *)buffer + buffer_size; +} + + +void *uc_pool_alloc(UCPool *pool) +{ + if (!pool->free_list) { + return NULL; + } + unsigned char *block = pool->free_list; + pool->free_list = *(unsigned char **)block; + +#ifdef DEBUG + memset(block, 0xCB, pool->block_size); +#endif + return block; +} +#include + +void uc_pool_free(UCPool *pool, void *p) +{ + if (p == NULL) { + return; + } +#ifdef DEBUG + assert((unsigned char *)p >= pool->start && (unsigned char*)p < pool->end); + assert(((uintptr_t)p - (uintptr_t)pool->start) % pool->block_size == 0); + + // double-free detection + for (unsigned char *f = pool->free_list; f; f = *(unsigned char**)f) { + if (f == p) { + assert(0 && "Double free detected in pool!"); + return; + } + } + + memset(p, 0xDD, pool->block_size); +#endif + // When not in use, store pointer to next free element inline in the block + *(unsigned char **)p = pool->free_list; + pool->free_list = p; +} + + +void uc_pool_reset(UCPool *pool) +{ + if (pool->capacity == 0) + return; + + uintptr_t start_addr = (uintptr_t)pool->start; + uintptr_t first_addr = UC_ALIGN(start_addr, pool->align); + + unsigned char *p = (unsigned char *)first_addr; + // build intrusive free list + for (size_t i = 0; i < pool->capacity - 1; ++i) { + *(unsigned char **)p = p + pool->block_size; + p += pool->block_size; + } + + *(unsigned char **)p = NULL; // last element + + pool->free_list = (unsigned char *)first_addr; +}