mm: make malloc and free system calls

This is required because the heap is shared among
all tasks and protected using a mutex which only
works in kernel space.
This commit is contained in:
anna 2021-08-12 14:34:18 +02:00
parent 040b5af5d6
commit fb9ec2a8bc
Signed by: fef
GPG key ID: EC22E476DC2D3D84
20 changed files with 214 additions and 72 deletions

View file

@ -3,13 +3,32 @@
#pragma once
#include <config.h>
#include <toolchain.h>
#if 1
#ifdef DEBUG
# define __breakpoint __asm__ volatile("bkpt")
#else
# define __breakpoint
# define NDEBUG
#endif
#else
#define __breakpoint
#endif
__always_inline int __is_kernel(void) {
int psr_val;
__asm__ volatile(
" mrs %0, psr \n"
: "=&r" (psr_val)
);
return psr_val & 0x01ff; /* bits 8-0 hold ISR_NUMBER */
}
/*
* This file is part of Ardix.
* Copyright (c) 2020, 2021 Felix Kopp <owo@fef.moe>.

View file

@ -5,6 +5,8 @@
#define ARCH_SYS_read 0
#define ARCH_SYS_write 1
#define ARCH_SYS_sleep 2
#define ARCH_SYS_malloc 3
#define ARCH_SYS_free 4
/*
* This file is part of Ardix.

View file

@ -6,7 +6,7 @@
#include <toolchain.h>
/**
* @defgroup malloc Memory Management
* @defgroup kmalloc Kernel Memory Management
*
* @{
*/
@ -15,18 +15,18 @@
* @brief Allocate `size` bytes of memory *w/out initializing it*.
*
* This method may block if an allocation is already taking place.
* Use `atomic_malloc()` if you are in kernel space and in atomic context.
* Use `atomic_kmalloc()` if you are in kernel space and in atomic context.
*
* @param size The amount of bytes to allocate.
* @return A pointer to the beginning of the memory area, or `NULL` if
* `size` was 0 or there is not enough free memory left.
*/
__shared __malloc(free, 1) void *malloc(size_t size);
__malloc(kfree, 1) void *kmalloc(size_t size);
/**
* @brief Allocate `size` bytes of memory *w/out initializing it*.
*
* Unlike `malloc()`, this method is guaranteed not to sleep. It does this by
* Unlike `kmalloc()`, this method is guaranteed not to sleep. It does this by
* using a completely separate, smaller heap. Only use this if you already are
* in atomic context, like when in an irq.
*
@ -34,18 +34,7 @@ __shared __malloc(free, 1) void *malloc(size_t size);
* @return A pointer to the beginning of the memory area, or `NULL` if
* `size` was 0 or there is not enough free memory left.
*/
__malloc(free, 1) void *atomic_malloc(size_t size);
/**
* @brief Allocate an array and initialize the memory to zeroes.
* The allocated size will be at least `nmemb * size`.
* If the multiplication would overflow, the allocation fails.
*
* @param nmemb The amount of members.
* @param size The size of an individual member.
* @return A pointer to the zeroed-out memory, or `NULL` if OOM.
*/
__shared __malloc(free, 1) void *calloc(size_t nmemb, size_t size);
__malloc(kfree, 1) void *atomic_kmalloc(size_t size);
/**
* @brief Free a previously allocated memory region.
@ -53,12 +42,12 @@ __shared __malloc(free, 1) void *calloc(size_t nmemb, size_t size);
*
* @param ptr The pointer, as returned by `malloc`/`calloc`.
*/
__shared void free(void *ptr);
/** @} */
void kfree(void *ptr);
/** Initialize the memory allocator, this is only called by the bootloader on early bootstrap. */
void malloc_init(void *heap, size_t size);
void kmalloc_init(void *heap, size_t size);
/** @} */
/*
* This file is part of Ardix.

View file

@ -13,6 +13,8 @@ enum syscall {
SYS_read = ARCH_SYS_read,
SYS_write = ARCH_SYS_write,
SYS_sleep = ARCH_SYS_sleep,
SYS_malloc = ARCH_SYS_malloc,
SYS_free = ARCH_SYS_free,
NSYSCALLS
};

24
include/stdassert.h Normal file
View file

@ -0,0 +1,24 @@
/* See the end of this file for copyright, license, and warranty information. */
#pragma once
#include <arch/debug.h>
#ifdef NDEBUG
# define assert(expr)
#else
# define assert(expr) if (!(expr)) { __breakpoint; }
#endif
/*
* This file is part of Ardix.
* Copyright (c) 2020, 2021 Felix Kopp <owo@fef.moe>.
*
* Ardix is non-violent software: you may only use, redistribute,
* and/or modify it under the terms of the CNPLv6+ as found in
* the LICENSE file in the source code root directory or at
* <https://git.pixie.town/thufie/CNPL>.
*
* Ardix comes with ABSOLUTELY NO WARRANTY, to the extent
* permitted by applicable law. See the CNPLv6+ for details.
*/

