Merge branch 'feature/alloc_from_region' into develop

This commit is contained in:
Tilen Majerle 2020-02-02 19:36:31 +01:00
commit df220761dd
16 changed files with 812 additions and 391 deletions

View File

@ -1,6 +1,6 @@
# Lightweight dynamic memory manager
<h3><a href="http://docs.majerle.eu/projects/lwmem">Documentation</a></h3>
<h3><a href="http://docs.majerle.eu/projects/lwmem/">Documentation</a></h3>
## Features

View File

@ -145,6 +145,7 @@
<ItemGroup>
<ClCompile Include="..\..\lwmem\src\lwmem\lwmem.c" />
<ClCompile Include="..\..\lwmem\src\system\lwmem_sys_win32.c" />
<ClCompile Include="..\..\tests\lwmem_test.c" />
<ClCompile Include="main.c" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />

View File

@ -8,6 +8,9 @@
<Filter Include="Source Files\LWMEM">
<UniqueIdentifier>{a9bad49b-d114-4596-8fe8-162c60f482ee}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\Tests">
<UniqueIdentifier>{87f67bb1-45c3-4724-b7de-f1e8551453e3}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="main.c">
@ -19,5 +22,8 @@
<ClCompile Include="..\..\lwmem\src\system\lwmem_sys_win32.c">
<Filter>Source Files\LWMEM</Filter>
</ClCompile>
<ClCompile Include="..\..\tests\lwmem_test.c">
<Filter>Source Files\Tests</Filter>
</ClCompile>
</ItemGroup>
</Project>

View File

