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> #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. * @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.
*/ */
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 nmemb The amount of members.
* @param size The size of an individual member. * @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. * @brief Free a previously allocated memory region.
*
* @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.
* Passing `NULL` has no effect. * Passing `NULL` has no effect.
* *
* @param ptr The pointer, as returned by `malloc`/`calloc`. * @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. */ /** Initialize the memory allocator, this is only called by the Kernel on early bootstrap. */
void malloc_init(void *heap, size_t size); void malloc_init(void *heap, size_t size);

View file

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

View file

@ -8,11 +8,14 @@
#include <string.h> #include <string.h>
#include <toolchain.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 * 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 * 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 * 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 * On 32-bit systems, a free block in memory followed by an allocated one might look
* something along the lines of this: * something along the lines of this:
* *
* ~~~{.txt}
* ----------------------------------------------------------------------------- * -----------------------------------------------------------------------------
* 0x20010000 | usable size in bytes (238) * 0x20010000 | usable size in bytes (236)
* 0x20010004 | ptr to next-smaller free block \ * 0x20010004 | ptr to next smaller free block \
* 0x20010008 | ptr to next-bigger free block | Usable memory area. If this * 0x20010008 | ptr to next bigger free block | Usable memory area. If this
* : | | was allocated, the returned * : | | was allocated, the returned
* : | <unused garbage data> | ptr would be 0x20010004. * : | <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 * 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. * 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. */ /** The list of free blocks, ordered by ascending size. */
LIST_HEAD(memblk_free_list); 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) static void memblk_set_size(struct memblk *block, size_t size)
{ {
block->size = 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(newblk, blk->size - MEMBLK_OVERHEAD - (size & ~1u));
memblk_set_size(blk, size); memblk_set_size(blk, size);
malloc_bytes_overhead += MEMBLK_OVERHEAD;
list_for_each_entry_reverse(&blk->list, cursor, list) { list_for_each_entry_reverse(&blk->list, cursor, list) {
if (cursor->size >= newblk->size || &cursor->list == &memblk_free_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) void malloc_init(void *heap, size_t size)
{ {
struct memblk *blk = heap; struct memblk *blk = heap;
malloc_bytes_free = size - MEMBLK_OVERHEAD;
/* /*
* TODO: This check will prevent accidentally calling the method twice, but should * 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) void *malloc(size_t size)
{ {
struct memblk *blk; struct memblk *blk;
@ -183,19 +193,27 @@ void *malloc(size_t size)
list_delete(&blk->list); list_delete(&blk->list);
malloc_bytes_free -= size + MEMBLK_OVERHEAD;
malloc_bytes_used += size + MEMBLK_OVERHEAD;
atomic_leave(); atomic_leave();
/* Keep the size field intact */ /* Keep the size field intact */
return ((void *)blk) + MEMBLK_SIZE_LENGTH; return ((void *)blk) + MEMBLK_SIZE_LENGTH;
} }
__shared __attribute__((malloc))
void *calloc(size_t nmemb, size_t size) 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) if (ptr != NULL)
memset(ptr, 0, nmemb * size); memset(ptr, 0, total);
return ptr; 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; size_t *endsz = (void *)hblk + hblk->size + MEMBLK_SIZE_LENGTH;
lblk->size = lblk->size + hblk->size + MEMBLK_OVERHEAD; lblk->size = lblk->size + hblk->size + MEMBLK_OVERHEAD;
*endsz = lblk->size; *endsz = lblk->size;
malloc_bytes_overhead -= MEMBLK_OVERHEAD;
} }
__shared
void free(void *ptr) void free(void *ptr)
{ {
struct memblk *tmp; struct memblk *tmp;
@ -225,6 +243,9 @@ void free(void *ptr)
memblk_set_size(blk, blk->size & ~1u); 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 */ /* check if our higher/right neighbor is allocated and merge if it is not */
neighsz = (void *)blk + MEMBLK_OVERHEAD + blk->size; neighsz = (void *)blk + MEMBLK_OVERHEAD + blk->size;
if ((*neighsz & 0x1u) == 0) { if ((*neighsz & 0x1u) == 0) {