58
include/stdlib.h Normal file
View file

@ -0,0 +1,58 @@
/* See the end of this file for copyright, license, and warranty information. */
#pragma once
#include <ardix/types.h>
#include <toolchain.h>
/**
* @defgroup malloc Memory Management
*
* @{
*/
/**
* @brief Allocate `size` bytes of memory *w/out initializing it*.
*
* This method may block if an allocation is already taking place.
* Use `atomickmalloc()` if you are in kernel space and in atomic context.
*
* @param size The amount of bytes to allocate.
* @return A pointer to the beginning of the memory area, or `NULL` if
* `size` was 0 or there is not enough free memory left.
*/
__shared __malloc(free, 1) void *malloc(size_t size);
/**
* @brief Allocate an array and initialize the memory to zeroes.
* The allocated size will be at least `nmemb * size`.
* If the multiplication would overflow, the allocation fails.
*
* @param nmemb The amount of members.
* @param size The size of an individual member.
* @return A pointer to the zeroed-out memory, or `NULL` if OOM.
*/
__malloc(free, 1) void *calloc(size_t nmemb, size_t size);
/**
* @brief Free a previously allocated memory region.
* Passing `NULL` has no effect.
*
* @param ptr The pointer, as returned by `malloc`/`calloc`.
*/
__shared void free(void *ptr);
/** @} */
/*
* This file is part of Ardix.
* Copyright (c) 2020, 2021 Felix Kopp <owo@fef.moe>.
*
* Ardix is non-violent software: you may only use, redistribute,
* and/or modify it under the terms of the CNPLv6+ as found in
* the LICENSE file in the source code root directory or at
* <https://git.pixie.town/thufie/CNPL>.
*
* Ardix comes with ABSOLUTELY NO WARRANTY, to the extent
* permitted by applicable law. See the CNPLv6+ for details.
*/

View file

