diff --git a/CMakeLists.txt b/CMakeLists.txt index 3f9adc0..7289be8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,6 +31,8 @@ set(SOURCES_COMMON src/keysc/keycodes.c src/keysc/keydev.c src/keysc/keysc.c + src/kmalloc/arena_osheap.c + src/kmalloc/kmalloc.c src/kprint/kprint.c src/kprint/kformat_fp.c src/mmu/mmu.c @@ -47,6 +49,7 @@ set(SOURCES_COMMON src/rtc/rtc_ticks.c src/spu/spu.c src/std/aprint.c + src/std/malloc.c src/std/memcmp.s src/std/memcpy.s src/std/memmove.s diff --git a/include/gint/kmalloc.h b/include/gint/kmalloc.h new file mode 100644 index 0000000..1fa4f98 --- /dev/null +++ b/include/gint/kmalloc.h @@ -0,0 +1,88 @@ +//--- +// gint:kmalloc - gint's memory allocator +//--- + +#ifndef GINT_KMALLOC +#define GINT_KMALLOC + +#include + +//--- +// Standard memory allocation API +//--- + +/* kmalloc(): Allocate memory in one of the available arenas + This function acts like malloc(). The second parameter specifies which arena + to allocate from; when NULL, all default arenas are considered. + + @size Size of requested block + @arena_name Name of arena to allocate in (can be NULL) + Returns address of allocated block, NULL on error. */ +void *kmalloc(size_t size, char const *arena_name); + +/* krealloc(): Reallocate memory + This function acts like realloc(). It only tries to reallocate the block in + the arena where it was previously allocated. Note that if NULL is returned, + the user needs to have a copy of the original address or the memory will + become unreachable. + + @ptr Existing allocated block + @size New requested size for the block + Returns address of reallocated block, NULL on error. */ +void *krealloc(void *ptr, size_t size); + +/* kfree(): Free memory allocated with kalloc() */ +void kfree(void *ptr); + +//--- +// Extension API for new areas and statistics +//--- + +typedef struct { + /* Functions managing the arena. The last argument is the [data] + attribute in this structure. */ + + /* kmalloc() handles size == 0 */ + void * (*malloc)(size_t size, void *data); + /* krealloc() handles ptr == NULL, as well as newsize == 0 */ + void * (*realloc)(void *ptr, size_t newsize, void *data); + /* kfree() handles ptr == NULL*/ + void (*free)(void *ptr, void *data); + + /* Name, should be unique; gint reserves names starting with "_" */ + char const *name; + /* Start and end of arena. This is used to find the proper arena to + free from in kfree(). This cannot be NULL except for the OS heap as + the exact addresses are unknown. */ + void *start, *end; + /* Pointer to arena-provided data, passed to malloc() and free() */ + void *data; + /* Whether to consider this arena when performing default allocations + (kmalloc() with arena_name == NULL) */ + int is_default; + + /* Statistics maintained by kmalloc() */ + struct kmalloc_stats { + int live_blocks; + int peak_live_blocks; + int total_volume; + int total_blocks; + int total_failures; + } stats; + +} kmalloc_arena_t; + +//--- +// Internal API +//--- + +/* kmalloc_init(): Initialize the dynamic allocator */ +void kmalloc_init(void); + +/* kmalloc_add_arena(): Add a new arena to the heap source + Adds a fully-initialized arena to the heap source. The priority of the new + arena compared to other default arenas is not specified. Returns true on + success, false if the maximum number of arenas has been reached. */ +bool kmalloc_add_arena(kmalloc_arena_t *arena); + +#endif /* GINT_KMALLOC */ diff --git a/src/kernel/kernel.c b/src/kernel/kernel.c index fc0b1fd..72dce80 100644 --- a/src/kernel/kernel.c +++ b/src/kernel/kernel.c @@ -8,6 +8,7 @@ #include #include #include +#include #include "cpu.h" #include "vbr.h" @@ -159,6 +160,9 @@ void kinit(void) /* Take control of the VBR and roll! */ drivers_wait(); sys_ctx.VBR = (*cpu_setVBR)(gint_ctx.VBR, drivers_save_and_init, 0); + + /* Initialize memory allocators */ + kmalloc_init(); } /* Due to dire space restrictions on SH3, event codes that are translated to diff --git a/src/kernel/syscalls.S b/src/kernel/syscalls.S index 943d799..67c2356 100644 --- a/src/kernel/syscalls.S +++ b/src/kernel/syscalls.S @@ -12,10 +12,10 @@ .text /* Dynamic allocation */ -.global _malloc -.global _free -.global _calloc -.global _realloc +.global ___malloc +.global ___free +.global ___calloc +.global ___realloc /* Bfile driver */ .global _BFile_Remove @@ -54,13 +54,11 @@ /* Dynamic allocation */ -_malloc: +___malloc: syscall(0x0acd) -_free: +___free: syscall(0x0acc) -_calloc: - syscall(0x0e6b) -_realloc: +___realloc: syscall(0x0e6d) /* BFile driver */ @@ -114,13 +112,11 @@ syscall_table: /* Dynamic allocation */ -_malloc: +___malloc: syscall(0x1f44) -_free: +___free: syscall(0x1f42) -_calloc: - syscall(0x1f40) -_realloc: +___realloc: syscall(0x1f46) /* BFile driver */ diff --git a/src/kmalloc/arena_osheap.c b/src/kmalloc/arena_osheap.c new file mode 100644 index 0000000..b28d650 --- /dev/null +++ b/src/kmalloc/arena_osheap.c @@ -0,0 +1,40 @@ +//--- +// gint:kmalloc:arena_osheap - An arena that uses the OS heap as input +//--- + +#include +#include + +/* Syscalls relating to the OS heap */ +extern void *__malloc(size_t size); +extern void *__realloc(void *ptr, size_t newsize); +extern void __free(void *ptr); + +static void *osheap_malloc(size_t size, GUNUSED void *data) +{ + return __malloc(size); +} + +static void *osheap_realloc(void *ptr, size_t newsize, GUNUSED void *data) +{ + return __realloc(ptr, newsize); +} + +static void osheap_free(void *ptr, GUNUSED void *data) +{ + return __free(ptr); +} + +/* This is a global variable, it's pulled by kmalloc.c. This arena is the only + one allowed to not specify start/end as the values are hard to determine. */ +kmalloc_arena_t kmalloc_arena_osheap = { + .malloc = osheap_malloc, + .realloc = osheap_realloc, + .free = osheap_free, + .name = "_os", + .start = NULL, + .end = NULL, + .data = NULL, + .is_default = 1, + .stats = { 0 }, +}; diff --git a/src/kmalloc/kmalloc.c b/src/kmalloc/kmalloc.c new file mode 100644 index 0000000..74ee075 --- /dev/null +++ b/src/kmalloc/kmalloc.c @@ -0,0 +1,121 @@ +//--- +// gint:kmalloc:kmalloc - Main allocator routines +//--- + +#include +#include +#include + +/* Maximum number of arenas */ +#define KMALLOC_ARENA_MAX 8 + +/* List of arenas in order of consideration */ +static kmalloc_arena_t *arenas[KMALLOC_ARENA_MAX] = { 0 }; + +/* kmalloc_init(): Initialize the dynamic allocator */ +void kmalloc_init(void) +{ + /* Provide the OS heap */ + extern kmalloc_arena_t kmalloc_arena_osheap; + arenas[0 /* KMALLOC_ARENA_MAX - 1 */] = &kmalloc_arena_osheap; +} + +//--- +// Allocation functions +//--- + +/* Find the arena that contains a given block */ +static kmalloc_arena_t *arena_owning(void *ptr) +{ + for(int i = 0; i < KMALLOC_ARENA_MAX; i++) + { + kmalloc_arena_t *a = arenas[i]; + if(!a) continue; + + if((a->start <= ptr && ptr < a->end) || + (a->start == NULL && a->end == NULL)) + return a; + } + return NULL; +} + +/* kmalloc(): Allocate memory in one of the available arenas */ +void *kmalloc(size_t size, char const *name) +{ + if(size == 0) return NULL; + + for(int i = 0; i < KMALLOC_ARENA_MAX; i++) if(arenas[i]) + { + kmalloc_arena_t *a = arenas[i]; + if(name && strcmp(a->name, name)) continue; + if(!name && !a->is_default) continue; + + /* Try to allocate in this arena */ + void *rc = a->malloc(size, a->data); + + /* Maintain statistics */ + struct kmalloc_stats *s = &a->stats; + if(rc) + { + s->live_blocks++; + s->peak_live_blocks = max(s->live_blocks, + s->peak_live_blocks); + s->total_volume += size; + s->total_blocks++; + return rc; + } + else + { + s->total_failures++; + } + } + + return NULL; +} + +/* krealloc(): Reallocate memory */ +void *krealloc(void *ptr, size_t size) +{ + if(!ptr) + { + return kmalloc(size, NULL); + } + if(!size) + { + kfree(ptr); + return NULL; + } + + kmalloc_arena_t *a = arena_owning(ptr); + if(!a) return NULL; + + return a->realloc(ptr, size, a->data); +} + +/* kfree(): Free memory allocated with kalloc() */ +void kfree(void *ptr) +{ + if(!ptr) return; + + /* If this condition fails, then the pointer is invalid */ + kmalloc_arena_t *a = arena_owning(ptr); + if(!a) return; + + a->free(ptr, a->data); + /* Maintain statistics */ + a->stats.live_blocks--; +} + +/* kmalloc_add_arena(): Add a new arena to the heap source */ +bool kmalloc_add_arena(kmalloc_arena_t *arena) +{ + for(int i = 0; i < KMALLOC_ARENA_MAX; i++) + { + if(!arenas[i]) + { + arenas[i] = arena; + return true; + } + } + return false; +} diff --git a/src/std/malloc.c b/src/std/malloc.c new file mode 100644 index 0000000..f7a5286 --- /dev/null +++ b/src/std/malloc.c @@ -0,0 +1,36 @@ +//--- +// gint:std:malloc - Standard memory allocation functions +//--- + +#include +#include + +/* malloc(): Allocate dynamic memory */ +void *malloc(size_t size) +{ + return kmalloc(size, NULL); +} + +/* free(): Free dynamic memory */ +void free(void *ptr) +{ + kfree(ptr); +} + +/* calloc(): Allocate and initialize dynamic memory */ +void *calloc(size_t nmemb, size_t size) +{ + uint64_t total = (uint64_t)nmemb * (uint64_t)size; + if(total >= 1ull << 32) return NULL; + size = total; + + void *ptr = malloc(size); + if(ptr) memset(ptr, 0, size); + return ptr; +} + +/* realloc(): Reallocate dynamic memory */ +void *realloc(void *ptr, size_t size) +{ + return krealloc(ptr, size); +}