Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Dependencies: BLE_API mbed-dev-bin nRF51822
Fork of microbit-dal by
Diff: source/core/MicroBitHeapAllocator.cpp
- Revision:
- 1:8aa5cdb4ab67
diff -r fb15f7887843 -r 8aa5cdb4ab67 source/core/MicroBitHeapAllocator.cpp
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/source/core/MicroBitHeapAllocator.cpp Thu Apr 07 01:33:22 2016 +0100
@@ -0,0 +1,405 @@
+/*
+The MIT License (MIT)
+
+Copyright (c) 2016 British Broadcasting Corporation.
+This software is provided by Lancaster University by arrangement with the BBC.
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+*/
+
+/**
+ * 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! :-)
+ *
+ * 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...
+ *
+ * @note The need for this should be reviewed in the future, if a different memory allocator is
+ * made availiable in the mbed platform.
+ *
+ * TODO: Consider caching recently freed blocks to improve allocation time.
+ */
+
+#include "MicroBitConfig.h"
+#include "MicroBitHeapAllocator.h"
+#include "MicroBitDevice.h"
+#include "ErrorNo.h"
+
+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.
+};
+
+// A list of all active heap regions, and their dimensions in memory.
+HeapDefinition heap[MICROBIT_MAXIMUM_HEAPS] = { };
+uint8_t heap_count = 0;
+
+#if CONFIG_ENABLED(MICROBIT_DBG) && CONFIG_ENABLED(MICROBIT_HEAP_DBG)
+// 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)
+ {
+ if(SERIAL_DEBUG) SERIAL_DEBUG->printf("--- HEAP NOT INITIALISED ---\n");
+ return;
+ }
+
+ if(SERIAL_DEBUG) SERIAL_DEBUG->printf("heap_start : %p\n", heap.heap_start);
+ if(SERIAL_DEBUG) SERIAL_DEBUG->printf("heap_end : %p\n", heap.heap_end);
+ if(SERIAL_DEBUG) SERIAL_DEBUG->printf("heap_size : %d\n", (int)heap.heap_end - (int)heap.heap_start);
+
+ // Disable IRQ temporarily to ensure no race conditions!
+ __disable_irq();
+
+ block = heap.heap_start;
+ while (block < heap.heap_end)
+ {
+ blockSize = *block & ~MICROBIT_HEAP_BLOCK_FREE;
+ if(SERIAL_DEBUG) SERIAL_DEBUG->printf("[%c:%d] ", *block & MICROBIT_HEAP_BLOCK_FREE ? 'F' : 'U', blockSize*4);
+ if (cols++ == 20)
+ {
+ if(SERIAL_DEBUG) SERIAL_DEBUG->printf("\n");
+ cols = 0;
+ }
+
+ if (*block & MICROBIT_HEAP_BLOCK_FREE)
+ totalFreeBlock += blockSize;
+ else
+ totalUsedBlock += blockSize;
+
+ block += blockSize;
+ }
+
+ // Enable Interrupts
+ __enable_irq();
+
+ if(SERIAL_DEBUG) SERIAL_DEBUG->printf("\n");
+
+ if(SERIAL_DEBUG) SERIAL_DEBUG->printf("mb_total_free : %d\n", totalFreeBlock*4);
+ if(SERIAL_DEBUG) SERIAL_DEBUG->printf("mb_total_used : %d\n", totalUsedBlock*4);
+}
+
+
+// Diagnostics function. Displays a usage summary about all initialised heaps.
+void microbit_heap_print()
+{
+ for (int i=0; i < heap_count; i++)
+ {
+ if(SERIAL_DEBUG) SERIAL_DEBUG->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;
+}
+
+/**
+ * Create and initialise a given memory region as for heap storage.
+ * After this is called, any future calls to malloc, new, free or delete may use the new heap.
+ * The heap allocator will attempt to allocate memory from heaps in the order that they are created.
+ * i.e. memory will be allocated from first heap created until it is full, then the second heap, and so on.
+ *
+ * @param start The start address of memory to use as a heap region.
+ *
+ * @param end The end address of memory to use as a heap region.
+ *
+ * @return MICROBIT_OK on success, or MICROBIT_NO_RESOURCES if the heap could not be allocated.
+ *
+ * @note 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 standard heap.
+ */
+int microbit_create_heap(uint32_t start, uint32_t end)
+{
+ // Ensure we don't exceed the maximum number of heap segments.
+ if (heap_count == MICROBIT_MAXIMUM_HEAPS)
+ return MICROBIT_NO_RESOURCES;
+
+ // Sanity check. Ensure range is valid, large enough and word aligned.
+ if (end <= start || end - start < MICROBIT_HEAP_BLOCK_SIZE*2 || end % 4 != 0 || start % 4 != 0)
+ return MICROBIT_INVALID_PARAMETER;
+
+ // Disable IRQ temporarily to ensure no race conditions!
+ __disable_irq();
+
+ // Record the dimensions of this new heap
+ heap[heap_count].heap_start = (uint32_t *)start;
+ heap[heap_count].heap_end = (uint32_t *)end;
+
+ // Initialise the heap as being completely empty and available for use.
+ microbit_initialise_heap(heap[heap_count]);
+ heap_count++;
+
+ // Enable Interrupts
+ __enable_irq();
+
+#if CONFIG_ENABLED(MICROBIT_DBG) && CONFIG_ENABLED(MICROBIT_HEAP_DBG)
+ microbit_heap_print();
+#endif
+
+ return MICROBIT_OK;
+}
+
+/**
+ * Create and initialise a heap region within the current the heap region specified
+ * by the linker script.
+ *
+ * If the requested amount is not available, then the amount requested will be reduced
+ * automatically to fit the space available.
+ *
+ * @param ratio The proportion of the underlying heap to allocate.
+ *
+ * @return MICROBIT_OK on success, or MICROBIT_NO_RESOURCES if the heap could not be allocated.
+ */
+int microbit_create_nested_heap(float ratio)
+{
+ uint32_t length;
+ void *p;
+
+ if (ratio <= 0.0 || ratio > 1.0)
+ return MICROBIT_INVALID_PARAMETER;
+
+ // Snapshot something at the top of the main heap.
+ p = native_malloc(sizeof(uint32_t));
+
+ // Estimate the size left in our heap, taking care to ensure it lands on a word boundary.
+ length = (uint32_t) (((float)(MICROBIT_HEAP_END - (uint32_t)p)) * ratio);
+ length &= 0xFFFFFFFC;
+
+ // Release our reference pointer.
+ native_free(p);
+ p = NULL;
+
+ // Allocate memory for our heap.
+ // We iteratively reduce the size of memory are allocate until it fits within available space.
+ while (p == NULL)
+ {
+ p = native_malloc(length);
+ if (p == NULL)
+ {
+ length -= 32;
+ if (length <= 0)
+ return MICROBIT_NO_RESOURCES;
+ }
+ }
+
+ uint32_t start = (uint32_t) p;
+ microbit_create_heap(start, start + length);
+
+ return MICROBIT_OK;
+}
+
+/**
+ * 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, 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;
+}
+
+/**
+ * Release a given area of memory from the heap.
+ *
+ * @param mem The memory area to release.
+ */
+void *microbit_malloc(size_t size)
+{
+ void *p;
+
+ // Assign the memory from the first heap created that has space.
+ for (int i=0; i < heap_count; i++)
+ {
+ p = microbit_malloc(size, heap[i]);
+ if (p != NULL)
+ {
+#if CONFIG_ENABLED(MICROBIT_DBG) && CONFIG_ENABLED(MICROBIT_HEAP_DBG)
+ if(SERIAL_DEBUG) SERIAL_DEBUG->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)
+ {
+#if CONFIG_ENABLED(MICROBIT_DBG) && CONFIG_ENABLED(MICROBIT_HEAP_DBG)
+ // Keep everything trasparent if we've not been initialised yet
+ if (heap_count > 0)
+ if(SERIAL_DEBUG) SERIAL_DEBUG->printf("microbit_malloc: NATIVE ALLOCATED: %d [%p]\n", size, p);
+#endif
+ return p;
+ }
+
+ // We're totally out of options (and memory!).
+#if CONFIG_ENABLED(MICROBIT_DBG) && CONFIG_ENABLED(MICROBIT_HEAP_DBG)
+ // Keep everything transparent if we've not been initialised yet
+ if (heap_count > 0)
+ if(SERIAL_DEBUG) SERIAL_DEBUG->printf("microbit_malloc: OUT OF MEMORY [%d]\n", size);
+#endif
+
+#if CONFIG_ENABLED(MICROBIT_PANIC_HEAP_FULL)
+ microbit_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;
+
+#if CONFIG_ENABLED(MICROBIT_DBG) && CONFIG_ENABLED(MICROBIT_HEAP_DBG)
+ if (heap_count > 0)
+ if(SERIAL_DEBUG) SERIAL_DEBUG->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 < 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);
+}
