361 lines
10 KiB
C++
361 lines
10 KiB
C++
|
#include "MicroBit.h"
|
||
|
|
||
|
/**
|
||
|
* A simple 32 bit block based memory allocator. This allows one or more memory segments to
|
||
|
* be designated as heap storage, and is designed to run in a static memory area or inside the standard C
|
||
|
* heap for use by the micro:bit runtime. This is required for several reasons:
|
||
|
*
|
||
|
* 1) It reduces memory fragmentation due to the high churn sometime placed on the heap
|
||
|
* by ManagedTypes, fibers and user code. Underlying heap implentations are often have very simplistic
|
||
|
* allocation pilicies and suffer from fragmentation in prolonged use - which can cause programs to
|
||
|
* stop working after a period of time. The algorithm implemented here is simple, but highly tolerant to
|
||
|
* large amounts of churn.
|
||
|
*
|
||
|
* 2) It allows us to reuse the 8K of SRAM set aside for SoftDevice as additional heap storage
|
||
|
* when BLE is not in use.
|
||
|
*
|
||
|
* 3) It gives a simple example of how memory allocation works! :-)
|
||
|
*
|
||
|
* N.B. The need for this should be reviewed in the future, should a different memory allocator be
|
||
|
* made availiable in the mbed platform.
|
||
|
*
|
||
|
* P.S. This is a very simple allocator, therefore not without its weaknesses. Why don't you consider
|
||
|
* what these are, and consider the tradeoffs against simplicity...
|
||
|
*
|
||
|
* TODO: Consider caching recently freed blocks to improve allocation time.
|
||
|
*/
|
||
|
struct HeapDefinition
|
||
|
{
|
||
|
uint32_t *heap_start; // Physical address of the start of this heap.
|
||
|
uint32_t *heap_end; // Physical address of the end of this heap.
|
||
|
};
|
||
|
|
||
|
// Create the necessary heap definitions.
|
||
|
// We use two heaps by default: one for SoftDevice reuse, and one to run inside the mbed heap.
|
||
|
HeapDefinition heap[MICROBIT_HEAP_COUNT] = {NULL};
|
||
|
|
||
|
|
||
|
#if defined(MICROBIT_DBG) && defined(MICROBIT_HEAP_DBG)
|
||
|
// Internal diagnostics function.
|
||
|
// Diplays a usage summary about a given heap...
|
||
|
void microbit_heap_print(HeapDefinition &heap)
|
||
|
{
|
||
|
uint32_t blockSize;
|
||
|
uint32_t *block;
|
||
|
int totalFreeBlock = 0;
|
||
|
int totalUsedBlock = 0;
|
||
|
int cols = 0;
|
||
|
|
||
|
if (heap.heap_start == NULL)
|
||
|
{
|
||
|
pc.printf("--- HEAP NOT INITIALISED ---\n");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Disable IRQ temporarily to ensure no race conditions!
|
||
|
__disable_irq();
|
||
|
|
||
|
pc.printf("heap_start : %p\n", heap.heap_start);
|
||
|
pc.printf("heap_end : %p\n", heap.heap_end);
|
||
|
pc.printf("heap_size : %d\n", (int)heap.heap_end - (int)heap.heap_start);
|
||
|
|
||
|
block = heap.heap_start;
|
||
|
while (block < heap.heap_end)
|
||
|
{
|
||
|
blockSize = *block & ~MICROBIT_HEAP_BLOCK_FREE;
|
||
|
pc.printf("[%C:%d] ", *block & MICROBIT_HEAP_BLOCK_FREE ? 'F' : 'U', blockSize*4);
|
||
|
if (cols++ == 20)
|
||
|
{
|
||
|
pc.printf("\n");
|
||
|
cols = 0;
|
||
|
}
|
||
|
|
||
|
if (*block & MICROBIT_HEAP_BLOCK_FREE)
|
||
|
totalFreeBlock += blockSize;
|
||
|
else
|
||
|
totalUsedBlock += blockSize;
|
||
|
|
||
|
block += blockSize;
|
||
|
}
|
||
|
pc.printf("\n");
|
||
|
|
||
|
pc.printf("mb_total_free : %d\n", totalFreeBlock*4);
|
||
|
pc.printf("mb_total_used : %d\n", totalUsedBlock*4);
|
||
|
}
|
||
|
|
||
|
|
||
|
// Internal diagnostics function.
|
||
|
// Diplays a usage summary about all known heaps...
|
||
|
void microbit_heap_print()
|
||
|
{
|
||
|
for (int i=0; i < MICROBIT_HEAP_COUNT; i++)
|
||
|
{
|
||
|
pc.printf("\nHEAP %d: \n", i);
|
||
|
microbit_heap_print(heap[i]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#endif
|
||
|
|
||
|
void microbit_initialise_heap(HeapDefinition &heap)
|
||
|
{
|
||
|
// Simply mark the entire heap as free.
|
||
|
*heap.heap_start = ((uint32_t) heap.heap_end - (uint32_t) heap.heap_start) / MICROBIT_HEAP_BLOCK_SIZE;
|
||
|
*heap.heap_start |= MICROBIT_HEAP_BLOCK_FREE;
|
||
|
}
|
||
|
|
||
|
int
|
||
|
microbit_create_sd_heap(HeapDefinition &heap)
|
||
|
{
|
||
|
#if !defined(MICROBIT_HEAP_REUSE_SD)
|
||
|
// We're not configure to use memory of this sort.
|
||
|
return 0;
|
||
|
#endif
|
||
|
|
||
|
// OK, see how much of the RAM assigned to Soft Device we can reclaim.
|
||
|
#ifdef MICROBIT_BLE_ENABLED
|
||
|
heap.heap_start = (uint32_t *)MICROBIT_HEAP_BASE_BLE_ENABLED;
|
||
|
heap.heap_end = (uint32_t *)MICROBIT_HEAP_SD_LIMIT;
|
||
|
#else
|
||
|
heap.heap_start = (uint32_t *)MICROBIT_HEAP_BASE_BLE_DISABLED;
|
||
|
heap.heap_end = (uint32_t *)MICROBIT_HEAP_SD_LIMIT;
|
||
|
#endif
|
||
|
|
||
|
microbit_initialise_heap(heap);
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
int
|
||
|
microbit_create_nested_heap(HeapDefinition &heap)
|
||
|
{
|
||
|
uint32_t mb_heap_max;
|
||
|
|
||
|
// Snapshot something at the top of the main heap.
|
||
|
void *p = native_malloc(sizeof(uint32_t));
|
||
|
|
||
|
// Compute the size left in our heap, taking care to ensure it lands on a word boundary.
|
||
|
mb_heap_max = (uint32_t) (((float)(MICROBIT_HEAP_END - (uint32_t)p)) * MICROBIT_HEAP_SIZE);
|
||
|
mb_heap_max &= 0xFFFFFFFC;
|
||
|
|
||
|
// Release our reference pointer.
|
||
|
native_free(p);
|
||
|
|
||
|
// Allocate memory for our heap.
|
||
|
// We do this iteratively, as some build configurations seem to have static limits
|
||
|
// on heap size... This allows us to be keep going anyway!
|
||
|
while (heap.heap_start == NULL)
|
||
|
{
|
||
|
heap.heap_start = (uint32_t *)native_malloc(mb_heap_max);
|
||
|
if (heap.heap_start == NULL)
|
||
|
{
|
||
|
mb_heap_max -= 32;
|
||
|
if (mb_heap_max <= 0)
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
heap.heap_end = heap.heap_start + mb_heap_max / MICROBIT_HEAP_BLOCK_SIZE;
|
||
|
microbit_initialise_heap(heap);
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Initialise the microbit heap according to the parameters defined in MicroBitConfig.h
|
||
|
* After this is called, any future calls to malloc, new, free or delete will use the new heap.
|
||
|
* n.b. only code that #includes MicroBitHeapAllocator.h will use this heap. This includes all micro:bit runtime
|
||
|
* code, and user code targetting the runtime. External code can choose to include this file, or
|
||
|
* simply use the strandard mbed heap.
|
||
|
*/
|
||
|
int
|
||
|
microbit_heap_init()
|
||
|
{
|
||
|
int r = 0;
|
||
|
|
||
|
// Disable IRQ temporarily to ensure no race conditions!
|
||
|
__disable_irq();
|
||
|
|
||
|
r += microbit_create_nested_heap(heap[0]);
|
||
|
r += microbit_create_sd_heap(heap[1]);
|
||
|
|
||
|
// Enable Interrupts
|
||
|
__enable_irq();
|
||
|
|
||
|
#if defined(MICROBIT_DBG) && defined(MICROBIT_HEAP_DBG)
|
||
|
microbit_heap_print();
|
||
|
#endif
|
||
|
return r;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Attempt to allocate a given amount of memory from the given heap.
|
||
|
* @param size The amount of memory, in bytes, to allocate.
|
||
|
* @param heap The heap the memory is to be allocated from.
|
||
|
* @return A pointer to the allocated memory, or NULL if insufficient memory is available.
|
||
|
*/
|
||
|
void *microbit_malloc(size_t size, HeapDefinition &heap)
|
||
|
{
|
||
|
uint32_t blockSize = 0;
|
||
|
uint32_t blocksNeeded = size % MICROBIT_HEAP_BLOCK_SIZE == 0 ? size / MICROBIT_HEAP_BLOCK_SIZE : size / MICROBIT_HEAP_BLOCK_SIZE + 1;
|
||
|
uint32_t *block;
|
||
|
uint32_t *next;
|
||
|
|
||
|
if (size <= 0)
|
||
|
return NULL;
|
||
|
|
||
|
// Account for the index block;
|
||
|
blocksNeeded++;
|
||
|
|
||
|
// Disable IRQ temporarily to ensure no race conditions!
|
||
|
__disable_irq();
|
||
|
|
||
|
// We implement a first fit algorithm with cache to handle rapid churn...
|
||
|
// We also defragment free blocks as we search, to optimise this and future searches.
|
||
|
block = heap.heap_start;
|
||
|
while (block < heap.heap_end)
|
||
|
{
|
||
|
// If the block is used, then keep looking.
|
||
|
if(!(*block & MICROBIT_HEAP_BLOCK_FREE))
|
||
|
{
|
||
|
block += *block;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
blockSize = *block & ~MICROBIT_HEAP_BLOCK_FREE;
|
||
|
|
||
|
// We have a free block. Let's see if the subsequent ones are too. If so, we can merge...
|
||
|
next = block + blockSize;
|
||
|
|
||
|
while (*next & MICROBIT_HEAP_BLOCK_FREE)
|
||
|
{
|
||
|
if (next >= heap.heap_end)
|
||
|
break;
|
||
|
|
||
|
// We can merge!
|
||
|
blockSize += (*next & ~MICROBIT_HEAP_BLOCK_FREE);
|
||
|
*block = blockSize | MICROBIT_HEAP_BLOCK_FREE;
|
||
|
|
||
|
next = block + blockSize;
|
||
|
}
|
||
|
|
||
|
// We have a free block. Let's see if it's big enough.
|
||
|
// If so, we have a winner.
|
||
|
if (blockSize >= blocksNeeded)
|
||
|
break;
|
||
|
|
||
|
// Otherwise, keep looking...
|
||
|
block += blockSize;
|
||
|
}
|
||
|
|
||
|
// We're full!
|
||
|
if (block >= heap.heap_end)
|
||
|
{
|
||
|
__enable_irq();
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
// If we're at the end of memory or have very near match then mark the whole segment as in use.
|
||
|
if (blockSize <= blocksNeeded+1 || block+blocksNeeded+1 >= heap.heap_end)
|
||
|
{
|
||
|
// Just mark the whole block as used.
|
||
|
*block &= ~MICROBIT_HEAP_BLOCK_FREE;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// We need to split the block.
|
||
|
uint32_t *splitBlock = block + blocksNeeded;
|
||
|
*splitBlock = blockSize - blocksNeeded;
|
||
|
*splitBlock |= MICROBIT_HEAP_BLOCK_FREE;
|
||
|
|
||
|
*block = blocksNeeded;
|
||
|
}
|
||
|
|
||
|
// Enable Interrupts
|
||
|
__enable_irq();
|
||
|
|
||
|
return block+1;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Attempt to allocate a given amount of memory from any of our configured heap areas.
|
||
|
* @param size The amount of memory, in bytes, to allocate.
|
||
|
* @return A pointer to the allocated memory, or NULL if insufficient memory is available.
|
||
|
*/
|
||
|
void *microbit_malloc(size_t size)
|
||
|
{
|
||
|
void *p;
|
||
|
|
||
|
// Assign the memory from the first heap created that has space.
|
||
|
for (int i=0; i < MICROBIT_HEAP_COUNT; i++)
|
||
|
{
|
||
|
if(heap[i].heap_start != NULL)
|
||
|
{
|
||
|
p = microbit_malloc(size, heap[i]);
|
||
|
if (p != NULL)
|
||
|
{
|
||
|
#ifdef MICROBIT_HEAP_DBG
|
||
|
pc.printf("microbit_malloc: ALLOCATED: %d [%p]\n", size, p);
|
||
|
#endif
|
||
|
return p;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If we reach here, then either we have no memory available, or our heap spaces
|
||
|
// haven't been initialised. Either way, we try the native allocator.
|
||
|
|
||
|
p = native_malloc(size);
|
||
|
if (p!= NULL)
|
||
|
{
|
||
|
#ifdef MICROBIT_HEAP_DBG
|
||
|
pc.printf("microbit_malloc: NATIVE ALLOCATED: %d [%p]\n", size, p);
|
||
|
#endif
|
||
|
return p;
|
||
|
}
|
||
|
|
||
|
// We're totally out of options (and memory!).
|
||
|
#ifdef MICROBIT_HEAP_DBG
|
||
|
pc.printf("microbit_malloc: OUT OF MEMORY\n");
|
||
|
#endif
|
||
|
|
||
|
#ifdef MICROBIT_PANIC_HEAP_FULL
|
||
|
panic(MICROBIT_OOM);
|
||
|
#endif
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Release a given area of memory from the heap.
|
||
|
* @param mem The memory area to release.
|
||
|
*/
|
||
|
void microbit_free(void *mem)
|
||
|
{
|
||
|
uint32_t *memory = (uint32_t *)mem;
|
||
|
uint32_t *cb = memory-1;
|
||
|
|
||
|
#ifdef MICROBIT_HEAP_DBG
|
||
|
pc.printf("microbit_free: %p\n", mem);
|
||
|
#endif
|
||
|
// Sanity check.
|
||
|
if (memory == NULL)
|
||
|
return;
|
||
|
|
||
|
// If this memory was created from a heap registered with us, free it.
|
||
|
for (int i=0; i < MICROBIT_HEAP_COUNT; i++)
|
||
|
{
|
||
|
if(memory > heap[i].heap_start && memory < heap[i].heap_end)
|
||
|
{
|
||
|
// The memory block given is part of this heap, so we can simply
|
||
|
// flag that this memory area is now free, and we're done.
|
||
|
*cb |= MICROBIT_HEAP_BLOCK_FREE;
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If we reach here, then the memory is not part of any registered heap.
|
||
|
// Forward it to the native heap allocator, and let nature take its course...
|
||
|
native_free(mem);
|
||
|
}
|
||
|
|