@ -3,87 +3,12 @@
#include "string.h"
#include "stdint.h"
#define ARRSIZE(x) (sizeof(x) / (sizeof((x)[0])))
/* Define regions in increasing order */
uint8_t mem1[1024];
lwmem_region_t
regions[] = {
{ mem1, sizeof(mem1) },
};
#define ASSERT(x) do { \
if (!(x)) { \
printf("Assert failed with condition (" # x ")\r\n"); \
} else {\
printf("Assert passed with condition (" # x ")\r\n"); \
}\
} while (0)
/* For debug purposes */
lwmem_region_t* regions_used;
size_t regions_count = 1; /* Use only 1 region for debug purposes of non-free areas */
extern void lwmem_test_run(void);
extern void lwmem_test_memory_structure(void);
int
main(void) {
uint8_t* ptr1, *ptr2, *ptr3, *ptr4;
uint8_t* rptr1, *rptr2, *rptr3, *rptr4;
/* Create regions for debug purpose */
if (!lwmem_debug_create_regions(&regions_used, regions_count, 128)) {
printf("Cannot allocate memory for regions for debug purpose!\r\n");
return -1;
}
lwmem_assignmem(regions_used, regions_count);
printf("Manager is ready!\r\n");
lwmem_debug_print(1, 1);
/* Test case 1, allocate 3 blocks, each of different size */
/* We know that sizeof internal metadata block is 8 bytes on win32 */
printf("\r\n\r\nAllocating 4 pointers and freeing first and third..\r\n");
ptr1 = lwmem_malloc(8);
ptr2 = lwmem_malloc(4);
ptr3 = lwmem_malloc(4);
ptr4 = lwmem_malloc(16);
lwmem_free(ptr1); /* Free but keep value for future comparison */
lwmem_free(ptr3); /* Free but keep value for future comparison */
lwmem_debug_print(1, 1);
printf("Debug above is effectively state 3\r\n");
lwmem_debug_save_state(); /* Every restore operations rewinds here */
/* We always try to reallocate pointer ptr2 */
/* Create 3a case */
printf("\r\n------------------------------------------------------------------------\r\n");
lwmem_debug_restore_to_saved();
printf("State 3a\r\n");
rptr1 = lwmem_realloc(ptr2, 8);
lwmem_debug_print(1, 1);
ASSERT(rptr1 == ptr2);
/* Create 3b case */
printf("\r\n------------------------------------------------------------------------\r\n");
lwmem_debug_restore_to_saved();
printf("State 3b\r\n");
rptr2 = lwmem_realloc(ptr2, 20);
lwmem_debug_print(1, 1);
ASSERT(rptr2 == ptr2);
/* Create 3c case */
printf("\r\n------------------------------------------------------------------------\r\n");
lwmem_debug_restore_to_saved();
printf("State 3c\r\n");
rptr3 = lwmem_realloc(ptr2, 24);
lwmem_debug_print(1, 1);
ASSERT(rptr3 == ptr1);
/* Create 3d case */
printf("\r\n------------------------------------------------------------------------\r\n");
lwmem_debug_restore_to_saved();
printf("State 3d\r\n");
rptr4 = lwmem_realloc(ptr2, 36);
lwmem_debug_print(1, 1);
ASSERT(rptr4 != ptr1 && rptr4 != ptr2 && rptr4 != ptr3 && rptr4 != ptr4);
lwmem_test_memory_structure();
//lwmem_test_run();
return 0;
}

View File

@ -0,0 +1,16 @@
#include "lwmem/lwmem.h"
/* Assignment has been done previously... */
/* ptr1 will be allocated in first free block */
/* ptr2 will be allocated from second region */
void* ptr1, *ptr2;
/* Allocate 8 bytes of memory in any region */
/* Use one of 2 options, both have same effect */
ptr1 = lwmem_malloc(8);
ptr1 = lwmem_malloc_ex(NULL, NULL, 8);
/* Allocate memory from specific region only */
/* Use second region */
ptr2 = lwmem_malloc_ex(NULL, &regions[1], 512);

View File

@ -13,4 +13,6 @@ lwmem_region_t regions[] = {
/* Later in the initialization process */
/* Assign regions for manager */
lwmem_assignmem(regions, sizeof(regions) / sizeof(regions[0]));
lwmem_assignmem(regions, sizeof(regions) / sizeof(regions[0]));
/* or */
lwmem_assignmem_ex(NULL, regions, sizeof(regions) / sizeof(regions[0]));

View File

@ -0,0 +1,22 @@
#include "lwmem/lwmem.h"
/**
* \brief Custom LwMEM instance
*/
static
lwmem_t lw_custom;
/*
* \brief Define regions for memory manager
*/
static
lwmem_region_t regions[] = {
/* Set start address and size of each region */
{ (void *)0x10000000, 0x00001000 },
{ (void *)0xA0000000, 0x00008000 },
{ (void *)0xC0000000, 0x00008000 },
};
/* Later in the initialization process */
/* Assign regions for custom instance */
lwmem_assignmem_ex(&lw_custom, regions, sizeof(regions) / sizeof(regions[0]));

View File

@ -13,10 +13,12 @@ Features
* Written in ANSI C99, compatible with ``size_t`` for size data types
* Implements standard C library functions for memory allocation, malloc, calloc, realloc and free
* Uses *first-fit* algorithm to search free block
* Uses *first-fit* algorithm to search for free block
* Supports multiple allocation instances to split between memories and/or CPU cores
* Supports different memory regions to allow use of fragmented memories
* Suitable for embedded applications with fragmented memories
* Suitable for automotive applications
* Highly configurable for memory allocation and reallocation
* Supports embedded applications with fragmented memories
* Supports automotive applications
* Supports advanced free/realloc algorithms to optimize memory usage
* Operating system ready, thread-safe API
* User friendly MIT license

1
docs/static/images/lwmem_arch vendored Normal file
View File

@ -0,0 +1 @@
<mxfile host="Electron" modified="2020-02-02T16:56:31.789Z" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/12.3.2 Chrome/78.0.3904.113 Electron/7.1.2 Safari/537.36" etag="0PingvSYfJsyOGvN1hy2" version="12.3.2" type="device" pages="1"><diagram id="gtKOu0siOd7cJkY6bJAT" name="Page-1">7Zhbb9owFMc/DY+bciEheSyUbpNaqRra1j468Uni1djImEL26WcThyQ4tFSDtpP6hM/flzi/c7HJwJ/MN18EWhQ3HAMdeA7eDPzLgee5nhurH62UlRLGw0rIBcFmUCPMyB8womPUFcGw7AyUnFNJFl0x5YxBKjsaEoKvu8MyTrtPXaAcLGGWImqrvwiWRaVGgdPoX4HkRf1k1zE9c1QPNsKyQJivW5I/HfgTwbmsWvPNBKiGV3Op5l0d6N1tTACTx0y4vwumEHs/yusk/hb/DH5djYNPblAt84joyryx2a0sawSCrxgGvYoz8MfrgkiYLVCqe9fK6Uor5Jwqy1XNjFA64ZSL7Vw/yyBMU6UvpeAP0OrBozhx9IJmAyAkbA6+mrsDpiIN+BykKNWQekJkGNdBZsx14zG/9kvR9lbtG2SiJN8t3YBUDcPyJVzPixUjiLJerGEaQZKdBuvwWao7gK9D1bOoXq9vpjd6GltKxBQ8L6RqG+NEqFauW6owKEZ66YTy9MFyg8Iju6y7TBlnsOcAIyFKcqbMVOEEpY81bKJKx4XpmBOM9WN6ndt1/9t5a3guZ/mWs2YSCXnQES/KhxPwCrq8XLcHWF/ROFt026X4VpBHJGEb3xl/d8SGRxLzz1ZlXYsJYHV8G5MLWfCcM0SnjbqXeM2Ya84XhtVvkLI0dxG0krxLEjZE3rXa93qpz4GxLjdm5a1R1oYqQuVd22jN0mYzbWvV86paVN88vJ4DNkqh/4BNomAYNLVFg3na74ojX4kUngAeVuNUHucgnxg36o8jARRJ8tjdx8mjIrTy6DvkhDOl2fHyT6dwABEe9rGPvMQPwxNdbuJu2o3srIv6TmHnbFln32U+su6cWTc6Muuit8y60eGsO/Hd91WyzntvWRcd5sv+Q77+e+MbW3ztKsfwhf6koKyUouWSpB8lqnvZf7ZEhf1B0fJ60OP1Wju6kpkn3HLC5MF/SLuYq1eo3tJMan9J2VtnL3StfwQVBWudbVjuXrovUpXZfA+qhjdf1fzpXw==</diagram></mxfile>

3
docs/static/images/lwmem_arch.svg vendored Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.8 KiB

View File

@ -36,7 +36,7 @@ First step is to define custom regions and assign them to memory manager.
.. note::
Order of regions must be lower address first. Regions must not overlap with their sizes.
When calling ``lwmem_assignmem``, manager prepares memory blocks and assigns default values.
When calling :c:macro:`lwmem_assignmem`, manager prepares memory blocks and assigns default values.
.. figure:: ../static/images/structure_default.svg
:align: center
@ -98,5 +98,48 @@ Image shows only first region to simplify process. Same procedure applies to oth
* No any memory allocated anymore, regions are back to default state
Allocate at specific region
***************************
When memory allocation is in progress, LwMEM manager will start
at first free block and will loop through all regions until first free block of sufficient size has been found.
At this stage, application really does not have any control which region has been used for allocation.
Especially in the world of embedded systems, sometimes application uses external RAM device,
which are by definition slower than internal one. Let's take an example below.
.. figure:: ../static/images/structure_default.svg
:align: center
:alt: Region definition with one internal and two external regions
Region definition with one internal and two external regions
And code example:
.. literalinclude:: ../examples_src/example_regions_definitions.c
:language: c
:linenos:
:caption: Region definition with one internal and two external regions
For the sake of this example, let's say that:
* First region is in very fast internal RAM, coupled with CPU core
* Application shall use this only for small chunks of memory, frequently used, not to disturb external RAM interface
* Second and third regions are used for bigger RAM blocks used less frequently and interface is not overloaded when used
Size of first region is ``0x1000`` bytes.
When application tries to allocate (example) ``512`` bytes, it will find first free block in first region.
However, application wants to use (if possible) external RAM for this size of allocation.
There is a way to specify in which region memory shall be allocated, using extended functions.
.. literalinclude:: ../examples_src/example_alloc_from_region.c
:language: c
:linenos:
:caption: Allocate memory from specific region
.. tip::
Check :cpp:func:`lwmem_malloc_ex` for more information about parameters and return values
.. toctree::
:maxdepth: 2

View File

@ -7,5 +7,6 @@ User manual
:maxdepth: 2
how-it-works
instances
realloc-algorithm
thread-safety

View File

@ -0,0 +1,57 @@
.. _lwmem_instances:
LwMEM instances
===============
LwMEM architecture allows multiple instances, to completely isolate memory management between different memories.
This may allow separation of memory management at hardware level with different security feature.
By default, LwMEM has single instance created at library level, called *default instance*.
Default instance does need any special attention as it is embedded at library core,
instead application has to assign memory regions for the instance.
Every instance has:
* Instance control block
* Multiple regions assigned to each instance
.. note::
Control block of default instance is already initialized by library core,
hence it does not need any special attention at application layer.
.. figure:: ../static/images/lwmem_arch.svg
:align: center
:alt: LwMEM internal architecture with control block
LwMEM internal architecture with control block
Picture above shows internal architecture of LwMEM.
Control block holds info about first free block for allocation and other private data,
such as mutex handle when operating system is in use.
Yellow part of the image shows customized, application-defined, regions,
which must be manually assigned to the instance during application start-up.
Known example for assinging regions to LwMEM is shown below.
Default instance is used, therefore no special attention needs to be added
when assigning regions or allocating memory.
.. literalinclude:: ../examples_src/example_regions_definitions.c
:language: c
:linenos:
:caption: Definition and assignment of regions for default LwMEM instance
When application adds second LwMEM instance, then special functions with `_ex` must be used.
These allow application to specify for which LwMEM instance specific operation is intended.
.. tip::
Check :cpp:func:`lwmem_assignmem_ex` description for more information about input parameters.
.. literalinclude:: ../examples_src/example_regions_definitions_custom.c
:language: c
:linenos:
:caption: Definition and assignment of regions for custom LwMEM instance
.. toctree::
:maxdepth: 2

View File

@ -39,6 +39,8 @@ extern "C" {
#endif /* __cplusplus */
#include <string.h>
#include <stdint.h>
#include <limits.h>
#include "lwmem_config.h"
/**
@ -47,17 +49,31 @@ extern "C" {
* \{
*/
/* --- Memory unique part starts --- */
/**
* \brief Memory function/typedef prefix string
*
* It is used to change function names in zero time to easily re-use same library between applications.
* Use `#define LWMEM_PREF(x) my_prefix_ ## x` to change all function names to (for example) `my_prefix_lwmem_init`
*
* \note Modification of this macro must be done in header and source file aswell
* \brief Memory block structure
*/
#define LWMEM_PREF(x) lwmem_ ## x
/* --- Memory unique part ends --- */
typedef struct lwmem_block {
struct lwmem_block* next; /*!< Next free memory block on linked list.
Set to \ref LWMEM_BLOCK_ALLOC_MARK when block is allocated and in use */
size_t size; /*!< Size of block. MSB bit is set to `1` when block is allocated and in use,
or `0` when block is free */
} lwmem_block_t;
/**
* \brief LwMEM main structure
*/
typedef struct lwmem {
lwmem_block_t start_block; /*!< Holds beginning of memory allocation regions */
lwmem_block_t* end_block; /*!< Pointer to the last memory location in regions linked list */
size_t mem_available_bytes; /*!< Memory size available for allocation */
size_t mem_regions_count; /*!< Number of regions used for allocation */
#if LWMEM_CFG_OS || __DOXYGEN__
LWMEM_CFG_OS_MUTEX_HANDLE mutex; /*!< System mutex for OS */
#endif /* LWMEM_CFG_OS || __DOXYGEN__ */
#if defined(LWMEM_DEV) && !__DOXYGEN__
lwmem_block_t start_block_first_use; /*!< Value of start block for very first time */
#endif /* defined(LWMEM_DEV) && !__DOXYGEN__ */
} lwmem_t;
/**
* \brief Memory region descriptor
@ -65,18 +81,71 @@ extern "C" {
typedef struct {
void* start_addr; /*!< Region start address */
size_t size; /*!< Size of region in units of bytes */
} LWMEM_PREF(region_t);
} lwmem_region_t;
size_t LWMEM_PREF(assignmem)(const LWMEM_PREF(region_t)* regions, const size_t len);
void * LWMEM_PREF(malloc)(const size_t size);
void * LWMEM_PREF(calloc)(const size_t nitems, const size_t size);
void * LWMEM_PREF(realloc)(void* const ptr, const size_t size);
unsigned char LWMEM_PREF(realloc_s)(void** const ptr, const size_t size);
void LWMEM_PREF(free)(void* const ptr);
void LWMEM_PREF(free_s)(void** const ptr);
size_t lwmem_assignmem_ex(lwmem_t* const lw, const lwmem_region_t* regions, const size_t len);
void * lwmem_malloc_ex(lwmem_t* const lw, const lwmem_region_t* region, const size_t size);
void * lwmem_calloc_ex(lwmem_t* const lw, const lwmem_region_t* region, const size_t nitems, const size_t size);
void * lwmem_realloc_ex(lwmem_t* const lw, const lwmem_region_t* region, void* const ptr, const size_t size);
unsigned char lwmem_realloc_s_ex(lwmem_t* const lw, const lwmem_region_t* region, void** const ptr, const size_t size);
void lwmem_free_ex(lwmem_t* const lw, void* const ptr);
void lwmem_free_s_ex(lwmem_t* const lw, void** const ptr);
/**
* \note This is a wrapper for \ref lwmem_assignmem_ex function
* \param[in] regions: Array of regions with address and its size.
* Regions must be in increasing order (start address) and must not overlap in-between
* \param[in] len: Number of regions in array
*/
#define lwmem_assignmem(regions, len) lwmem_assignmem_ex(NULL, (regions), (len))
/**
* \note This is a wrapper for \ref lwmem_malloc_ex function.
* It operates in default LwMEM instance and uses first available region for memory operations
* \param[in] size: Size to allocate in units of bytes
*/
#define lwmem_malloc(size) lwmem_malloc_ex(NULL, NULL, (size))
/**
* \note This is a wrapper for \ref lwmem_calloc_ex function.
* It operates in default LwMEM instance and uses first available region for memory operations
* \param[in] nitems: Number of elements to be allocated
* \param[in] size: Size of each element, in units of bytes
*/
#define lwmem_calloc(nitems, size) lwmem_calloc_ex(NULL, NULL, (nitems), (size))
/**
* \note This is a wrapper for \ref lwmem_realloc_ex function
* \param[in] ptr: Memory block previously allocated with one of allocation functions.
* It may be set to `NULL` to create new clean allocation
* \param[in] size: Size of new memory to reallocate
*/
#define lwmem_realloc(ptr, size) lwmem_realloc_ex(NULL, NULL, (ptr), (size))
/**
* \note This is a wrapper for \ref lwmem_realloc_s_ex function
* \param[in] ptrptr: Pointer to pointer to allocated memory. Must not be set to `NULL`.
* If reallocation is successful, it modified where pointer points to,
* or sets it to `NULL` in case of `free` operation
* \param[in] size: New requested size
*/
#define lwmem_realloc_s(ptrptr, size) lwmem_realloc_s_ex(NULL, NULL, (ptrptr), (size))
/**
* \note This is a wrapper for \ref lwmem_free_ex function
* \param[in] ptr: Memory to free. `NULL` pointer is valid input
*/
#define lwmem_free(ptr) lwmem_free_ex(NULL, (ptr))
/**
* \note This is a wrapper for \ref lwmem_free_s_ex function
* \param[in] ptrptr: Pointer to pointer to allocated memory.
* When set to non `NULL`, pointer is freed and set to `NULL`
*/
#define lwmem_free_s(ptrptr) lwmem_free_s_ex(NULL, (ptrptr))
#if defined(LWMEM_DEV) && !__DOXYGEN__
unsigned char lwmem_debug_create_regions(LWMEM_PREF(region_t)** regs_out, size_t count, size_t size);
unsigned char lwmem_debug_create_regions(lwmem_region_t** regs_out, size_t count, size_t size);
void lwmem_debug_save_state(void);
void lwmem_debug_restore_to_saved(void);

View File

@ -38,17 +38,9 @@
#include "system/lwmem_sys.h"
#endif /* LWMEM_CFG_OS */
/* --- Memory unique part starts --- */
/* Prefix for all buffer functions and typedefs */
/**
* \brief Memory function/typedef prefix string
*/
#define LWMEM_PREF(x) lwmem_ ## x
#define LWMEM_MEMSET memset
#define LWMEM_MEMCPY memcpy
#define LWMEM_MEMMOVE memmove
/* --- Memory unique part ends --- */
/**
* \brief Transform alignment number (power of `2`) to bits
@ -124,71 +116,98 @@
*/
#define LWMEM_BLOCK_MIN_SIZE (LWMEM_BLOCK_META_SIZE)
/**
* \brief Get lwmem instance based on user input
* \param[in] _lw_: LwMEM instance
*/
#define LWMEM_GET_LW(_lw_) ((_lw_) != NULL ? (_lw_) : (&lwmem_default))
/**
* \brief Gets block before input block (marked as prev) and its previous free block
* \param[in] _lw_: LwMEM instance. Set to `NULL` to use default instance
* \param[in] in_b: Input block to find previous and its previous
* \param[in] in_pp: Previous previous of input block
* \param[in] in_p: Previous of input block
*/
#define LWMEM_GET_PREV_CURR_OF_BLOCK(in_b, in_pp, in_p) do { \
for ((in_pp) = NULL, (in_p) = &lwmem.start_block; \
#define LWMEM_GET_PREV_CURR_OF_BLOCK(_lw_, in_b, in_pp, in_p) do { \
for ((in_pp) = NULL, (in_p) = &(LWMEM_GET_LW(_lw_)->start_block); \
(in_p) != NULL && (in_p)->next < (in_b); \
(in_pp) = (in_p), (in_p) = (in_p)->next \
) {} \
} while (0)
#if LWMEM_CFG_OS
#define LWMEM_PROTECT() lwmem_sys_mutex_wait(&mutex)
#define LWMEM_UNPROTECT() lwmem_sys_mutex_release(&mutex)
#define LWMEM_PROTECT(lw) lwmem_sys_mutex_wait(&(LWMEM_GET_LW(lw)->mutex))
#define LWMEM_UNPROTECT(lw) lwmem_sys_mutex_release(&(LWMEM_GET_LW(lw)->mutex))
#else /* LWMEM_CFG_OS */
#define LWMEM_PROTECT()
#define LWMEM_UNPROTECT()
#define LWMEM_PROTECT(lw)
#define LWMEM_UNPROTECT(lw)
#endif /* !LWMEM_CFG_OS */
/**
* \brief Memory block structure
* \brief LwMEM data
*/
typedef struct lwmem_block {
struct lwmem_block* next; /*!< Next free memory block on linked list.
Set to \ref LWMEM_BLOCK_ALLOC_MARK when block is allocated and in use */
size_t size; /*!< Size of block. MSB bit is set to `1` when block is allocated and in use,
or `0` when block is free */
} lwmem_block_t;
static lwmem_t lwmem_default;
/**
* \brief Lwmem main structure
* \brief Get region aligned start address and aligned size
* \param[in] region: Region to check for size and address
* \param[out] msa: Memory start address output variable
* \param[out] ms: Memory size output variable
* \return `1` if region valid, `0` otherwise
*/
typedef struct lwmem {
lwmem_block_t start_block; /*!< Holds beginning of memory allocation regions */
lwmem_block_t* end_block; /*!< Pointer to the last memory location in regions linked list */
size_t mem_available_bytes; /*!< Memory size available for allocation */
size_t mem_regions_count; /*!< Number of regions used for allocation */
#if defined(LWMEM_DEV) && !__DOXYGEN__
lwmem_block_t start_block_first_use; /*!< Value of start block for very first time */
#endif /* defined(LWMEM_DEV) && !__DOXYGEN__ */
} lwmem_t;
static unsigned char
prv_get_region_addr_size(const lwmem_region_t* region, unsigned char** msa, size_t* ms) {
size_t mem_size;
unsigned char* mem_start_addr;
/**
* \brief Lwmem data
*/
static lwmem_t lwmem;
#if LWMEM_CFG_OS || __DOXYGEN__
static LWMEM_CFG_OS_MUTEX_HANDLE mutex; /*!< System mutex */
#endif /* LWMEM_CFG_OS || __DOXYGEN__ */
if (region == NULL || msa == NULL || ms == NULL) {
return 0;
}
*msa = NULL;
*ms = 0;
/* Check region size and align it to config bits */
mem_size = region->size & ~LWMEM_ALIGN_BITS; /* Size does not include lower bits */
if (mem_size < (2 * LWMEM_BLOCK_MIN_SIZE)) {
return 0;
}
/*
* Start address must be aligned to configuration
* Increase start address and decrease effective region size
*/
mem_start_addr = region->start_addr;
if (((size_t)mem_start_addr) & LWMEM_ALIGN_BITS) { /* Check alignment boundary */
mem_start_addr += ((size_t)LWMEM_CFG_ALIGN_NUM) - ((size_t)mem_start_addr & LWMEM_ALIGN_BITS);
mem_size -= (size_t)(mem_start_addr - LWMEM_TO_BYTE_PTR(region->start_addr));
}
/* Check final memory size */
if (mem_size >= (2 * LWMEM_BLOCK_MIN_SIZE)) {
/* Set pointers */
*msa = mem_start_addr;
*ms = mem_size;
return 1;
}
return 0;
}
/**
* \brief Insert free block to linked list of free blocks
* \param[in] lw: LwMEM instance. Set to `NULL` to use default instance
* \param[in] nb: New free block to insert into linked list
*/
static void
prv_insert_free_block(lwmem_block_t* nb) {
prv_insert_free_block(lwmem_t* const lw, lwmem_block_t* nb) {
lwmem_block_t* prev;
/*
* Try to find position to put new block in-between
* Search until all free block addresses are lower than entry block
*/
for (prev = &lwmem.start_block; prev != NULL && prev->next < nb; prev = prev->next) {}
for (prev = &(LWMEM_GET_LW(lw)->start_block); prev != NULL && prev->next < nb; prev = prev->next) {}
/* This is hard error with wrong memory usage */
if (prev == NULL) {
@ -214,14 +233,14 @@ prv_insert_free_block(lwmem_block_t* nb) {
*/
}
/*
/*
* Check if new block and next of previous create big contiguous block
* Do not merge with "end of region" indication (commented part of if statement)
*/
if (prev->next != NULL && prev->next->size > 0 /* Do not remove "end of region" indicator in each region */
&& (LWMEM_TO_BYTE_PTR(nb) + nb->size) == LWMEM_TO_BYTE_PTR(prev->next)) {
if (prev->next == lwmem.end_block) { /* Does it points to the end? */
nb->next = lwmem.end_block; /* Set end block pointer */
if (prev->next == LWMEM_GET_LW(lw)->end_block) {/* Does it points to the end? */
nb->next = LWMEM_GET_LW(lw)->end_block; /* Set end block pointer */
} else {
nb->size += prev->next->size; /* Expand of current block for size of next free block which is right behind new block */
nb->next = prev->next->next; /* Next free is pointed to the next one of previous next */
@ -241,12 +260,13 @@ prv_insert_free_block(lwmem_block_t* nb) {
/**
* \brief Split too big block and add it to list of free blocks
* \param[in] lw: LwMEM instance. Set to `NULL` to use default instance
* \param[in] block: Pointer to block with size already set
* \param[in] new_block_size: New block size to be set
* \return `1` if block splitted, `0` otherwise
*/
static unsigned char
prv_split_too_big_block(lwmem_block_t* block, size_t new_block_size) {
prv_split_too_big_block(lwmem_t* const lw, lwmem_block_t* block, size_t new_block_size) {
lwmem_block_t* next;
size_t block_size, is_alloc_bit;
unsigned char success = 0;
@ -264,8 +284,8 @@ prv_split_too_big_block(lwmem_block_t* block, size_t new_block_size) {
next->size = block_size - new_block_size; /* Modify block data */
block->size = new_block_size; /* Current size is now smaller */
lwmem.mem_available_bytes += next->size;/* Increase available bytes by new block size */
prv_insert_free_block(next); /* Add new block to the free list */
LWMEM_GET_LW(lw)->mem_available_bytes += next->size;/* Increase available bytes by new block size */
prv_insert_free_block(lw, next); /* Add new block to the free list */
success = 1;
} else {
@ -283,11 +303,14 @@ prv_split_too_big_block(lwmem_block_t* block, size_t new_block_size) {
/**
* \brief Private allocation function
* \param[in] lw: LwMEM instance. Set to `NULL` to use default instance
* \param[in] region: Pointer to region to allocate from.
* Set to `NULL` for any region
* \param[in] size: Application wanted size, excluding size of meta header
* \return Pointer to allocated memory, `NULL` otherwise
*/
static void *
prv_alloc(const size_t size) {
prv_alloc(lwmem_t* const lw, const lwmem_region_t* region, const size_t size) {
lwmem_block_t* prev, *curr;
void* retval = NULL;
@ -295,16 +318,58 @@ prv_alloc(const size_t size) {
const size_t final_size = LWMEM_ALIGN(size) + LWMEM_BLOCK_META_SIZE;
/* Check if initialized and if size is in the limits */
if (lwmem.end_block == NULL || final_size == LWMEM_BLOCK_META_SIZE || (final_size & LWMEM_ALLOC_BIT)) {
if (LWMEM_GET_LW(lw)->end_block == NULL || final_size == LWMEM_BLOCK_META_SIZE || (final_size & LWMEM_ALLOC_BIT) > 0) {
return NULL;
}
/* Try to find first block with at least `size` bytes of available memory */
for (prev = &lwmem.start_block, curr = prev->next; /* Start from very beginning and set curr as first empty block */
curr != NULL && curr->size < final_size;/* Loop until block size is smaller than requested */
prev = curr, curr = curr->next) { /* Go to next free block */
if (curr->next == NULL || curr == lwmem.end_block) {/* If no more blocks available */
return NULL; /* No sufficient memory available to allocate block of memory */
/* Set default values */
prev = &(LWMEM_GET_LW(lw)->start_block); /* Use pointer from custom lwmem block */
curr = prev->next; /* Curr represents first actual free block */
/*
* If region is not set to NULL,
* request for memory allocation came from specific region:
*
* - Start at the beginning like normal (from very first region)
* - Loop until free block is between region start addr and its size
*/
if (region != NULL) {
unsigned char* region_start_addr;
size_t region_size;
/* Get data about region */
if (!prv_get_region_addr_size(region, &region_start_addr, &region_size)) {
return NULL;
}
/*
* Scan all regions from lwmem and find first available block
* which is within address of region and is big enough
*/
for (; curr != NULL; prev = curr, curr = curr->next) {
/* Check bounds */
if (curr->next == NULL || curr == LWMEM_GET_LW(lw)->end_block) {
return NULL;
}
if ((unsigned char*)curr < (unsigned char *)region_start_addr) { /* Check if we reached region */
continue;
}
if ((unsigned char*)curr >= (unsigned char *)(region_start_addr + region_size)) { /* Check if we are out already */
return NULL;
}
if (curr->size >= final_size) {
break; /* Free block identified */
}
}
} else {
/*
* Try to find first block with at least `size` bytes of available memory
* Loop until size of current block is smaller than requested final size
*/
for (; curr != NULL && curr->size < final_size; prev = curr, curr = curr->next) {
if (curr->next == NULL || curr == LWMEM_GET_LW(lw)->end_block) {/* If no more blocks available */
return NULL; /* No sufficient memory available to allocate block of memory */
}
}
}
@ -319,8 +384,8 @@ prv_alloc(const size_t size) {
/* curr block is now removed from linked list */
lwmem.mem_available_bytes -= curr->size; /* Decrease available bytes by allocated block size */
prv_split_too_big_block(curr, final_size); /* Split block if it is too big */
LWMEM_GET_LW(lw)->mem_available_bytes -= curr->size;/* Decrease available bytes by allocated block size */
prv_split_too_big_block(lw, curr, final_size); /* Split block if it is too big */
LWMEM_BLOCK_SET_ALLOC(curr); /* Set block as allocated */
return retval;
@ -328,182 +393,20 @@ prv_alloc(const size_t size) {
/**
* \brief Free input pointer
* \param[in] lw: LwMEM instance. Set to `NULL` to use default instance
* \param[in] ptr: Input pointer to free
*/
static void
prv_free(void* const ptr) {
prv_free(lwmem_t* const lw, void* const ptr) {
lwmem_block_t* const block = LWMEM_GET_BLOCK_FROM_PTR(ptr);
if (LWMEM_BLOCK_IS_ALLOC(block)) { /* Check if block is valid */
block->size &= ~LWMEM_ALLOC_BIT; /* Clear allocated bit indication */
lwmem.mem_available_bytes += block->size; /* Increase available bytes */
prv_insert_free_block(block); /* Put block back to list of free block */
LWMEM_GET_LW(lw)->mem_available_bytes += block->size; /* Increase available bytes */
prv_insert_free_block(lw, block); /* Put block back to list of free block */
}
}
/**
* \brief Initializes and assigns user regions for memory used by allocator algorithm
* \param[in] regions: Array of regions with address and its size.
* Regions must be in increasing order (start address) and must not overlap in-between
* \param[in] len: Number of regions in array
* \return `0` on failure, number of final regions used for memory manager on success
* \note This function is not thread safe when used with operating system.
* It must be called only once to setup memory regions
*/
size_t
LWMEM_PREF(assignmem)(const LWMEM_PREF(region_t)* regions, const size_t len) {
unsigned char* mem_start_addr;
size_t mem_size;
lwmem_block_t* first_block, *prev_end_block;
if (lwmem.end_block != NULL /* Init function may only be called once */
|| (((size_t)LWMEM_CFG_ALIGN_NUM) & (((size_t)LWMEM_CFG_ALIGN_NUM) - 1))/* Must be power of 2 */
|| regions == NULL || len == 0
#if LWMEM_CFG_OS
|| lwmem_sys_mutex_isvalid(&mutex) /* Check if mutex valid already */
#endif /* LWMEM_CFG_OS */
) { /* Check inputs */
return 0;
}
#if LWMEM_CFG_OS
if (!lwmem_sys_mutex_create(&mutex)) {
return 0;
}
#endif /* LWMEM_CFG_OS */
/* Ensure regions are growing linearly and do not overlap in between */
mem_start_addr = (void *)0;
mem_size = 0;
for (size_t i = 0; i < len; ++i) {
/* New region(s) must be higher (in address space) than previous one */
if ((mem_start_addr + mem_size) > LWMEM_TO_BYTE_PTR(regions[i].start_addr)) {
return 0;
}
/* Save new values for next try */
mem_start_addr = regions[i].start_addr;
mem_size = regions[i].size;
}
for (size_t i = 0; i < len; ++i, ++regions) {
/*
* Check region start address and align start address accordingly
* It is ok to cast to size_t, even if pointer could be larger
* Important is to check lower-bytes (and bits)
*/
mem_size = regions->size & ~LWMEM_ALIGN_BITS; /* Size does not include lower bits */
if (mem_size < (2 * LWMEM_BLOCK_MIN_SIZE)) {
continue; /* Ignore region, go to next one */
}
/*
* Start address must be aligned to configuration
* Increase start address and decrease effective region size
*/
mem_start_addr = regions->start_addr;
if (((size_t)mem_start_addr) & LWMEM_ALIGN_BITS) { /* Check alignment boundary */
mem_start_addr += ((size_t)LWMEM_CFG_ALIGN_NUM) - ((size_t)mem_start_addr & LWMEM_ALIGN_BITS);
mem_size -= (size_t)(mem_start_addr - LWMEM_TO_BYTE_PTR(regions->start_addr));
}
/* Ensure region size has enough memory after all the alignment checks */
if (mem_size < (2 * LWMEM_BLOCK_MIN_SIZE)) {
continue; /* Ignore region, go to next one */
}
/*
* If end_block == NULL, this indicates first iteration.
* In first indication application shall set start_block and never again
* end_block value holds
*/
if (lwmem.end_block == NULL) {
/*
* Next entry of start block is first region
* It points to beginning of region data
* In the later step(s) first block is manually set on top of memory region
*/
lwmem.start_block.next = (void *)mem_start_addr;
lwmem.start_block.size = 0; /* Size of dummy start block is zero */
}
/* Save current end block status as it is used later for linked list insertion */
prev_end_block = lwmem.end_block;
/* Put end block to the end of the region with size = 0 */
lwmem.end_block = (void *)(mem_start_addr + mem_size - LWMEM_BLOCK_META_SIZE);
lwmem.end_block->next = NULL; /* End block in region does not have next entry */
lwmem.end_block->size = 0; /* Size of end block is zero */
/*
* Create memory region first block.
*
* First block meta size includes size of metadata too
* Subtract MEM_BLOCK_META_SIZE as there is one more block (end_block) at the end of region
*
* Actual maximal available size for application in the region is mem_size - 2 * MEM_BLOCK_META_SIZE
*/
first_block = (void *)mem_start_addr;
first_block->next = lwmem.end_block; /* Next block of first is last block */
first_block->size = mem_size - LWMEM_BLOCK_META_SIZE;
/* Check if previous regions exist by checking previous end block state */
if (prev_end_block != NULL) {
prev_end_block->next = first_block; /* End block of previous region now points to start of current region */
}
lwmem.mem_available_bytes += first_block->size; /* Increase number of available bytes */
++lwmem.mem_regions_count; /* Increase number of used regions */
}
#if defined(LWMEM_DEV)
/* Copy default state of start block */
LWMEM_MEMCPY(&lwmem.start_block_first_use, &lwmem.start_block, sizeof(lwmem.start_block));
#endif /* defined(LWMEM_DEV) */
return lwmem.mem_regions_count; /* Return number of regions used by manager */
}
/**
* \brief Allocate memory of requested size
* \note Function declaration is in-line with standard C function `malloc`
* \param[in] size: Number of bytes to allocate
* \return Pointer to allocated memory on success, `NULL` otherwise
* \note This function is thread safe when \ref LWMEM_CFG_OS is enabled
*/
void *
LWMEM_PREF(malloc)(const size_t size) {
void* ptr;
LWMEM_PROTECT();
ptr = prv_alloc(size);
LWMEM_UNPROTECT();
return ptr;
}
/**
* \brief Allocate contiguous block of memory for requested number of items and its size.
*
* It resets allocated block of memory to zero if allocation is successful
*
* \note Function declaration is in-line with standard C function `calloc`
* \param[in] nitems: Number of elements to be allocated
* \param[in] size: Size of each element, in units of bytes
* \return Pointer to allocated memory on success, `NULL` otherwise
* \note This function is thread safe when \ref LWMEM_CFG_OS is enabled
*/
void *
LWMEM_PREF(calloc)(const size_t nitems, const size_t size) {
void* ptr;
const size_t s = size * nitems;
LWMEM_PROTECT();
if ((ptr = prv_alloc(s)) != NULL) {
LWMEM_MEMSET(ptr, 0x00, s);
}
LWMEM_UNPROTECT();
return ptr;
}
/**
* \brief Reallocates already allocated memory with new size
*
@ -514,39 +417,35 @@ LWMEM_PREF(calloc)(const size_t nitems, const size_t size) {
* - `ptr != NULL; size == 0`: Function frees memory, equivalent to `free(ptr)`
* - `ptr != NULL; size > 0`: Function tries to allocate new memory of copy content before returning pointer on success
*
* \note Function declaration is in-line with standard C function `realloc`
*
* \param[in] lw: LwMEM instance. Set to `NULL` to use default instance
* \param[in] region: Pointer to region to allocate from.
* Set to `NULL` for any region
* \param[in] ptr: Memory block previously allocated with one of allocation functions.
* It may be set to `NULL` to create new clean allocation
* \param[in] size: Size of new memory to reallocate
* \return Pointer to allocated memory on success, `NULL` otherwise
* \note This function is thread safe when \ref LWMEM_CFG_OS is enabled
*/
void *
LWMEM_PREF(realloc)(void* const ptr, const size_t size) {
prv_realloc(lwmem_t* const lw, const lwmem_region_t* region, void* const ptr, const size_t size) {
lwmem_block_t* block, *prevprev, *prev;
size_t block_size; /* Holds size of input block (ptr), including metadata size */
const size_t final_size = LWMEM_ALIGN(size) + LWMEM_BLOCK_META_SIZE;/* Holds size of new requested block size, including metadata size */
void* retval; /* Return pointer, used with LWMEM_RETURN macro */
/* Protect lwmem core */
#define LWMEM_RETURN(x) do { retval = (x); goto ret; } while (0)
LWMEM_PROTECT();
/* Check optional input parameters */
if (size == 0) {
if (ptr != NULL) {
prv_free(ptr);
prv_free(lw, ptr);
}
LWMEM_RETURN(NULL);
return NULL;
}
if (ptr == NULL) {
LWMEM_RETURN(prv_alloc(size));
return prv_alloc(lw, NULL, size);
}
/* Try to reallocate existing pointer */
if ((size & LWMEM_ALLOC_BIT) || (final_size & LWMEM_ALLOC_BIT)) {
LWMEM_RETURN(NULL);
return NULL;
}
/* Process existing block */
@ -556,7 +455,7 @@ LWMEM_PREF(realloc)(void* const ptr, const size_t size) {
/* Check current block size is the same as new requested size */
if (block_size == final_size) {
LWMEM_RETURN(ptr); /* Just return pointer, nothing to do */
return ptr; /* Just return pointer, nothing to do */
}
/*
@ -579,7 +478,7 @@ LWMEM_PREF(realloc)(void* const ptr, const size_t size) {
*/
if (final_size < block_size) {
if ((block_size - final_size) >= LWMEM_BLOCK_MIN_SIZE) {
prv_split_too_big_block(block, final_size); /* Split block if it is too big */
prv_split_too_big_block(lw, block, final_size); /* Split block if it is too big */
} else {
/*
* It is not possible to create new empty block at the end of input block
@ -589,7 +488,7 @@ LWMEM_PREF(realloc)(void* const ptr, const size_t size) {
*/
/* Find free blocks before input block */
LWMEM_GET_PREV_CURR_OF_BLOCK(block, prevprev, prev);
LWMEM_GET_PREV_CURR_OF_BLOCK(lw, block, prevprev, prev);
/* Check if current block and next free are connected */
if ((LWMEM_TO_BYTE_PTR(block) + block_size) == LWMEM_TO_BYTE_PTR(prev->next)
@ -602,23 +501,23 @@ LWMEM_PREF(realloc)(void* const ptr, const size_t size) {
prev->next = (void *)(LWMEM_TO_BYTE_PTR(prev->next) - (block_size - final_size));
prev->next->size = tmp_size + (block_size - final_size);
prev->next->next = tmp_next;
lwmem.mem_available_bytes += block_size - final_size; /* Increase available bytes by increase of free block */
LWMEM_GET_LW(lw)->mem_available_bytes += block_size - final_size; /* Increase available bytes by increase of free block */
block->size = final_size; /* Block size is requested size */
}
}
LWMEM_BLOCK_SET_ALLOC(block); /* Set block as allocated */
LWMEM_RETURN(ptr); /* Return existing pointer */
return ptr; /* Return existing pointer */
}
/* New requested size is bigger than current block size is */
/* Find last free (and its previous) block, located just before input block */
LWMEM_GET_PREV_CURR_OF_BLOCK(block, prevprev, prev);
LWMEM_GET_PREV_CURR_OF_BLOCK(lw, block, prevprev, prev);
/* If entry could not be found, there is a hard error */
if (prev == NULL) {
LWMEM_RETURN(NULL);
return NULL;
}
/* Order of variables is: | prevprev ---> prev --->--->--->--->--->--->--->--->--->---> prev->next | */
@ -633,13 +532,13 @@ LWMEM_PREF(realloc)(void* const ptr, const size_t size) {
* Merge blocks together by increasing current block with size of next free one
* and remove next free from list of free blocks
*/
lwmem.mem_available_bytes -= prev->next->size; /* For now decrease effective available bytes */
LWMEM_GET_LW(lw)->mem_available_bytes -= prev->next->size; /* For now decrease effective available bytes */
block->size = block_size + prev->next->size;/* Increase effective size of new block */
prev->next = prev->next->next; /* Set next to next's next, effectively remove expanded block from free list */
prv_split_too_big_block(block, final_size); /* Split block if it is too big */
prv_split_too_big_block(lw, block, final_size); /* Split block if it is too big */
LWMEM_BLOCK_SET_ALLOC(block); /* Set block as allocated */
LWMEM_RETURN(ptr); /* Return existing pointer */
return ptr; /* Return existing pointer */
}
/*
@ -663,14 +562,14 @@ LWMEM_PREF(realloc)(void* const ptr, const size_t size) {
*/
LWMEM_MEMMOVE(new_data_ptr, old_data_ptr, block_size);
lwmem.mem_available_bytes -= prev->size;/* For now decrease effective available bytes */
LWMEM_GET_LW(lw)->mem_available_bytes -= prev->size;/* For now decrease effective available bytes */
prev->size += block_size; /* Increase size of input block size */
prevprev->next = prev->next; /* Remove prev from free list as it is now being used for allocation together with existing block */
block = prev; /* Move block pointer to previous one */
prv_split_too_big_block(block, final_size); /* Split block if it is too big */
prv_split_too_big_block(lw, block, final_size); /* Split block if it is too big */
LWMEM_BLOCK_SET_ALLOC(block); /* Set block as allocated */
LWMEM_RETURN(new_data_ptr); /* Return new data ptr */
return new_data_ptr; /* Return new data ptr */
}
/*
@ -699,18 +598,18 @@ LWMEM_PREF(realloc)(void* const ptr, const size_t size) {
*/
LWMEM_MEMMOVE(new_data_ptr, old_data_ptr, block_size); /* Copy old buffer size to new location */
lwmem.mem_available_bytes -= prev->size + prev->next->size; /* Decrease effective available bytes for free blocks before and after input block */
LWMEM_GET_LW(lw)->mem_available_bytes -= prev->size + prev->next->size; /* Decrease effective available bytes for free blocks before and after input block */
prev->size += block_size + prev->next->size;/* Increase size of new block by size of 2 free blocks */
prevprev->next = prev->next->next; /* Remove free block before current one and block after current one from linked list (remove 2) */
block = prev; /* Previous block is now current */
prv_split_too_big_block(block, final_size); /* Split block if it is too big */
prv_split_too_big_block(lw, block, final_size); /* Split block if it is too big */
LWMEM_BLOCK_SET_ALLOC(block); /* Set block as allocated */
LWMEM_RETURN(new_data_ptr); /* Return new data ptr */
return new_data_ptr; /* Return new data ptr */
}
} else {
/* Hard error. Input pointer is not NULL and block is not considered allocated */
LWMEM_RETURN(NULL);
return NULL;
}
/*
@ -722,25 +621,205 @@ LWMEM_PREF(realloc)(void* const ptr, const size_t size) {
*
* Final solution is to find completely new empty block of sufficient size and copy content from old one to new one
*/
retval = prv_alloc(size); /* Try to allocate new block */
retval = prv_alloc(lw, NULL, size); /* Try to allocate new block */
if (retval != NULL) {
block_size = (block->size & ~LWMEM_ALLOC_BIT) - LWMEM_BLOCK_META_SIZE; /* Get application size from input pointer */
LWMEM_MEMCPY(retval, ptr, size > block_size ? block_size : size); /* Copy content to new allocated block */
prv_free(ptr); /* Free input pointer */
prv_free(lw, ptr); /* Free input pointer */
}
LWMEM_RETURN(retval);
ret:
LWMEM_UNPROTECT();
return retval;
}
/**
* \brief Safe version of classic realloc function
* \brief Initializes and assigns user regions for memory used by allocator algorithm
* \param[in] lw: LwMEM instance. Set to `NULL` to use default instance
* \param[in] regions: Array of regions with address and its size.
* Regions must be in increasing order (start address) and must not overlap in-between
* \param[in] len: Number of regions in array
* \return `0` on failure, number of final regions used for memory manager on success
* \note This function is not thread safe when used with operating system.
* It must be called only once to setup memory regions
*/
size_t
lwmem_assignmem_ex(lwmem_t* const lw, const lwmem_region_t* regions, const size_t len) {
unsigned char* mem_start_addr;
size_t mem_size;
lwmem_block_t* first_block, *prev_end_block;
if (LWMEM_GET_LW(lw)->end_block != NULL /* Init function may only be called once per lwmem instance */
|| ((((size_t)LWMEM_CFG_ALIGN_NUM) & (((size_t)LWMEM_CFG_ALIGN_NUM) - 1)) > 0) /* Must be power of 2 */
|| regions == NULL || len == 0
#if LWMEM_CFG_OS
|| lwmem_sys_mutex_isvalid(&(LWMEM_GET_LW(lw)->mutex)) /* Check if mutex valid already */
#endif /* LWMEM_CFG_OS */
) { /* Check inputs */
return 0;
}
#if LWMEM_CFG_OS
if (!lwmem_sys_mutex_create(&(LWMEM_GET_LW(lw)->mutex))) {
return 0;
}
#endif /* LWMEM_CFG_OS */
/* Ensure regions are growing linearly and do not overlap in between */
mem_start_addr = (void *)0;
mem_size = 0;
for (size_t i = 0; i < len; ++i) {
/* New region(s) must be higher (in address space) than previous one */
if ((mem_start_addr + mem_size) > LWMEM_TO_BYTE_PTR(regions[i].start_addr)) {
return 0;
}
/* Save new values for next try */
mem_start_addr = regions[i].start_addr;
mem_size = regions[i].size;
}
for (size_t i = 0; i < len; ++i, ++regions) {
/* Get region start address and size */
if (!prv_get_region_addr_size(regions, &mem_start_addr, &mem_size)) {
continue;
}
/*
* If end_block == NULL, this indicates first iteration.
* In first indication application shall set start_block and never again
* end_block value holds
*/
if (LWMEM_GET_LW(lw)->end_block == NULL) {
/*
* Next entry of start block is first region
* It points to beginning of region data
* In the later step(s) first block is manually set on top of memory region
*/
LWMEM_GET_LW(lw)->start_block.next = (void *)mem_start_addr;
LWMEM_GET_LW(lw)->start_block.size = 0; /* Size of dummy start block is zero */
}
/* Save current end block status as it is used later for linked list insertion */
prev_end_block = LWMEM_GET_LW(lw)->end_block;
/* Put end block to the end of the region with size = 0 */
LWMEM_GET_LW(lw)->end_block = (void *)(mem_start_addr + mem_size - LWMEM_BLOCK_META_SIZE);
LWMEM_GET_LW(lw)->end_block->next = NULL; /* End block in region does not have next entry */
LWMEM_GET_LW(lw)->end_block->size = 0; /* Size of end block is zero */
/*
* Create memory region first block.
*
* First block meta size includes size of metadata too
* Subtract MEM_BLOCK_META_SIZE as there is one more block (end_block) at the end of region
*
* Actual maximal available size for application in the region is mem_size - 2 * MEM_BLOCK_META_SIZE
*/
first_block = (void *)mem_start_addr;
first_block->next = LWMEM_GET_LW(lw)->end_block;/* Next block of first is last block */
first_block->size = mem_size - LWMEM_BLOCK_META_SIZE;
/* Check if previous regions exist by checking previous end block state */
if (prev_end_block != NULL) {
prev_end_block->next = first_block; /* End block of previous region now points to start of current region */
}
LWMEM_GET_LW(lw)->mem_available_bytes += first_block->size; /* Increase number of available bytes */
++LWMEM_GET_LW(lw)->mem_regions_count; /* Increase number of used regions */
}
#if defined(LWMEM_DEV)
/* Copy default state of start block */
LWMEM_MEMCPY(&lwmem_default.start_block_first_use, &lwmem_default.start_block, sizeof(lwmem_default.start_block));
#endif /* defined(LWMEM_DEV) */
return LWMEM_GET_LW(lw)->mem_regions_count; /* Return number of regions used by manager */
}
/**
* \brief Allocate memory of requested size in specific lwmem instance and optional region.
* \note This is an extended malloc version function declaration to support advanced features
* \param[in] lw: LwMEM instance. Set to `NULL` to use default instance
* \param[in] region: Optional region instance within LwMEM instance to force allocation from.
* Set to `NULL` to use any region within LwMEM instance
* \param[in] size: Number of bytes to allocate
* \return Pointer to allocated memory on success, `NULL` otherwise
* \note This function is thread safe when \ref LWMEM_CFG_OS is enabled
*/
void *
lwmem_malloc_ex(lwmem_t* const lw, const lwmem_region_t* region, const size_t size) {
void* ptr;
LWMEM_PROTECT(lw);
ptr = prv_alloc(lw, region, size);
LWMEM_UNPROTECT(lw);
return ptr;
}
/**
* \brief Allocate contiguous block of memory for requested number of items and its size
* in specific lwmem instance and region.
*
* It resets allocated block of memory to zero if allocation is successful
* \note This is an extended calloc version function declaration to support advanced features
* \param[in] lw: LwMEM instance. Set to `NULL` to use default instance
* \param[in] region: Optional region instance within LwMEM instance to force allocation from.
* Set to `NULL` to use any region within LwMEM instance
* \param[in] nitems: Number of elements to be allocated
* \param[in] size: Size of each element, in units of bytes
* \return Pointer to allocated memory on success, `NULL` otherwise
* \note This function is thread safe when \ref LWMEM_CFG_OS is enabled
*/
void *
lwmem_calloc_ex(lwmem_t* const lw, const lwmem_region_t* region, const size_t nitems, const size_t size) {
void* ptr;
const size_t s = size * nitems;
LWMEM_PROTECT(lw);
if ((ptr = prv_alloc(lw, region, s)) != NULL) {
LWMEM_MEMSET(ptr, 0x00, s);
}
LWMEM_UNPROTECT(lw);
return ptr;
}
/**
* \brief Reallocates already allocated memory with new size in specific lwmem instance and region.
*
* \note This function may only be used with allocations returned by any of `_from` API functions
*
* Function behaves differently, depends on input parameter of `ptr` and `size`:
*
* - `ptr == NULL; size == 0`: Function returns `NULL`, no memory is allocated or freed
* - `ptr == NULL; size > 0`: Function tries to allocate new block of memory with `size` length, equivalent to `malloc(region, size)`
* - `ptr != NULL; size == 0`: Function frees memory, equivalent to `free(ptr)`
* - `ptr != NULL; size > 0`: Function tries to allocate new memory of copy content before returning pointer on success
*
* \param[in] lw: LwMEM instance. Set to `NULL` to use default instance
* \param[in] region: Pointer to region to allocate from.
* Set to `NULL` to use any region within LwMEM instance.
* Instance must be the same as used during allocation procedure
* \param[in] ptr: Memory block previously allocated with one of allocation functions.
* It may be set to `NULL` to create new clean allocation
* \param[in] size: Size of new memory to reallocate
* \return Pointer to allocated memory on success, `NULL` otherwise
* \note This function is thread safe when \ref LWMEM_CFG_OS is enabled
*/
void *
lwmem_realloc_ex(lwmem_t* const lw, const lwmem_region_t* region, void* const ptr, const size_t size) {
void* p;
LWMEM_PROTECT(lw);
p = prv_realloc(lw, region, ptr, size);
LWMEM_UNPROTECT(lw);
return p;
}
/**
* \brief Safe version of realloc_ex function.
*
* After memory is reallocated, input pointer automatically points to new memory
* to prevent use of dangling pointers. When reallocation is not successful,
* original pointer is not modified and application still has control of it.
*
* It is advised to use this function when reallocating memory.
* After memory is reallocated, input pointer automatically points to new memory
* to prevent use of dangling pointers.
*
* Function behaves differently, depends on input parameter of `ptr` and `size`:
*
@ -750,6 +829,10 @@ ret:
* - `*ptr != NULL; size == 0`: Function frees memory, equivalent to `free(ptr)`, sets input pointer pointing to `NULL`
* - `*ptr != NULL; size > 0`: Function tries to reallocate existing pointer with new size and copy content to new block
*
* \param[in] lw: LwMEM instance. Set to `NULL` to use default instance
* \param[in] region: Pointer to region to allocate from.
* Set to `NULL` to use any region within LwMEM instance.
* Instance must be the same as used during allocation procedure
* \param[in] ptr: Pointer to pointer to allocated memory. Must not be set to `NULL`.
* If reallocation is successful, it modified where pointer points to,
* or sets it to `NULL` in case of `free` operation
@ -758,7 +841,7 @@ ret:
* \note This function is thread safe when \ref LWMEM_CFG_OS is enabled
*/
unsigned char
LWMEM_PREF(realloc_s)(void** const ptr, const size_t size) {
lwmem_realloc_s_ex(lwmem_t* const lw, const lwmem_region_t* region, void** const ptr, const size_t size) {
void* new_ptr;
/*
@ -770,7 +853,7 @@ LWMEM_PREF(realloc_s)(void** const ptr, const size_t size) {
return 0;
}
new_ptr = LWMEM_PREF(realloc)(*ptr, size); /* Try to reallocate existing pointer */
new_ptr = lwmem_realloc_ex(lw, region, *ptr, size); /* Try to reallocate existing pointer */
if (new_ptr != NULL) {
*ptr = new_ptr;
} else if (size == 0) { /* size == 0 means free input memory */
@ -782,34 +865,40 @@ LWMEM_PREF(realloc_s)(void** const ptr, const size_t size) {
/**
* \brief Free previously allocated memory using one of allocation functions
* \note Function declaration is in-line with standard C function `free`
* in specific lwmem instance.
* \param[in] lw: LwMEM instance. Set to `NULL` to use default instance.
* Instance must be the same as used during allocation procedure
* \note This is an extended free version function declaration to support advanced features
* \param[in] ptr: Memory to free. `NULL` pointer is valid input
* \note This function is thread safe when \ref LWMEM_CFG_OS is enabled
*/
void
LWMEM_PREF(free)(void* const ptr) {
LWMEM_PROTECT();
prv_free(ptr); /* Free pointer */
LWMEM_UNPROTECT();
lwmem_free_ex(lwmem_t* const lw, void* const ptr) {
LWMEM_PROTECT(lw);
prv_free(lw, ptr);
LWMEM_UNPROTECT(lw);
}
/**
* \brief Safe version of free function
*
* It is advised to use this function when freeing memory.
* After memory is freed, input pointer is safely set to `NULL`
* to prevent use of dangling pointers.
*
* It is advised to use this function when freeing memory.
*
* \param[in] lw: LwMEM instance. Set to `NULL` to use default instance.
* Instance must be the same as used during allocation procedure
* \param[in] ptr: Pointer to pointer to allocated memory.
* When set to non `NULL`, pointer is freed and set to `NULL`
* \note This function is thread safe when \ref LWMEM_CFG_OS is enabled
*/
void
LWMEM_PREF(free_s)(void** const ptr) {
lwmem_free_s_ex(lwmem_t* const lw, void** const ptr) {
if (ptr != NULL && *ptr != NULL) {
LWMEM_PROTECT();
prv_free(*ptr);
LWMEM_UNPROTECT();
LWMEM_PROTECT(lw);
prv_free(lw, *ptr);
LWMEM_UNPROTECT(lw);
*ptr = NULL;
}
}
@ -828,10 +917,10 @@ static lwmem_region_t* regions_orig;
static lwmem_region_t* regions_temp;
static size_t regions_count;
static LWMEM_PREF(region_t) *
static lwmem_region_t *
create_regions(size_t count, size_t size) {
LWMEM_PREF(region_t)* regions;
LWMEM_PREF(region_t) tmp;
lwmem_region_t* regions;
lwmem_region_t tmp;
/* Allocate pointer structure */
regions = malloc(count * sizeof(*regions));
@ -866,16 +955,16 @@ static void
print_block(size_t i, lwmem_block_t* block) {
size_t is_free, block_size;
is_free = (block->size & LWMEM_ALLOC_BIT) == 0 && block != &lwmem.start_block_first_use && block->size > 0;
is_free = (block->size & LWMEM_ALLOC_BIT) == 0 && block != &lwmem_default.start_block_first_use && block->size > 0;
block_size = block->size & ~LWMEM_ALLOC_BIT;
printf("| %5d | %12p | %6d | %4d | %16d |",
printf("| %5d | %16p | %6d | %4d | %16d |",
(int)i,
block,
(int)is_free,
(int)block_size,
(int)(is_free ? (block_size - LWMEM_BLOCK_META_SIZE) : 0));
if (block == &lwmem.start_block_first_use) {
if (block == &lwmem_default.start_block_first_use) {
printf(" Start block ");
} else if (block_size == 0) {
printf(" End of region ");
@ -894,14 +983,13 @@ lwmem_debug_print(unsigned char print_alloc, unsigned char print_free) {
size_t block_size;
lwmem_block_t* block;
printf("|-------|------------------|--------|------|------------------|-----------------|\r\n");
printf("| Block | Address | IsFree | Size | MaxUserAllocSize | Meta |\r\n");
printf("|-------|------------------|--------|------|------------------|-----------------|\r\n");
printf("|-------|--------------|--------|------|------------------|-----------------|\r\n");
printf("| Block | Address | IsFree | Size | MaxUserAllocSize | Meta |\r\n");
printf("|-------|--------------|--------|------|------------------|-----------------|\r\n");
block = &lwmem.start_block_first_use;
print_block(0, &lwmem.start_block_first_use);
printf("|-------|--------------|--------|------|------------------|-----------------|\r\n");
block = &lwmem_default.start_block_first_use;
print_block(0, &lwmem_default.start_block_first_use);
printf("|-------|------------------|--------|------|------------------|-----------------|\r\n");
for (size_t i = 0, j = 1; i < regions_count; ++i) {
block = regions_orig[i].start_addr;
@ -917,7 +1005,7 @@ lwmem_debug_print(unsigned char print_alloc, unsigned char print_free) {
break;
}
}
printf("|-------|--------------|--------|------|------------------|-----------------|\r\n");
printf("|-------|------------------|--------|------|------------------|-----------------|\r\n");
}
}
@ -937,7 +1025,7 @@ lwmem_debug_create_regions(lwmem_region_t** regs_out, size_t count, size_t size)
void
lwmem_debug_save_state(void) {
memcpy(&lwmem_temp, &lwmem, sizeof(lwmem_temp));
memcpy(&lwmem_temp, &lwmem_default, sizeof(lwmem_temp));
for (size_t i = 0; i < regions_count; ++i) {
memcpy(regions_temp[i].start_addr, regions_orig[i].start_addr, regions_temp[i].size);
}
@ -946,7 +1034,7 @@ lwmem_debug_save_state(void) {
void
lwmem_debug_restore_to_saved(void) {
memcpy(&lwmem, &lwmem_temp, sizeof(lwmem_temp));
memcpy(&lwmem_default, &lwmem_temp, sizeof(lwmem_temp));
for (size_t i = 0; i < regions_count; ++i) {
memcpy(regions_orig[i].start_addr, regions_temp[i].start_addr, regions_temp[i].size);
}

185
tests/lwmem_test.c Normal file
View File

@ -0,0 +1,185 @@
#include "lwmem/lwmem.h"
/* Assert check */
#define ASSERT(x) do { \
if (!(x)) { \
printf("Assert on line %d failed with condition (" # x ")\r\n", (int)__LINE__); \
} else {\
printf("Assert on line %d passed with condition (" # x ")\r\n", (int)__LINE__); \
}\
} while (0)
/********************************************/
/* Test case helpers */
#define IS_ALLOC_IN_REGION(ptr, region) ASSERT((unsigned char *)(ptr) >= (region)->start_addr && (unsigned char *)(ptr) < ((unsigned char *)(region)->start_addr + (region)->size))
/********************************************/
/* Configuration for default lwmem instance */
/* Region memory declaration */
uint8_t lw_mem1[1024], lw_mem2[256], lw_mem3[128];
/* Regions descriptor */
lwmem_region_t
lw_regions[] = {
{ lw_mem3, sizeof(lw_mem3) },
{ lw_mem2, sizeof(lw_mem2) },
{ lw_mem1, sizeof(lw_mem1) },
};
/********************************************/
/********************************************/
/* Configuration for custom lwmem instance */
/* LwMEM instance */
lwmem_t lw_c;
/* Region memory declaration */
uint8_t lw_c_mem1[1024], lw_c_mem2[256], lw_c_mem3[128];
/* Regions descriptor */
lwmem_region_t
lw_c_regions[] = {
{ lw_c_mem3, sizeof(lw_c_mem3) },
{ lw_c_mem2, sizeof(lw_c_mem2) },
{ lw_c_mem1, sizeof(lw_c_mem1) },
};
/********************************************/
void
lwmem_test_run(void) {
void* ptr_1, * ptr_2, * ptr_3, * ptr_4;
void* ptr_c_1, * ptr_c_2, * ptr_c_3, * ptr_c_4;
/* Initialize default lwmem instance */
/* Use one of 2 possible function calls: */
lwmem_assignmem(lw_regions, sizeof(lw_regions) / sizeof(lw_regions[0]));
//lwmem_assignmem_ex(NULL, lw_regions, sizeof(lw_regions) / sizeof(lw_regions[0]));
/* Initialize another, custom instance */
lwmem_assignmem_ex(&lw_c, lw_c_regions, sizeof(lw_c_regions) / sizeof(lw_c_regions[0]));
/* Regions initialized... */
/********************************************/
/* Run tests on default region */
/********************************************/
/* Allocation of 64 bytes must in in first region */
ptr_1 = lwmem_malloc(64);
IS_ALLOC_IN_REGION(ptr_1, &lw_regions[0]);
/* Allocation of 256 bytes can only be in 3rd region */
ptr_2 = lwmem_malloc(256);
IS_ALLOC_IN_REGION(ptr_2, &lw_regions[2]);
/* Allocation of 128 bytes can be in second or third region (depends on memory availability),
but in case of these tests it can be (and should be) in second region */
ptr_3 = lwmem_malloc(128);
IS_ALLOC_IN_REGION(ptr_3, &lw_regions[1]);
/* Free all pointers to default state */
lwmem_free(ptr_1);
lwmem_free(ptr_2);
lwmem_free(ptr_3);
/* Force allocation region to be used */
/* Allocation of 16-bytes forced to 2nd region */
ptr_1 = lwmem_malloc_ex(NULL, &lw_regions[1], 16);
IS_ALLOC_IN_REGION(ptr_1, &lw_regions[1]);
/* Allocate ptr 2 in any region of default lwmem, the first available must be 1st region */
ptr_2 = lwmem_malloc_ex(NULL, NULL, 16);
IS_ALLOC_IN_REGION(ptr_2, &lw_regions[0]);
/* Free pointers */
lwmem_free(ptr_1);
lwmem_free(ptr_2);
/********************************************/
/* Run tests on custom region */
/********************************************/
/* Allocation of 64 bytes must in in first region */
ptr_c_1 = lwmem_malloc_ex(&lw_c, NULL, 64);
IS_ALLOC_IN_REGION(ptr_c_1, &lw_c_regions[0]);
/* Allocation of 256 bytes can only be in 3rd region */
ptr_c_2 = lwmem_malloc_ex(&lw_c, NULL, 256);
IS_ALLOC_IN_REGION(ptr_c_2, &lw_c_regions[2]);
/* Allocation of 128 bytes can be in second or third region (depends on memory availability),
but in case of these tests it can be (and should be) in second region */
ptr_c_3 = lwmem_malloc_ex(&lw_c, NULL, 128);
IS_ALLOC_IN_REGION(ptr_c_3, &lw_c_regions[1]);
/* Free all pointers to default state */
lwmem_free(ptr_c_1);
lwmem_free(ptr_c_2);
lwmem_free(ptr_c_3);
}
/* For debug purposes */
lwmem_region_t* regions_used;
size_t regions_count = 4; /* Use only 1 region for debug purposes of non-free areas */
void
lwmem_test_memory_structure(void) {
uint8_t* ptr1, *ptr2, *ptr3, *ptr4;
uint8_t* rptr1, *rptr2, *rptr3, *rptr4;
/* Create regions for debug purpose */
if (!lwmem_debug_create_regions(&regions_used, regions_count, 128)) {
printf("Cannot allocate memory for regions for debug purpose!\r\n");
return;
}
lwmem_assignmem(regions_used, regions_count);
printf("Manager is ready!\r\n");
lwmem_debug_print(1, 1);
/* Test case 1, allocate 3 blocks, each of different size */
/* We know that sizeof internal metadata block is 8 bytes on win32 */
printf("\r\n\r\nAllocating 4 pointers and freeing first and third..\r\n");
ptr1 = lwmem_malloc(8);
ptr2 = lwmem_malloc(4);
ptr3 = lwmem_malloc(4);
ptr4 = lwmem_malloc(16);
lwmem_free(ptr1); /* Free but keep value for future comparison */
lwmem_free(ptr3); /* Free but keep value for future comparison */
lwmem_debug_print(1, 1);
printf("Debug above is effectively state 3\r\n");
lwmem_debug_save_state(); /* Every restore operations rewinds here */
/* We always try to reallocate pointer ptr2 */
/* Create 3a case */
printf("\r\n------------------------------------------------------------------------\r\n");
lwmem_debug_restore_to_saved();
printf("State 3a\r\n");
rptr1 = lwmem_realloc(ptr2, 8);
lwmem_debug_print(1, 1);
ASSERT(rptr1 == ptr2);
/* Create 3b case */
printf("\r\n------------------------------------------------------------------------\r\n");
lwmem_debug_restore_to_saved();
printf("State 3b\r\n");
rptr2 = lwmem_realloc(ptr2, 20);
lwmem_debug_print(1, 1);
ASSERT(rptr2 == ptr2);
/* Create 3c case */
printf("\r\n------------------------------------------------------------------------\r\n");
lwmem_debug_restore_to_saved();
printf("State 3c\r\n");
rptr3 = lwmem_realloc(ptr2, 24);
lwmem_debug_print(1, 1);
ASSERT(rptr3 == ptr1);
/* Create 3d case */
printf("\r\n------------------------------------------------------------------------\r\n");
lwmem_debug_restore_to_saved();
printf("State 3d\r\n");
rptr4 = lwmem_realloc(ptr2, 36);
lwmem_debug_print(1, 1);
ASSERT(rptr4 != ptr1 && rptr4 != ptr2 && rptr4 != ptr3 && rptr4 != ptr4);
}