library: tests: Add function to allocate from end of pool

Add tests for it.
This commit is contained in:
Antonio Niño Díaz 2022-10-27 22:44:05 +01:00
parent 4ec6488068
commit 3b09697a91
3 changed files with 213 additions and 9 deletions

View File

@ -37,8 +37,14 @@ typedef struct {
int NE_AllocInit(NEChunk **first_element, void *start, void *end);
int NE_AllocEnd(NEChunk **first_element);
// Returns NULL on error, or a valid pointer on success.
// Allocates data at the first available space starting from the start of the
// memory pool. Returns NULL on error, or a valid pointer on success.
void *NE_Alloc(NEChunk *first_element, size_t size);
// Allocates data at the first available space starting from the end of the
// memory pool. Returns NULL on error, or a valid pointer on success.
void *NE_AllocFromEnd(NEChunk *first_element, size_t size);
// Returns 0 on success. On error, it returns a negative number.
int NE_Free(NEChunk *first_element, void *pointer);

View File

@ -138,6 +138,100 @@ void *NE_Alloc(NEChunk *first_chunk, size_t size)
return NULL;
}
void *NE_AllocFromEnd(NEChunk *first_chunk, size_t size)
{
if ((first_chunk == NULL) || (size == 0))
return NULL;
// Force sizes multiple of NE_ALLOC_MIN_SIZE
const size_t mask = NE_ALLOC_MIN_SIZE - 1;
if ((size & mask) != 0)
size += NE_ALLOC_MIN_SIZE - (size & mask);
// Find last chunk
NEChunk *this = first_chunk;
while (this->next != NULL)
this = this->next;
// Traverse list from end to beginning
for ( ; this != NULL; this = this->previous)
{
// Skip non-free chunks
if (this->state != NE_STATE_FREE)
continue;
uintptr_t this_start = (uintptr_t)this->start;
uintptr_t this_end = (uintptr_t)this->end;
size_t this_size = this_end - this_start;
// If this chunk doesn't have enough space, simply skip it.
if (this_size < size)
continue;
// If we have exactly the space requested, we're done.
if (this_size == size)
{
this->state = NE_STATE_USED;
return this->start;
}
// If we have more space than requested, split this chunk:
//
// | THIS | NEXT |
// +------------------+------+ Before
// | NOT USED | USED |
//
// | THIS | NEW | NEXT |
// +-----------+------+------+ After
// | NOT USED | USED | USED |
// Get next chunk and create a new one.
NEChunk *new = malloc(sizeof(NEChunk));
NE_AssertPointer(new, "Couldn't allocate chunk metadata.");
NEChunk *next = this->next;
// Flag the new chunk as used
new->state = NE_STATE_USED;
// Update pointers in the linked list
// ----------------------------------
this->next = new;
new->previous = this;
if (next == NULL)
{
new->next = NULL;
}
else
{
new->next = next;
next->previous = new;
// It shouldn't be free because deallocating a chunk should merge it
// with any free chunk next to it.
NE_Assert(next->state != NE_STATE_FREE,
"Possible list corruption");
}
// Update pointers to start and end of this chunk and the new chunk
// ----------------------------------------------------------------
new->end = this->end;
new->start = (void *)(this_end - size);
this->end = new->start;
return new->start;
}
// No more chunks... Not enough free space.
return NULL;
}
int NE_Free(NEChunk *first_chunk, void *pointer)
{
if (first_chunk == NULL)

View File

@ -30,6 +30,7 @@
#define ASSERT(cond) \
if (!(cond)) { \
printf("Line %d\n", __LINE__); \
while (1); \
}
#define ASSERT_MSG(cond, msg) \
@ -78,6 +79,42 @@ void test_alloc_align(void)
POOL_DEINITIALIZE();
}
// Test that the pointer advances as expected when allocating memory from the
// end of the pool, and that chunks that are too small are aligned to 16 bytes.
void test_alloc_from_end_align(void)
{
printf("%s\n", __func__);
POOL_INITIALIZE();
uintptr_t addr = POOL_END_ADDR;
void *ptr;
addr -= 64;
ptr = NE_AllocFromEnd(alloc, 64);
ASSERT(A(ptr) == addr);
addr -= 32;
ptr = NE_AllocFromEnd(alloc, 32);
ASSERT(A(ptr) == addr);
addr -= 16;
ptr = NE_AllocFromEnd(alloc, 16);
ASSERT(A(ptr) == addr);
ASSERT_MSG(16 == NE_ALLOC_MIN_SIZE, "Unexpected NE_ALLOC_MIN_SIZE");
addr -= NE_ALLOC_MIN_SIZE;
ptr = NE_AllocFromEnd(alloc, 8);
ASSERT(A(ptr) == addr);
addr -= NE_ALLOC_MIN_SIZE;
ptr = NE_AllocFromEnd(alloc, 4);
ASSERT(A(ptr) == addr);
POOL_DEINITIALIZE();
}
// Test that a freed chunk can be reused
void test_free(void)
{
@ -104,6 +141,33 @@ void test_free(void)
POOL_DEINITIALIZE();
}
// Test that a freed chunk can be reused
void test_free_from_end(void)
{
printf("%s\n", __func__);
POOL_INITIALIZE();
uintptr_t addr = POOL_END_ADDR;
addr -= 256;
void *ptr1 = NE_AllocFromEnd(alloc, 256);
ASSERT(A(ptr1) == addr);
addr -= 256;
void *ptr2 = NE_AllocFromEnd(alloc, 256);
ASSERT(A(ptr2) == addr);
// Free the first chunk
int ret = NE_Free(alloc, ptr1);
ASSERT(ret == 0);
void *ptr3 = NE_AllocFromEnd(alloc, 256);
ASSERT(A(ptr3) == A(ptr1));
POOL_DEINITIALIZE();
}
// Several tests to lock and unlock chunks
void test_lock_unlock(void)
{
@ -187,6 +251,9 @@ void test_alloc_fail(void)
ptr = NE_Alloc(alloc, 0);
ASSERT(ptr == NULL);
ptr = NE_AllocFromEnd(alloc, 0);
ASSERT(ptr == NULL);
// Try to allocate the maximum size
ptr = NE_Alloc(alloc, POOL_SIZE);
@ -195,11 +262,20 @@ void test_alloc_fail(void)
ret = NE_Free(alloc, ptr);
ASSERT(ret == 0);
ptr = NE_AllocFromEnd(alloc, POOL_SIZE);
ASSERT(A(ptr) == addr);
ret = NE_Free(alloc, ptr);
ASSERT(ret == 0);
// Try to allocate more than the limit
void *fail = NE_Alloc(alloc, POOL_SIZE + 1);
ASSERT(fail == NULL);
fail = NE_AllocFromEnd(alloc, POOL_SIZE + 1);
ASSERT(fail == NULL);
// Fragment the memory pool and try to allocate the remaining space, which
// should fail.
@ -221,6 +297,9 @@ void test_alloc_fail(void)
fail = NE_Alloc(alloc, info.free);
ASSERT(fail == NULL);
fail = NE_AllocFromEnd(alloc, info.free);
ASSERT(fail == NULL);
POOL_DEINITIALIZE();
}
@ -250,8 +329,8 @@ void test_statistics(void)
ASSERT(A(ptr3) == addr);
addr += size;
void *ptr4 = NE_Alloc(alloc, size);
ASSERT(A(ptr4) == addr);
void *ptr4 = NE_AllocFromEnd(alloc, size);
ASSERT(A(ptr4) == POOL_END_ADDR - size);
// Free one of them
@ -317,6 +396,21 @@ int verify_consistency(NEChunk *list, void *start, void *end)
return 0;
}
void print_list(NEChunk *list)
{
if (list == NULL)
{
printf("NULL\n");
return;
}
for ( ; list != NULL; list = list->next)
{
printf("%p-%p (%zu)\n", list->start, list->end,
(size_t)(A(list->end) - A(list->start)));
}
}
// Test that the internal linked list is in the expected state
void test_internal_list_state(void)
{
@ -446,7 +540,7 @@ void test_stress(void)
for (int i = 0; i < NUM_PTRS; i++)
ptr[i] = NULL;
for (int i = 0; i < 300000; i++)
for (int i = 0; i < 500000; i++)
{
unsigned int selected = rand() % NUM_PTRS;
@ -458,7 +552,13 @@ void test_stress(void)
size_t size = (rand() & 0x3FFF) + 1;
void *p = NE_Alloc(alloc, size);
void *p;
if (size & 1)
p = NE_Alloc(alloc, size);
else
p = NE_AllocFromEnd(alloc, size);
ASSERT(p != NULL);
ptr[selected] = p;
@ -471,11 +571,13 @@ void test_stress(void)
ptr[selected] = NULL;
}
// Verify list every now and then
if ((i % 64) == 0)
// Verify consistency of the list
ret = verify_consistency(alloc, POOL_START, POOL_END);
if (ret != 0)
{
ret = verify_consistency(alloc, POOL_START, POOL_END);
ASSERT(ret == 0);
printf("ret = %d", ret);
print_list(alloc);
while (1);
}
}
@ -489,7 +591,9 @@ int main(void)
consoleDemoInit();
test_alloc_align();
test_alloc_from_end_align();
test_free();
test_free_from_end();
test_lock_unlock();
test_alloc_fail();
test_statistics();