blob: 11334843c6000f0f3d7472bd48b136d4964d95d0 [file]
#include <err.h>
#include <kernel/thread.h>
#include <lib/unittest/unittest.h>
#include <malloc.h>
#include <stdatomic.h>
static uintptr_t expected_malloc_alignment(size_t size) {
/* TODO use ffs? */
if (size >= 16) {
return sizeof(void*) * 2;
} else if (size >= 8) {
return 8;
} else if (size >= 4) {
return 4;
} else if (size >= 2) {
return 2;
} else {
return 1;
}
}
TEST(memorytest, malloc_alignment) {
for (int size = 2; size < 256; size++) {
const uintptr_t alignment_mask = expected_malloc_alignment(size) - 1;
void* ptr1 = malloc(size);
void* ptr2 = malloc(size / 2); /* Try to shake up the alignment. */
void* ptr3 = malloc(size);
ASSERT_EQ(0, (uintptr_t)ptr1 & alignment_mask, "size %d / align %ld\n",
size, alignment_mask + 1);
ASSERT_EQ(0, (uintptr_t)ptr3 & alignment_mask, "size %d / align %ld\n",
size, alignment_mask + 1);
free(ptr3);
free(ptr2);
free(ptr1);
}
test_abort:;
}
#define MEMORYTEST_CONCURRENT_ALLOCATION_THREADS 64
struct memorytest_alloc_thread_arg {
size_t alloc_size;
int count;
atomic_int threads_done;
atomic_int chunks_allocated;
atomic_bool go;
};
int memorytest_alloc_thread(void* _arg) {
struct memorytest_alloc_thread_arg* arg = _arg;
void** ptrs;
int i;
int ret;
ptrs = calloc(arg->count, sizeof(*ptrs));
if (!ptrs) {
return ERR_NO_MEMORY;
}
/* Busy-wait until control thread says go. */
while (!atomic_load(&arg->go)) {
}
for (i = 0; i < arg->count; i++) {
ptrs[i] = malloc(arg->alloc_size);
if (!ptrs[i]) {
ret = ERR_NO_MEMORY;
goto err_malloc;
}
atomic_fetch_add(&arg->chunks_allocated, 1);
}
ret = 0;
err_malloc:
atomic_fetch_add(&arg->threads_done, 1);
while (atomic_load(&arg->threads_done) !=
MEMORYTEST_CONCURRENT_ALLOCATION_THREADS) {
thread_sleep(10);
}
while (i-- > 0) {
free(ptrs[i]);
}
free(ptrs);
return ret;
}
TEST(memorytest, concurrent_allocation) {
/*
* Test concurrent allocation by creating many threads. If this test is
* as the first test after boot, it will test the behavior while growing
* the heap. Assuming the heap implemention never shrinks the heap,
* additional test runs will not exercise this path.
*/
struct memorytest_alloc_thread_arg thread_arg = {
.alloc_size = PAGE_SIZE / 4 * 3, /* ~1 page after heap overhead */
.count = 8,
.threads_done = 0,
.chunks_allocated = 0,
.go = false,
};
struct thread* thread[MEMORYTEST_CONCURRENT_ALLOCATION_THREADS];
for (size_t i = 0; i < countof(thread); i++) {
thread[i] = thread_create("memorytest", memorytest_alloc_thread,
&thread_arg, HIGH_PRIORITY - 1,
DEFAULT_STACK_SIZE);
ASSERT_NE(0, thread[i]);
}
for (size_t i = 0; i < countof(thread); i++) {
ASSERT_EQ(0, thread_resume(thread[i]));
}
/* Wait for test threads to start and migrate to other cpus */
thread_sleep(100);
atomic_store(&thread_arg.go, true);
for (size_t i = 0; i < countof(thread); i++) {
int retcode;
ASSERT_EQ(0, thread_join(thread[i], &retcode, INFINITE_TIME));
EXPECT_EQ(0, retcode, "Chunks allocated: %d\n",
atomic_load(&thread_arg.chunks_allocated));
}
test_abort:;
}
PORT_TEST(memorytest, "com.android.kernel.memorytest");