slab: simple test for custom alignment and unaligned size

This commit is contained in:
uosfz 2026-05-28 17:28:13 +02:00
parent d256e462f8
commit c8533c8387
Signed by: uosfz
SSH key fingerprint: SHA256:FlktuluyhTQg3jHZNLKwxOOC5hbfrUXM0tz3IA3lGJo
4 changed files with 88 additions and 8 deletions

View file

@ -105,6 +105,12 @@ build/tests/test_tlsf: tests/test_tlsf.c $(TEST_TLSF_DEPS) build/tests
@"$(CC)" $(CFLAGS) -c -o $@.o $< $(CPPFLAGS)
@"$(CC)" -o $@ $@.o $(TEST_TLSF_DEPS)
TEST_SLAB_DEPS=build/src/test.o build/src/x86_64/address.o build/src/slab.o
build/tests/test_slab: tests/test_slab.c $(TEST_SLAB_DEPS) build/tests
@printf "CC %s\n" $@
@"$(CC)" $(CFLAGS) -c -o $@.o $< $(CPPFLAGS)
@"$(CC)" -o $@ $@.o $(TEST_SLAB_DEPS)
build/tests:
mkdir -p $@

View file

@ -11,6 +11,7 @@
/// Usually this is allocated statically somewhere.
struct slab_cache {
size_t obj_size;
size_t obj_size_align8;
size_t obj_align;
size_t obj_align_shift;
size_t num_slots_per_slab;

View file

@ -23,7 +23,8 @@ void slab_cache_init_with_align(
ASSERT(obj_align > 0);
ASSERT(IS_POWER_OF_2(obj_align));
cache->obj_size = obj_size,
cache->obj_size = obj_size;
cache->obj_size_align8 = ROUND_UP_8(obj_size);
cache->obj_align = obj_align;
size_t obj_align_shift = LOG2(obj_align);
cache->obj_align_shift = obj_align_shift;
@ -69,18 +70,17 @@ bool slab_cache_new_slab(struct slab_cache *cache) {
// build the free-slot stack in the bytes after the header
raw += sizeof(struct slab);
size_t obj_size_align8 = ROUND_UP_8(cache->obj_size);
struct free_stack *head = NULL;
for (size_t i = 0; i < cache->num_slots_per_slab; i++) {
raw = (uint8_t*)ROUND_UP(raw, cache->obj_align_shift);
uint8_t *end_of_obj = raw + obj_size_align8 + sizeof(struct free_stack);
uint8_t *end_of_obj = raw + cache->obj_size_align8 + sizeof(struct free_stack);
ASSERT(end_of_obj < page_end);
if (cache->constructor) {
cache->constructor(raw);
}
raw += obj_size_align8;
raw += cache->obj_size_align8;
struct free_stack *stack = (struct free_stack *)raw;
stack->magic = SLOT_MAGIC_FREE;
stack->next = head;
@ -111,7 +111,7 @@ static void *slab_obj_alloc(struct slab *slab) {
// move to beginning of object
ASSERT(free->magic == SLOT_MAGIC_FREE);
free->magic = SLOT_MAGIC_OCCUPIED;
return (char*)free - slab->cache->obj_size;
return (char*)free - slab->cache->obj_size_align8;
}
bool slab_allocation_asserted;
@ -195,7 +195,7 @@ static void slab_obj_free(struct slab *slab, void *obj) {
ASSERT(slab->free_count < slab->cache->num_slots_per_slab);
ASSERT(PAGE_BASE(obj) == (void*)slab);
struct free_stack *node = (struct free_stack *)((char*)obj + slab->cache->obj_size);
struct free_stack *node = (struct free_stack *)((char*)obj + slab->cache->obj_size_align8);
// check if `obj` is actually a valid object pointer
ASSERT((char*)node + sizeof(struct free_stack) <= (char*)slab + PAGE_SIZE);
@ -219,11 +219,10 @@ static void slab_cache_return_slab_to_kernel(
uint8_t * const page_start = raw;
uint8_t * const page_end = page_start + PG_SIZE;
raw += sizeof(struct slab);
size_t obj_size_align8 = ROUND_UP_8(cache->obj_size);
for (size_t i = 0; i < cache->num_slots_per_slab; i++) {
raw = (uint8_t*)ROUND_UP(raw, cache->obj_align_shift);
uint8_t *end_of_obj = raw + obj_size_align8 + sizeof(struct free_stack);
uint8_t *end_of_obj = raw + cache->obj_size_align8 + sizeof(struct free_stack);
ASSERT(end_of_obj < page_end);
if (cache->destructor) {
cache->destructor(raw);

74
tests/test_slab.c Normal file
View file

@ -0,0 +1,74 @@
#define _POSIX_C_SOURCE 200112L
#include <stdlib.h>
#include "slab.h"
#include "test.h"
#include "address.h"
#include "ram.h"
#include "string.h"
extern uint64_t identity_mapping_start;
bool ram_alloc_frame(struct ppn *ppn_out, enum frame_size size) {
TEST_ASSERT(size == RAM_PAGE_NORMAL);
void *addr;
TEST_ASSERT(posix_memalign(&addr, 4096, 4096) == 0);
*ppn_out = ppn_from_aligned_pa(pa_from_value((uint64_t)addr));
return true;
}
void ram_free(struct ppn ppn) {
free(ppn_to_pointer(ppn));
}
size_t num_ctor_calls = 0;
size_t num_dtor_calls = 0;
void constructor(void *obj) {
TEST_ASSERT(((uint64_t)obj & 0x3f) == 0);
uint8_t *p = obj;
for (int i = 0; i < 100; i++) {
p[i] = i;
}
num_ctor_calls++;
}
void destructor(void *obj) {
TEST_ASSERT(((uint64_t)obj & 0x3f) == 0);
num_dtor_calls++;
}
int main() {
identity_mapping_start = 0;
struct slab_cache cache;
TEST_SHOULD_PANIC(slab_cache_alloc(&cache));
slab_cache_init_with_align(&cache, 100, 64, constructor, destructor);
uint8_t *ptr0 = slab_cache_alloc(&cache);
TEST_ASSERT(ptr0 != NULL);
TEST_ASSERT(((uint64_t)ptr0 & 0x3f) == 0);
uint8_t *ptr1 = slab_cache_alloc(&cache);
TEST_ASSERT(ptr1 != NULL);
TEST_ASSERT(((uint64_t)ptr1 & 0x3f) == 0);
for (int i = 0; i < 100; i++) {
TEST_ASSERT(ptr0[i] == i);
TEST_ASSERT(ptr1[i] == i);
}
memset(ptr0, 42, 100);
memset(ptr1, 43, 100);
for (int i = 0; i < 100; i++) {
TEST_ASSERT(ptr0[i] == 42);
TEST_ASSERT(ptr1[i] == 43);
}
slab_cache_free(ptr0);
slab_cache_free(ptr1);
slab_cache_destroy(&cache);
TEST_ASSERT(num_ctor_calls > 0 && num_ctor_calls == num_dtor_calls);
}
// TODO test where we write past object and then expect panic on free