@ -15,6 +15,7 @@ target_sources(ardix_kernel PRIVATE
kent.c
kevent.c
main.c
mm.c
mutex.c
ringbuf.c
sched.c

View file

@ -14,7 +14,7 @@ struct kent *devices_kent = NULL;
static void devices_destroy(struct kent *kent)
{
/* should never be executed because the root devices kent is immortal */
free(kent);
kfree(kent);
}
/** Initialize the devices subsystem. */
@ -23,7 +23,7 @@ int devices_init(void)
if (devices_kent != NULL)
return -EEXIST;
devices_kent = malloc(sizeof(*devices_kent));
devices_kent = kmalloc(sizeof(*devices_kent));
if (devices_kent == NULL)
return -ENOMEM;
@ -36,7 +36,7 @@ int devices_init(void)
static void device_destroy(struct kent *kent)
{
struct device *dev = kent_to_device(kent);
free(dev);
kfree(dev);
}
int device_init(struct device *dev)
@ -54,12 +54,12 @@ static void device_kevent_destroy(struct kent *kent)
{
struct kevent *event = container_of(kent, struct kevent, kent);
struct device_kevent *device_kevent = container_of(event, struct device_kevent, kevent);
free(device_kevent);
kfree(device_kevent);
}
struct device_kevent *device_kevent_create(struct device *device, enum device_kevent_flags flags)
{
struct device_kevent *event = atomic_malloc(sizeof(*event));
struct device_kevent *event = atomic_kmalloc(sizeof(*event));
if (event == NULL)
return NULL;
@ -70,7 +70,7 @@ struct device_kevent *device_kevent_create(struct device *device, enum device_ke
event->kevent.kent.destroy = device_kevent_destroy;
int err = kent_init(&event->kevent.kent);
if (err) {
free(event);
kfree(event);
event = NULL;
}

View file

@ -12,7 +12,7 @@
static void dmabuf_destroy(struct kent *kent)
{
struct dmabuf *buf = kent_to_dmabuf(kent);
free(buf);
kfree(buf);
}
struct dmabuf *dmabuf_create(struct device *dev, size_t len)
@ -22,7 +22,7 @@ struct dmabuf *dmabuf_create(struct device *dev, size_t len)
* allocation needs to be atomic because the buffer might be
* free()d from within an irq handler which cannot sleep
*/
struct dmabuf *buf = atomic_malloc(sizeof(*buf) + len);
struct dmabuf *buf = atomic_kmalloc(sizeof(*buf) + len);
if (buf == NULL)
return NULL;
@ -31,7 +31,7 @@ struct dmabuf *dmabuf_create(struct device *dev, size_t len)
err = kent_init(&buf->kent);
if (err) {
free(buf);
kfree(buf);
return NULL;
}

View file

@ -20,7 +20,7 @@ static void file_destroy(struct kent *kent)
fdtab[file->fd] = NULL;
mutex_unlock(&fdtab_lock);
free(file);
kfree(file);
}
struct file *file_create(struct device *device, enum file_type type, int *err)
@ -41,7 +41,7 @@ struct file *file_create(struct device *device, enum file_type type, int *err)
return NULL;
}
f = malloc(sizeof(*f));
f = kmalloc(sizeof(*f));
if (f == NULL) {
*err = -ENOMEM;
mutex_unlock(&fdtab_lock);
@ -102,7 +102,7 @@ static int io_device_kevent_listener(struct kevent *event, void *_extra)
return KEVENT_CB_NONE;
extra->task->state = TASK_QUEUE;
free(extra);
kfree(extra);
file_put(extra->file);
kent_put(&extra->task->kent);
return KEVENT_CB_LISTENER_DEL | KEVENT_CB_STOP;
@ -114,7 +114,7 @@ static int iowait_device(struct file *file, enum device_kevent_flags flags)
kent_get(&current->kent);
/* this must be atomic because event listeners can't sleep but need to call free() */
struct io_device_kevent_extra *extra = atomic_malloc(sizeof(*extra));
struct io_device_kevent_extra *extra = atomic_kmalloc(sizeof(*extra));
if (extra == NULL)
return -ENOMEM;
@ -201,12 +201,12 @@ static void file_kevent_destroy(struct kent *kent)
{
struct kevent *kevent = container_of(kent, struct kevent, kent);
struct file_kevent *file_kevent = container_of(kevent, struct file_kevent, kevent);
free(file_kevent);
kfree(file_kevent);
}
struct file_kevent *file_kevent_create(struct file *f, enum file_kevent_flags flags)
{
struct file_kevent *event = atomic_malloc(sizeof(*event));
struct file_kevent *event = atomic_kmalloc(sizeof(*event));
if (event == NULL)
return NULL;
@ -217,7 +217,7 @@ struct file_kevent *file_kevent_create(struct file *f, enum file_kevent_flags fl
event->kevent.kent.destroy = file_kevent_destroy;
int err = kent_init(&event->kevent.kent);
if (err != 0) {
free(event);
kfree(event);
event = NULL;
}

View file

@ -18,7 +18,7 @@ long sys_read(int fd, __user void *buf, size_t len)
if (f == NULL)
return -EBADF;
copy = malloc(len);
copy = kmalloc(len);
if (copy == NULL)
return -ENOMEM;
@ -26,7 +26,7 @@ long sys_read(int fd, __user void *buf, size_t len)
if (ret >= 0)
ret = copy_to_user(buf, copy, ret);
free(copy);
kfree(copy);
file_put(f);
return ret;
}

View file

@ -18,7 +18,7 @@ long sys_write(int fd, __user const void *buf, size_t len)
if (f == NULL)
return -EBADF;
copy = malloc(len);
copy = kmalloc(len);
if (copy == NULL) {
file_put(f);
return -ENOMEM;
@ -27,7 +27,7 @@ long sys_write(int fd, __user const void *buf, size_t len)
len = copy_from_user(copy, buf, len);
ret = file_write(f, copy, len);
free(copy);
kfree(copy);
file_put(f);
return ret;
}

View file

@ -66,7 +66,7 @@ static inline void process_single_queue(struct kevent_queue *queue, struct list_
if (cb_ret & KEVENT_CB_LISTENER_DEL) {
list_delete(&listener->link);
free(listener);
kfree(listener);
}
if (cb_ret & KEVENT_CB_STOP)
@ -102,7 +102,6 @@ void kevents_process(void)
process_single_queue(&kev_queues[i], &kev_listeners[i]);
}
/* called from irq context only */
void kevent_dispatch(struct kevent *event)
{
struct kevent_queue *queue = &kev_queues[event->kind];
@ -138,7 +137,7 @@ struct kevent_listener *kevent_listener_add(enum kevent_kind kind,
int (*cb)(struct kevent *, void *),
void *extra)
{
struct kevent_listener *listener = malloc(sizeof(*listener));
struct kevent_listener *listener = kmalloc(sizeof(*listener));
if (listener != NULL) {
listener->cb = cb;
@ -158,7 +157,7 @@ void kevent_listener_del(struct kevent_listener *listener)
list_delete(&listener->link);
mutex_unlock(&kev_listeners_lock);
free(listener);
kfree(listener);
}
/*

View file

@ -27,7 +27,7 @@
* header containing its size w/out overhead; free blocks additionally have a
* `struct list_head` after that in order to keep track of where the free blocks
* are. This list is ordered by size ascendingly, so we can directly take the
* first sufficiently sized block when iterating over the list in `malloc()`.
* first sufficiently sized block when iterating over the list in kmalloc()`.
*
* Additionally, the effective block size is copied to the very end of the block
* (directly after the last usable address) in order to be able to find a
@ -106,7 +106,7 @@ struct memblk {
/** @brief If the block is allocated, this will be overwritten */
struct list_head list;
/** @brief Used as the return value for `malloc()` */
/** @brief Used as the return value for kmalloc()` */
uint8_t data[0];
/** @brief Used to get the copy of the size field at the end of the block */
size_t endsz[0];
@ -164,7 +164,18 @@ static struct memblk *blk_try_merge(struct list_head *heap, struct memblk *blk);
/** @brief Cut a slice from a free block and return the slice. */
static struct memblk *blk_slice(struct list_head *heap, struct memblk *bottom, size_t bottom_size);
void malloc_init(void *heap, size_t size)
long sys_malloc(size_t size)
{
void *ptr = kmalloc(size);
return *(long *)&ptr;
}
void sys_free(void *ptr)
{
kfree(ptr);
}
void kmalloc_init(void *heap, size_t size)
{
memset(heap, 0, size);
@ -191,7 +202,7 @@ void malloc_init(void *heap, size_t size)
atomic_heap_free = blk_get_size(atomic_block);
}
void *malloc(size_t size)
void *kmalloc(size_t size)
{
if (size == 0)
return NULL; /* as per POSIX */
@ -230,7 +241,7 @@ void *malloc(size_t size)
return ptr;
}
void *atomic_malloc(size_t size)
void *atomic_kmalloc(size_t size)
{
if (size == 0)
return NULL;
@ -260,23 +271,7 @@ void *atomic_malloc(size_t size)
return ptr;
}
void *calloc(size_t nmemb, size_t size)
{
size_t total = nmemb * size;
/* check for overflow as mandated by POSIX */
if (size != 0 && total / size != nmemb)
return NULL;
void *ptr = malloc(total);
if (ptr != NULL)
memset(ptr, 0, total);
return ptr;
}
void free(void *ptr)
void kfree(void *ptr)
{
if (ptr == NULL)
return; /* as per POSIX.1-2008 */

View file

@ -10,7 +10,7 @@
struct ringbuf *ringbuf_create(size_t size)
{
struct ringbuf *buf = malloc(sizeof(*buf) + size);
struct ringbuf *buf = kmalloc(sizeof(*buf) + size);
if (buf == NULL)
return NULL;
@ -24,7 +24,7 @@ struct ringbuf *ringbuf_create(size_t size)
inline void ringbuf_destroy(struct ringbuf *buf)
{
free(buf);
kfree(buf);
}
size_t ringbuf_read(void *dest, struct ringbuf *buf, size_t len)

View file

@ -57,7 +57,7 @@ static void task_destroy(struct kent *kent)
{
struct task *task = container_of(kent, struct task, kent);
tasks[task->pid] = NULL;
free(task);
kfree(task);
}
int sched_init(void)

View file

@ -13,6 +13,8 @@ long (*const sys_table[NSYSCALLS])(sysarg_t arg1, sysarg_t arg2, sysarg_t arg3,
sys_table_entry(SYS_read, sys_read),
sys_table_entry(SYS_write, sys_write),
sys_table_entry(SYS_sleep, sys_sleep),
sys_table_entry(SYS_malloc, sys_malloc),
sys_table_entry(SYS_free, sys_free),
};
long sys_stub(void)

View file

@ -10,8 +10,8 @@ target_sources(ardix_lib PRIVATE
ctype.c
errno.c
list.c
malloc.c
printf.c
stdlib.c
string.c
unistd.c
)

View file

@ -1,13 +1,12 @@
/* See the end of this file for copyright, license, and warranty information. */
#include <ardix/malloc.h>
#include <errno.h>
/* Using GCC's stdarg.h is recommended even with -nodefaultlibs and -fno-builtin */
#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

52
lib/stdlib.c Normal file
View file

@ -0,0 +1,52 @@
/* See the end of this file for copyright, license, and warranty information. */
#include <ardix/syscall.h>
#include <stddef.h>
#include <stdlib.h>
/*
* kmalloc() and free() are system calls in Ardix because the heap is shared
* among all tasks and locked using a mutex. If the lock is already claimed,
* the `mutex_lock()` routine will suspend the current task until the lock
* becomes available to the current process. However, this can only happen
* when we already are in kernel space.
*/
void *malloc(size_t size)
{
if (size == 0) {
return NULL;
} else {
long int intptr = syscall(SYS_malloc, (sysarg_t)size);
return *(void **)&intptr;
}
}
void *calloc(size_t nmemb, size_t size)
{
size_t total = nmemb * size;
if (nmemb != 0 && total / nmemb != size)
return NULL; /* overflow check as mandated by POSIX.1 */
long int intptr = syscall(SYS_malloc, (sysarg_t)total);
return *(void **)&intptr;
}
void free(void *ptr)
{
if (ptr != NULL)
syscall(SYS_free, (sysarg_t)ptr);
}
/*
* This file is part of Ardix.
* Copyright (c) 2020, 2021 Felix Kopp <owo@fef.moe>.
*
* Ardix is non-violent software: you may only use, redistribute,
* and/or modify it under the terms of the CNPLv6+ as found in
* the LICENSE file in the source code root directory or at
* <https://git.pixie.town/thufie/CNPL>.
*
* Ardix comes with ABSOLUTELY NO WARRANTY, to the extent
* permitted by applicable law. See the CNPLv6+ for details.
*/