diff --git a/CMakeLists.txt b/CMakeLists.txt index e55f145d5..430271f77 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -299,6 +299,7 @@ set(SRCS src/md5/wrap.c src/mem/mem.c + src/mem/mem_pool.c src/mem/secure.c src/mod/mod.c diff --git a/include/re_mem.h b/include/re_mem.h index 70b135df5..3ce71ec24 100644 --- a/include/re_mem.h +++ b/include/re_mem.h @@ -39,3 +39,15 @@ int mem_get_stat(struct memstat *mstat); /* Secure memory functions */ int mem_seccmp(const uint8_t *s1, const uint8_t *s2, size_t n); void mem_secclean(void *data, size_t size); + + +/* Mem Pool */ +struct mem_pool; +struct mem_pool_entry; +int mem_pool_alloc(struct mem_pool **poolp, size_t nmemb, size_t membsize, + mem_destroy_h *dh); +int mem_pool_extend(struct mem_pool *pool, size_t num); +struct mem_pool_entry *mem_pool_borrow(struct mem_pool *pool); +void *mem_pool_release(struct mem_pool *pool, + const struct mem_pool_entry *entry); +void *mem_pool_member(const struct mem_pool_entry *entry); diff --git a/src/mem/mem_pool.c b/src/mem/mem_pool.c new file mode 100644 index 000000000..3ac6a61bf --- /dev/null +++ b/src/mem/mem_pool.c @@ -0,0 +1,235 @@ +/** + * @file mem_pool.c Pre-Allocated Memory pool management + * + * Copyright (C) 2025 Sebastian Reimers + */ + +#include + +#include +#include +#include +#include + + +#define DEBUG_MODULE "mem_pool" +#define DEBUG_LEVEL 5 +#include + + +struct mem_pool { + RE_ATOMIC size_t next_free; + size_t nmemb; + size_t membsize; + struct mem_pool_entry *objs; + mem_destroy_h *membdh; +}; + +struct mem_pool_entry { + RE_ATOMIC bool used; + void *member; +}; + + +static void mem_pool_destroy(void *data) +{ + struct mem_pool *p = data; + + for (size_t i = 0; i < p->nmemb; i++) { + mem_deref(p->objs[i].member); + } + + mem_deref(p->objs); +} + + +/** + * @brief Allocate a memory pool. + * + * This function initializes a memory pool with a specified number of elements, + * each of a given size. Optionally, a destructor callback can be provided + * to handle cleanup when a member is released or pool is destroyed. + * + * @param poolp Pointer to the memory pool pointer to be initialized. + * @param nmemb Number of elements to allocate in the pool. + * @param membsize Size of each element in the pool. + * @param dh Optional destructor callback for pool cleanup (can be + * NULL). + * + * @return 0 for success, otherwise error code + */ +int mem_pool_alloc(struct mem_pool **poolp, size_t nmemb, size_t membsize, + mem_destroy_h *dh) +{ + int err; + + if (!poolp || !nmemb || !membsize) + return EINVAL; + + struct mem_pool *p = mem_zalloc(sizeof(struct mem_pool), NULL); + if (!p) + return ENOMEM; + + p->nmemb = nmemb; + p->membsize = membsize; + p->membdh = dh; + p->objs = mem_zalloc(nmemb * sizeof(struct mem_pool_entry), NULL); + if (!p->objs) { + err = ENOMEM; + goto error; + } + + mem_destructor(p, mem_pool_destroy); + + for (size_t i = 0; i < nmemb; i++) { + p->objs[i].member = mem_zalloc(membsize, dh); + if (!p->objs[i].member) { + err = ENOMEM; + goto error; + } + } + + *poolp = p; + + return 0; + +error: + mem_deref(p); + return err; +} + + +/** + * @brief Extend an existing memory pool. + * + * Adds additional elements to an existing memory pool. + * + * @note This function is not thread-safe. Concurrent access to the memory + * pool while extending it may result in undefined behavior. Ensure that + * proper synchronization mechanisms are in place if this function is used + * in a multithreaded context. + * + * @param pool Pointer to the memory pool to extend. + * @param num Number of additional elements to add to the pool. + * + * @return 0 for success, otherwise error code + */ +int mem_pool_extend(struct mem_pool *pool, size_t num) +{ + if (!pool || !num) + return EINVAL; + + size_t nmemb = pool->nmemb + num; + + struct mem_pool_entry *objs; + objs = mem_zalloc(nmemb * sizeof(struct mem_pool_entry), NULL); + if (!objs) + return ENOMEM; + + /* Copy old members */ + size_t i = 0; + for (; i < pool->nmemb; i++) { + objs[i].member = pool->objs[i].member; + re_atomic_rlx_set(&objs[i].used, + re_atomic_rlx(&pool->objs[i].used)); + } + + /* Allocate new members */ + for (; i < nmemb; i++) { + objs[i].member = mem_zalloc(pool->membsize, NULL); + if (!objs[i].member) { + mem_deref(objs); + return ENOMEM; + } + } + + mem_deref(pool->objs); + pool->objs = objs; + pool->nmemb = nmemb; + + return 0; +} + + +/** + * @brief Borrow an entry from the memory pool. + * + * Retrieves an unused entry from the memory pool for temporary use. + * + * @param pool Pointer to the memory pool. + * + * @return Pointer to a memory pool entry, or NULL if no entries are available. + */ +struct mem_pool_entry *mem_pool_borrow(struct mem_pool *pool) +{ + if (!pool) + return NULL; + + for (size_t i = re_atomic_acq(&pool->next_free); i < pool->nmemb; + i++) { + if (!re_atomic_acq(&pool->objs[i].used)) { + re_atomic_rls_set(&pool->objs[i].used, true); + re_atomic_rls_set(&pool->next_free, i + 1); + return &(pool->objs[i]); + } + } + + for (size_t i = 0; i < pool->nmemb; i++) { + if (!re_atomic_acq(&pool->objs[i].used)) { + re_atomic_rls_set(&pool->objs[i].used, true); + re_atomic_rls_set(&pool->next_free, i + 1); + return &(pool->objs[i]); + } + } + + return NULL; +} + + +/** + * @brief Release a borrowed entry back to the memory pool. + * + * Returns a previously borrowed memory pool entry back to the pool. + * When the entry is released, the member destructor callback (if provided) + * is called to perform any necessary cleanup. Additionally, the memory + * associated with the entry is re-initialized to zero to ensure a clean state + * for future use. + * + * @param pool Pointer to the memory pool. + * @param entry Pointer to the memory pool entry to release. + * + * @return Always NULL + */ +void *mem_pool_release(struct mem_pool *pool, + const struct mem_pool_entry *entry) +{ + if (!pool || !entry) + return NULL; + + size_t i = ((uintptr_t)entry - (uintptr_t)pool->objs) / + sizeof(struct mem_pool_entry); + re_assert(i < pool->nmemb); + re_assert(re_atomic_rlx(&pool->objs[i].used)); + re_atomic_rls_set(&pool->objs[i].used, false); + re_atomic_rls_set(&pool->next_free, i); + + if (pool->membdh) + pool->membdh(pool->objs[i].member); + + memset(pool->objs[i].member, 0, pool->membsize); + + return NULL; +} + + +/** + * Return Pool member + * + * @param entry Pointer to the memory pool entry. + * + * @return Pointer to the data associated with the memory pool entry or NULL + */ +void *mem_pool_member(const struct mem_pool_entry *entry) +{ + return entry ? entry->member : NULL; +} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 8458987b5..90b48a5da 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -102,6 +102,7 @@ set(SRCS mbuf.c md5.c mem.c + mem_pool.c mock/dnssrv.c mock/nat.c mock/sipsrv.c diff --git a/test/mem_pool.c b/test/mem_pool.c new file mode 100644 index 000000000..85a56169e --- /dev/null +++ b/test/mem_pool.c @@ -0,0 +1,62 @@ +/** + * @file mem_pool.c Memory Pool Testcode + * + * Copyright (C) 2025 Sebastian Reimers + */ +#include +#include "test.h" + + +#define DEBUG_MODULE "test_mem_pool" +#define DEBUG_LEVEL 5 +#include + +struct object { + int a; +}; + +enum { + NUM_OBJECTS = 10, +}; + + +int test_mem_pool(void) +{ + int err; + + struct mem_pool *pool = NULL; + err = mem_pool_alloc(&pool, NUM_OBJECTS, sizeof(struct object), NULL); + TEST_ERR(err); + + struct mem_pool_entry *e; + struct object *o; + + for (int i = 0; i < NUM_OBJECTS; i++) { + e = mem_pool_borrow(pool); + TEST_ASSERT(e); + + o = mem_pool_member(e); + TEST_NOT_EQUALS(o->a, i + 1); + + o->a = i + 1; + } + + TEST_ASSERT(!mem_pool_borrow(pool)); + + e = mem_pool_release(pool, e); + e = mem_pool_borrow(pool); + TEST_ASSERT(e); + + TEST_ASSERT(!mem_pool_borrow(pool)); + + err = mem_pool_extend(pool, 1); + TEST_ERR(err); + e = mem_pool_borrow(pool); + TEST_ASSERT(e); + + TEST_ASSERT(!mem_pool_borrow(pool)); + +out: + mem_deref(pool); + return err; +} diff --git a/test/test.c b/test/test.c index 9be0c7bc0..93ac7b26c 100644 --- a/test/test.c +++ b/test/test.c @@ -142,6 +142,7 @@ static const struct test tests[] = { TEST(test_mbuf), TEST(test_md5), TEST(test_mem), + TEST(test_mem_pool), TEST(test_mem_reallocarray), TEST(test_mem_secure), TEST(test_net_if), diff --git a/test/test.h b/test/test.h index ef4f8626c..04f6d9679 100644 --- a/test/test.h +++ b/test/test.h @@ -247,6 +247,7 @@ int test_list_sort(void); int test_mbuf(void); int test_md5(void); int test_mem(void); +int test_mem_pool(void); int test_mem_reallocarray(void); int test_mem_secure(void); int test_mqueue(void);