malloc: track memory usage and clarify docs
This commit is contained in:
parent
711970ae10
commit
528785d40e
3 changed files with 63 additions and 66 deletions
|
@ -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);
|
||||||
|
|
|
@ -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.
|
||||||
|
|
51
lib/malloc.c
51
lib/malloc.c
|
@ -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) {
|
||||||
|
|
Loading…
Reference in a new issue