malloc: track memory usage and clarify docs

This commit is contained in:
anna 2021-08-03 17:35:58 +02:00
parent 711970ae10
commit 528785d40e
Signed by: fef
GPG key ID: EC22E476DC2D3D84
3 changed files with 63 additions and 66 deletions

View file

@ -6,42 +6,40 @@
#include <toolchain.h>
/**
* Allocate `size` bytes of memory *w/out initializing it*.
* @defgroup malloc Memory Management
*
* @{
*/
/**
* @brief Allocate `size` bytes of memory *w/out initializing it*.
*
* @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.
*/
void *malloc(size_t size);
__shared __malloc(free, 1) void *malloc(size_t size);
/**
* Allocate an array and initialize the memory to zeroes.
* @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` of `ENOMEM`.
* @return A pointer to the zeroed-out memory, or `NULL` if OOM.
*/
void *calloc(size_t nmemb, size_t size);
__shared __malloc(free, 1) void *calloc(size_t nmemb, size_t size);
/**
* Allocate at least `size` bytes of memory and initialize it to zero.
*
* @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.
*/
__always_inline void *zalloc(size_t size)
{
return calloc(1, size);
}
/**
* Free a previously allocated memory region.
* @brief Free a previously allocated memory region.
* Passing `NULL` has no effect.
*
* @param ptr The pointer, as returned by `malloc`/`calloc`.
*/
void free(void *ptr);
__shared void free(void *ptr);
/** @} */
/** Initialize the memory allocator, this is only called by the Kernel on early bootstrap. */
void malloc_init(void *heap, size_t size);

View file

