kmalloc: create the kmalloc interface

This change introduces a centralized memory allocator in the kernel.
This interface can call into multiple arenas, including the default OS
heap and planned arenas managed by a gint algorithm.

The main advantage of this method is that it allows the heap to be
extended over previously-unused areas of RAM such as the end of the
static RAM region (apart from where the stack resides). Not using the OS
heap is also sometimes a matter of correctness since on some OS versions
the heap is known to fragment badly and degrade over time.

I hope the deep control this interfaces gives over meomry allocation
will allow very particular applications like object-specific allocators
in fragmented SPU memory.

This change does not introduce any new algorithm or arena so programs
should behave exactly as before.
This commit is contained in:
Lephe 2021-03-12 17:22:24 +01:00
parent 910677f7ff
commit 162b11cc73
Signed by: Lephenixnoir
GPG Key ID: 1BBA026E13FC0495
7 changed files with 302 additions and 14 deletions

View File

@ -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

88
include/gint/kmalloc.h Normal file
View File

@ -0,0 +1,88 @@
//---
// gint:kmalloc - gint's memory allocator
//---
#ifndef GINT_KMALLOC
#define GINT_KMALLOC
#include <gint/defs/types.h>
//---
// 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 */

View File

@ -8,6 +8,7 @@
#include <gint/hardware.h>
#include <gint/mmu.h>
#include <gint/mpu/intc.h>
#include <gint/kmalloc.h>
#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

View File

@ -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 */

View File

@ -0,0 +1,40 @@
//---
// gint:kmalloc:arena_osheap - An arena that uses the OS heap as input
//---
#include <gint/kmalloc.h>
#include <gint/defs/attributes.h>
/* 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 },
};

121
src/kmalloc/kmalloc.c Normal file
View File

@ -0,0 +1,121 @@
//---
// gint:kmalloc:kmalloc - Main allocator routines
//---
#include <gint/kmalloc.h>
#include <gint/defs/util.h>
#include <gint/std/string.h>
/* 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;
}

36
src/std/malloc.c Normal file
View File

@ -0,0 +1,36 @@
//---
// gint:std:malloc - Standard memory allocation functions
//---
#include <gint/kmalloc.h>
#include <gint/std/string.h>
/* 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);
}