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 #pragma once
#include <config.h> #include <config.h>
#include <toolchain.h>
#if 1
#ifdef DEBUG #ifdef DEBUG
# define __breakpoint __asm__ volatile("bkpt") # define __breakpoint __asm__ volatile("bkpt")
#else #else
# define __breakpoint # define __breakpoint
# define NDEBUG
#endif #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. * This file is part of Ardix.
* Copyright (c) 2020, 2021 Felix Kopp <owo@fef.moe>. * Copyright (c) 2020, 2021 Felix Kopp <owo@fef.moe>.

View file

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

View file

@ -6,7 +6,7 @@
#include <toolchain.h> #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*. * @brief Allocate `size` bytes of memory *w/out initializing it*.
* *
* This method may block if an allocation is already taking place. * 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. * @param size The amount of bytes to allocate.
* @return A pointer to the beginning of the memory area, or `NULL` if * @return A pointer to the beginning of the memory area, or `NULL` if
* `size` was 0 or there is not enough free memory left. * `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*. * @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 * using a completely separate, smaller heap. Only use this if you already are
* in atomic context, like when in an irq. * 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 * @return A pointer to the beginning of the memory area, or `NULL` if
* `size` was 0 or there is not enough free memory left. * `size` was 0 or there is not enough free memory left.
*/ */
__malloc(free, 1) void *atomic_malloc(size_t size); __malloc(kfree, 1) void *atomic_kmalloc(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);
/** /**
* @brief Free a previously allocated memory region. * @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`. * @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. */ /** 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. * This file is part of Ardix.

View file

@ -13,6 +13,8 @@ enum syscall {
SYS_read = ARCH_SYS_read, SYS_read = ARCH_SYS_read,
SYS_write = ARCH_SYS_write, SYS_write = ARCH_SYS_write,
SYS_sleep = ARCH_SYS_sleep, SYS_sleep = ARCH_SYS_sleep,
SYS_malloc = ARCH_SYS_malloc,
SYS_free = ARCH_SYS_free,
NSYSCALLS 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 kent.c
kevent.c kevent.c
main.c main.c
mm.c
mutex.c mutex.c
ringbuf.c ringbuf.c
sched.c sched.c

View file

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

View file

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

View file

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

View file

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

View file

@ -18,7 +18,7 @@ long sys_write(int fd, __user const void *buf, size_t len)
if (f == NULL) if (f == NULL)
return -EBADF; return -EBADF;
copy = malloc(len); copy = kmalloc(len);
if (copy == NULL) { if (copy == NULL) {
file_put(f); file_put(f);
return -ENOMEM; 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); len = copy_from_user(copy, buf, len);
ret = file_write(f, copy, len); ret = file_write(f, copy, len);
free(copy); kfree(copy);
file_put(f); file_put(f);
return ret; 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) { if (cb_ret & KEVENT_CB_LISTENER_DEL) {
list_delete(&listener->link); list_delete(&listener->link);
free(listener); kfree(listener);
} }
if (cb_ret & KEVENT_CB_STOP) if (cb_ret & KEVENT_CB_STOP)
@ -102,7 +102,6 @@ void kevents_process(void)
process_single_queue(&kev_queues[i], &kev_listeners[i]); process_single_queue(&kev_queues[i], &kev_listeners[i]);
} }
/* called from irq context only */
void kevent_dispatch(struct kevent *event) void kevent_dispatch(struct kevent *event)
{ {
struct kevent_queue *queue = &kev_queues[event->kind]; 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 *), int (*cb)(struct kevent *, void *),
void *extra) void *extra)
{ {
struct kevent_listener *listener = malloc(sizeof(*listener)); struct kevent_listener *listener = kmalloc(sizeof(*listener));
if (listener != NULL) { if (listener != NULL) {
listener->cb = cb; listener->cb = cb;
@ -158,7 +157,7 @@ void kevent_listener_del(struct kevent_listener *listener)
list_delete(&listener->link); list_delete(&listener->link);
mutex_unlock(&kev_listeners_lock); 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 * 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 * `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 * 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 * 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 * (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 */ /** @brief If the block is allocated, this will be overwritten */
struct list_head list; struct list_head list;
/** @brief Used as the return value for `malloc()` */ /** @brief Used as the return value for kmalloc()` */
uint8_t data[0]; uint8_t data[0];
/** @brief Used to get the copy of the size field at the end of the block */ /** @brief Used to get the copy of the size field at the end of the block */
size_t endsz[0]; 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. */ /** @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); 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); memset(heap, 0, size);
@ -191,7 +202,7 @@ void malloc_init(void *heap, size_t size)
atomic_heap_free = blk_get_size(atomic_block); atomic_heap_free = blk_get_size(atomic_block);
} }
void *malloc(size_t size) void *kmalloc(size_t size)
{ {
if (size == 0) if (size == 0)
return NULL; /* as per POSIX */ return NULL; /* as per POSIX */
@ -230,7 +241,7 @@ void *malloc(size_t size)
return ptr; return ptr;
} }
void *atomic_malloc(size_t size) void *atomic_kmalloc(size_t size)
{ {
if (size == 0) if (size == 0)
return NULL; return NULL;
@ -260,23 +271,7 @@ void *atomic_malloc(size_t size)
return ptr; return ptr;
} }
void *calloc(size_t nmemb, size_t size) void kfree(void *ptr)
{
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)
{ {
if (ptr == NULL) if (ptr == NULL)
return; /* as per POSIX.1-2008 */ return; /* as per POSIX.1-2008 */

View file

@ -10,7 +10,7 @@
struct ringbuf *ringbuf_create(size_t size) struct ringbuf *ringbuf_create(size_t size)
{ {
struct ringbuf *buf = malloc(sizeof(*buf) + size); struct ringbuf *buf = kmalloc(sizeof(*buf) + size);
if (buf == NULL) if (buf == NULL)
return NULL; return NULL;
@ -24,7 +24,7 @@ struct ringbuf *ringbuf_create(size_t size)
inline void ringbuf_destroy(struct ringbuf *buf) inline void ringbuf_destroy(struct ringbuf *buf)
{ {
free(buf); kfree(buf);
} }
size_t ringbuf_read(void *dest, struct ringbuf *buf, size_t len) 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); struct task *task = container_of(kent, struct task, kent);
tasks[task->pid] = NULL; tasks[task->pid] = NULL;
free(task); kfree(task);
} }
int sched_init(void) 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_read, sys_read),
sys_table_entry(SYS_write, sys_write), sys_table_entry(SYS_write, sys_write),
sys_table_entry(SYS_sleep, sys_sleep), 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) long sys_stub(void)

View file

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

View file

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