@ -6,74 +6,52 @@
#error "Only GCC is supported"
#endif /* __GNUC__ */
#ifndef __always_inline
/**
* Force a method to always be inlined by the compiler.
* Do not use this for functions exceeding one or two lines.
*/
/** Force a method to always be inlined. */
#define __always_inline inline __attribute__(( always_inline ))
#endif /* __always_inline */
#ifndef __naked
/** Function attribute for disabling register saving. */
#define __naked __attribute__(( naked ))
#endif
#ifndef __noreturn
/** Function attribute denoting the call will never return. */
#define __noreturn __attribute__(( noreturn ))
#endif /* __noreturn */
#ifndef __weak
/**
* Add the `weak` attribute to a symbol.
* This allows that identifier to be re-declared without any warnings.
* This allows that identifier to be redeclared without any warnings.
*/
#define __weak __attribute__(( weak ))
#endif /* __weak */
#ifndef __alias
/**
* Declare an identifier as an alias for some other identifier.
*
* @param name: The identifier (w/out quotes) this should be an alias for.
*/
#define __alias(name) __attribute__((alias(#name)))
#endif /* __alias */
#define __alias(name) __attribute__(( alias(#name) ))
#ifndef __section
/**
* Define the program section this symbol should live in.
*
* @param name: The section name w/out quotes.
*/
#define __section(name) __attribute__((section(#name)))
#endif /* __section */
#define __section(name) __attribute__(( section(#name) ))
#ifndef __rodata
/** Place a variable in program memory rather than into RAM. */
#define __rodata __section(.rodata#)
#endif
#ifndef __pure
/** Declare a function is pure so gcc can do some common subexpression elimination. */
#define __pure __attribute__((pure))
#endif
#define __pure __attribute__(( pure ))
#ifndef __const
/** Like `__pure`, and the fuction does not access any memory except its stack. */
#define __const __attribute__((const))
#endif
#define __const __attribute__(( __const__ ))
#ifndef __user
/** Denote a pointer to user space (this will be used for static code checks later) */
/** Denote a pointer to user space (this will be used for static code checks later)- */
#define __user
#endif
#ifndef __shared
/** Storage attribute indicating the symbol will be shared with userspace. */
#define __shared __section(.text.shared)
#endif
/** Function attribute for hinting this function has malloc-like behavior. */
#define __malloc(deallocator, argn) __attribute__(( malloc ))
/*
* This file is part of Ardix.

View file

@ -8,11 +8,14 @@
#include <string.h>
#include <toolchain.h>
/*
* Stupid memory allocator implementation.
/**
* @file Stupid memory allocator.
*
* This design is heavily inspired by (read: stolen from) Doug Lea's Malloc
* <http://gee.cs.oswego.edu/dl/html/malloc.html>. It basically works like this:
* <http://gee.cs.oswego.edu/dl/html/malloc.html>, with some features (notably binning)
* removed for the sake of simplicity. Furthermore, as the MPU is not implemented yet,
* the allocator uses one big heap for all processes including the kernel. We also
* don't have virtual memory and therefore no wilderness chunk to take care of.
*
* Memory is divided into individual blocks of dynamic size. Every block has a header
* containing its size w/out overhead; free blocks additionally have a
@ -30,21 +33,23 @@
* On 32-bit systems, a free block in memory followed by an allocated one might look
* something along the lines of this:
*
* ~~~{.txt}
* -----------------------------------------------------------------------------
* 0x20010000 | usable size in bytes (238)
* 0x20010004 | ptr to next-smaller free block \
* 0x20010008 | ptr to next-bigger free block | Usable memory area. If this
* 0x20010000 | usable size in bytes (236)
* 0x20010004 | ptr to next smaller free block \
* 0x20010008 | ptr to next bigger free block | Usable memory area. If this
* : | | was allocated, the returned
* : | <unused garbage data> | ptr would be 0x20010004.
* : | /
* 0x200100EE | usable size in bytes (238)
* 0x200100EC | usable size in bytes (236)
* -----------------------------------------------------------------------------
* 0x200100F2 | usable size in bytes (32) + 1 for the "used" bit
* 0x200100F0 | usable size in bytes (32) + 1 for the "used" bit
* : | \
* : | <user-defined data> | 32 bytes
* : | <user data> | 32 bytes
* : | /
* 0x20010112 | usable size in bytes (32 + 1)
* 0x20010110 | usable size in bytes (32 + 1)
* -----------------------------------------------------------------------------
* ~~~
*
* That makes the minimal allocation size `sizeof(struct list_head *)`, because we need to
* store those pointers for the linked list when `free()`ing a block.
@ -87,6 +92,10 @@ struct memblk {
/** The list of free blocks, ordered by ascending size. */
LIST_HEAD(memblk_free_list);
size_t malloc_bytes_free;
size_t malloc_bytes_used = MEMBLK_OVERHEAD;
size_t malloc_bytes_overhead = MEMBLK_OVERHEAD;
static void memblk_set_size(struct memblk *block, size_t size)
{
block->size = size;
@ -113,6 +122,7 @@ static struct memblk *memblk_split(struct memblk *blk, size_t size)
memblk_set_size(newblk, blk->size - MEMBLK_OVERHEAD - (size & ~1u));
memblk_set_size(blk, size);
malloc_bytes_overhead += MEMBLK_OVERHEAD;
list_for_each_entry_reverse(&blk->list, cursor, list) {
if (cursor->size >= newblk->size || &cursor->list == &memblk_free_list) {
@ -127,6 +137,7 @@ static struct memblk *memblk_split(struct memblk *blk, size_t size)
void malloc_init(void *heap, size_t size)
{
struct memblk *blk = heap;
malloc_bytes_free = size - MEMBLK_OVERHEAD;
/*
* TODO: This check will prevent accidentally calling the method twice, but should
@ -140,7 +151,6 @@ void malloc_init(void *heap, size_t size)
}
}
__shared __attribute__((malloc))
void *malloc(size_t size)
{
struct memblk *blk;
@ -183,19 +193,27 @@ void *malloc(size_t size)
list_delete(&blk->list);
malloc_bytes_free -= size + MEMBLK_OVERHEAD;
malloc_bytes_used += size + MEMBLK_OVERHEAD;
atomic_leave();
/* Keep the size field intact */
return ((void *)blk) + MEMBLK_SIZE_LENGTH;
}
__shared __attribute__((malloc))
void *calloc(size_t nmemb, size_t size)
{
void *ptr = malloc(nmemb * 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, nmemb * size);
memset(ptr, 0, total);
return ptr;
}
@ -206,9 +224,9 @@ static void memblk_merge(struct memblk *lblk, struct memblk *hblk)
size_t *endsz = (void *)hblk + hblk->size + MEMBLK_SIZE_LENGTH;
lblk->size = lblk->size + hblk->size + MEMBLK_OVERHEAD;
*endsz = lblk->size;
malloc_bytes_overhead -= MEMBLK_OVERHEAD;
}
__shared
void free(void *ptr)
{
struct memblk *tmp;
@ -225,6 +243,9 @@ void free(void *ptr)
memblk_set_size(blk, blk->size & ~1u);
malloc_bytes_free += blk->size + MEMBLK_OVERHEAD;
malloc_bytes_used -= blk->size + MEMBLK_OVERHEAD;
/* check if our higher/right neighbor is allocated and merge if it is not */
neighsz = (void *)blk + MEMBLK_OVERHEAD + blk->size;
if ((*neighsz & 0x1u) == 0) {