/* ---------------------------------------------------------------------------- * umm_malloc.c - a memory allocator for embedded systems (microcontrollers) * * The MIT License (MIT) * * Copyright (c) 2015 Ralph Hempel * * 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. * * ---------------------------------------------------------------------------- * * Note: this version is significantly modified from the version * distributed by Ralph Hempel. In particular, block numbers are * 32 bits in this version. * ---------------------------------------------------------------------------- */ #include #include #include #include #include #include #include "umm-malloc.hpp" /* * -------------------------------------------------------------------------- * UMM_BEST_FIT (default) * * Set this if you want to use a best-fit algorithm for allocating new blocks. * On by default, turned off by UMM_FIRST_FIT * * UMM_FIRST_FIT * * Set this if you want to use a first-fit algorithm for allocating new blocks. * Faster than UMM_BEST_FIT but can result in higher fragmentation. * * ---------------------------------------------------------------------------- */ #define UMM_BEST_FIT #undef UMM_FIRST_FIT /* * -------------------------------------------------------------------------- * * Three macros to make it easier to protect the memory allocator in a * multitasking system. You should set these macros up to use whatever your * system uses for this purpose. You can disable interrupts entirely, or just * disable task switching - it's up to you * * NOTE WELL that these macros MUST be allowed to nest, because umm_free() is * called from within umm_malloc() * * -------------------------------------------------------------------------- */ #define UMM_CRITICAL_DECL(tag) #define UMM_CRITICAL_ENTRY(tag) #define UMM_CRITICAL_EXIT(tag) /* * -------------------------------------------------------------------------- * * Debug Logging. * * -------------------------------------------------------------------------- */ // #define DBGLOG(format, ...) { fprintf(stderr, format,##__VA_ARGS__); fflush(stderr); } #define DBGLOG(format, ...) /* ------------------------------------------------------------------------- */ typedef struct umm_ptr_t { uint32_t next; uint32_t prev; } umm_ptr; typedef struct umm_block_t { union { umm_ptr used; } header; union { umm_ptr free; uint8_t data[8]; } body; } umm_block; #define UMM_FREELIST_MASK ((uint32_t)(0x80000000)) #define UMM_BLOCKNO_MASK ((uint32_t)(0x7FFFFFFF)) /* ------------------------------------------------------------------------- */ struct umm_heap_config { umm_block *pheap; size_t heap_size; uint32_t numblocks; }; static struct umm_heap_config umm_heap_current; #define UMM_HEAP (umm_heap_current.pheap) #define UMM_HEAPSIZE (umm_heap_current.heap_size) #define UMM_NUMBLOCKS (umm_heap_current.numblocks) #define UMM_BLOCKSIZE (sizeof(umm_block)) #define UMM_BLOCK_LAST (UMM_NUMBLOCKS - 1) /* ------------------------------------------------------------------------- * These macros evaluate to the address of the block and data respectively */ #define UMM_BLOCK(b) (UMM_HEAP[b]) #define UMM_DATA(b) (UMM_BLOCK(b).body.data) /* ------------------------------------------------------------------------- * These macros evaluate to the index of the block - NOT the address!!! */ #define UMM_NBLOCK(b) (UMM_BLOCK(b).header.used.next) #define UMM_PBLOCK(b) (UMM_BLOCK(b).header.used.prev) #define UMM_NFREE(b) (UMM_BLOCK(b).body.free.next) #define UMM_PFREE(b) (UMM_BLOCK(b).body.free.prev) /* ---------------------------------------------------------------------------- * One of the coolest things about this little library is that it's VERY * easy to get debug information about the memory heap by simply iterating * through all of the memory blocks. * * As you go through all the blocks, you can check to see if it's a free * block by looking at the high order bit of the next block index. You can * also see how big the block is by subtracting the next block index from * the current block number. * * The umm_info function does all of that and makes the results available * in the ummHeapInfo structure. * ---------------------------------------------------------------------------- */ typedef struct UMM_HEAP_INFO_t { unsigned int totalEntries; unsigned int usedEntries; unsigned int freeEntries; unsigned int totalBlocks; unsigned int usedBlocks; unsigned int freeBlocks; unsigned int freeBlocksSquared; unsigned int maxFreeContiguousBlocks; int usage_metric; int fragmentation_metric; } UMM_HEAP_INFO; static UMM_HEAP_INFO ummHeapInfo; static void compute_usage_metric(void) { if (0 == ummHeapInfo.freeBlocks) { ummHeapInfo.usage_metric = -1; // No free blocks! } else { ummHeapInfo.usage_metric = (int)((ummHeapInfo.usedBlocks * 100) / (ummHeapInfo.freeBlocks)); } } static void compute_fragmentation_metric(void) { if (0 == ummHeapInfo.freeBlocks) { ummHeapInfo.fragmentation_metric = 0; // No free blocks ... so no fragmentation either! } else { ummHeapInfo.fragmentation_metric = 100 - (((uint32_t)(sqrtf(ummHeapInfo.freeBlocksSquared)) * 100) / (ummHeapInfo.freeBlocks)); } } #define DBGLOG_32_BIT_PTR(x) ((uint32_t)(((uintptr_t)(x)) & 0xffffffff)) void umm_info() { uint32_t blockNo = 0; UMM_CRITICAL_DECL(id_info); /* Protect the critical section... */ UMM_CRITICAL_ENTRY(id_info); /* * Clear out all of the entries in the ummHeapInfo structure before doing * any calculations.. */ memset(&ummHeapInfo, 0, sizeof(ummHeapInfo)); DBGLOG("\n"); DBGLOG("+----------+-------+--------+--------+-------+--------+--------+\n"); DBGLOG("|0x%08x|B %5i|NB %5i|PB %5i|Z %5i|NF %5i|PF %5i|\n", DBGLOG_32_BIT_PTR(&UMM_BLOCK(blockNo)), blockNo, UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK, UMM_PBLOCK(blockNo), (UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK) - blockNo, UMM_NFREE(blockNo), UMM_PFREE(blockNo)); /* * Now loop through the block lists, and keep track of the number and size * of used and free blocks. The terminating condition is an nb pointer with * a value of zero... */ blockNo = UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK; while (UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK) { size_t curBlocks = (UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK) - blockNo; ++ummHeapInfo.totalEntries; ummHeapInfo.totalBlocks += curBlocks; /* Is this a free block? */ if (UMM_NBLOCK(blockNo) & UMM_FREELIST_MASK) { ++ummHeapInfo.freeEntries; ummHeapInfo.freeBlocks += curBlocks; ummHeapInfo.freeBlocksSquared += (curBlocks * curBlocks); if (ummHeapInfo.maxFreeContiguousBlocks < curBlocks) { ummHeapInfo.maxFreeContiguousBlocks = curBlocks; } DBGLOG("|0x%08x|B %5i|NB %5i|PB %5i|Z %5u|NF %5i|PF %5i|\n", DBGLOG_32_BIT_PTR(&UMM_BLOCK(blockNo)), blockNo, UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK, UMM_PBLOCK(blockNo), (uint32_t)curBlocks, UMM_NFREE(blockNo), UMM_PFREE(blockNo)); } else { ++ummHeapInfo.usedEntries; ummHeapInfo.usedBlocks += curBlocks; DBGLOG("|0x%08x|B %5i|NB %5i|PB %5i|Z %5u| |\n", DBGLOG_32_BIT_PTR(&UMM_BLOCK(blockNo)), blockNo, UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK, UMM_PBLOCK(blockNo), (uint32_t)curBlocks); } blockNo = UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK; } /* * The very last block is used as a placeholder to indicate that * there are no more blocks in the heap, so it cannot be used * for anything - at the same time, the size of this block must * ALWAYS be exactly 1 ! */ DBGLOG("|0x%08x|B %5i|NB %5i|PB %5i|Z %5i|NF %5i|PF %5i|\n", DBGLOG_32_BIT_PTR(&UMM_BLOCK(blockNo)), blockNo, UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK, UMM_PBLOCK(blockNo), UMM_NUMBLOCKS - blockNo, UMM_NFREE(blockNo), UMM_PFREE(blockNo)); DBGLOG("+----------+-------+--------+--------+-------+--------+--------+\n"); DBGLOG("Total Entries %5i Used Entries %5i Free Entries %5i\n", ummHeapInfo.totalEntries, ummHeapInfo.usedEntries, ummHeapInfo.freeEntries); DBGLOG("Total Blocks %5i Used Blocks %5i Free Blocks %5i\n", ummHeapInfo.totalBlocks, ummHeapInfo.usedBlocks, ummHeapInfo.freeBlocks); DBGLOG("+--------------------------------------------------------------+\n"); compute_usage_metric(); DBGLOG("Usage Metric: %5i\n", ummHeapInfo.usage_metric); compute_fragmentation_metric(); DBGLOG("Fragmentation Metric: %5i\n", ummHeapInfo.fragmentation_metric); DBGLOG("+--------------------------------------------------------------+\n"); /* Release the critical section... */ UMM_CRITICAL_EXIT(id_info); } /* ------------------------------------------------------------------------ */ static uint32_t umm_blocks(size_t size) { /* * The calculation of the block size is not too difficult, but there are * a few little things that we need to be mindful of. * * When a block removed from the free list, the space used by the free * pointers is available for data. That's what the first calculation * of size is doing. * * We don't check for the special case of (size == 0) here as this needs * special handling in the caller depending on context. For example when we * realloc() a block to size 0 it should simply be freed. * * We do NOT need to check for allocating more blocks than the heap can * possibly hold - the allocator figures this out for us. * * There are only two cases left to consider: * * 1. (size <= body) Obviously this is just one block * 2. (blocks > (2^15)) This should return ((2^15)) to force a * failure when the allocator runs * * If the requested size is greater that 32677-2 blocks (max block index * minus the overhead of the top and bottom bookkeeping blocks) then we * will return an incorrectly truncated value when the result is cast to * a uint32_t. */ if (size <= (sizeof(((umm_block *)0)->body))) { return 1; } /* * If it's for more than that, then we need to figure out the number of * additional whole blocks the size of an umm_block are required, so * reduce the size request by the number of bytes in the body of the * first block. */ size -= (sizeof(((umm_block *)0)->body)); /* NOTE WELL that we take advantage of the fact that INT16_MAX is the * number of blocks that we can index in 15 bits :-) * * The below expression looks wierd, but it's right. Assuming body * size of 4 bytes and a block size of 8 bytes: * * BYTES (BYTES-BODY) (BYTES-BODY-1)/BLOCKSIZE BLOCKS * 1 n/a n/a 1 * 5 1 0 2 * 12 8 0 2 * 13 9 1 3 */ size_t blocks = (2 + ((size-1) / (UMM_BLOCKSIZE))); if (blocks > (INT16_MAX)) { blocks = INT16_MAX; } return (uint32_t)blocks; } /* ------------------------------------------------------------------------ */ /* * Split the block `c` into two blocks: `c` and `c + blocks`. * * - `new_freemask` should be `0` if `c + blocks` used, or `UMM_FREELIST_MASK` * otherwise. * * Note that free pointers are NOT modified by this function. */ static void umm_split_block(uint32_t c, uint32_t blocks, uint32_t new_freemask) { UMM_NBLOCK(c + blocks) = (UMM_NBLOCK(c) & UMM_BLOCKNO_MASK) | new_freemask; UMM_PBLOCK(c + blocks) = c; UMM_PBLOCK(UMM_NBLOCK(c) & UMM_BLOCKNO_MASK) = (c + blocks); UMM_NBLOCK(c) = (c + blocks); } /* ------------------------------------------------------------------------ */ static void umm_disconnect_from_free_list(uint32_t c) { /* Disconnect this block from the FREE list */ UMM_NFREE(UMM_PFREE(c)) = UMM_NFREE(c); UMM_PFREE(UMM_NFREE(c)) = UMM_PFREE(c); /* And clear the free block indicator */ UMM_NBLOCK(c) &= (~UMM_FREELIST_MASK); } /* ------------------------------------------------------------------------ * The umm_assimilate_up() function does not assume that UMM_NBLOCK(c) * has the UMM_FREELIST_MASK bit set. It only assimilates up if the * next block is free. */ static void umm_assimilate_up(uint32_t c) { if (UMM_NBLOCK(UMM_NBLOCK(c)) & UMM_FREELIST_MASK) { /* * The next block is a free block, so assimilate up and remove it from * the free list */ DBGLOG("Assimilate up to next block, which is FREE\n"); /* Disconnect the next block from the FREE list */ umm_disconnect_from_free_list(UMM_NBLOCK(c)); /* Assimilate the next block with this one */ UMM_PBLOCK(UMM_NBLOCK(UMM_NBLOCK(c)) & UMM_BLOCKNO_MASK) = c; UMM_NBLOCK(c) = UMM_NBLOCK(UMM_NBLOCK(c)) & UMM_BLOCKNO_MASK; } } /* ------------------------------------------------------------------------ * The umm_assimilate_down() function assumes that UMM_NBLOCK(c) does NOT * have the UMM_FREELIST_MASK bit set. In other words, try to assimilate * up before assimilating down. */ static uint32_t umm_assimilate_down(uint32_t c, uint32_t freemask) { // We are going to assimilate down to the previous block because // it was free, so remove it from the fragmentation metric UMM_NBLOCK(UMM_PBLOCK(c)) = UMM_NBLOCK(c) | freemask; UMM_PBLOCK(UMM_NBLOCK(c)) = UMM_PBLOCK(c); return UMM_PBLOCK(c); } /* ------------------------------------------------------------------------- */ void umm_init_heap(void *ptr, size_t size) { /* init heap pointer and size, and memset it to 0 */ UMM_HEAP = (umm_block *)ptr; UMM_HEAPSIZE = size; UMM_NUMBLOCKS = (UMM_HEAPSIZE / UMM_BLOCKSIZE); memset(UMM_HEAP, 0x00, UMM_HEAPSIZE); /* Set up umm_block[0], which just points to umm_block[1] */ UMM_NBLOCK(0) = 1; UMM_NFREE(0) = 1; UMM_PFREE(0) = 1; /* * Now, we need to set the whole heap space as a huge free block. We should * not touch umm_block[0], since it's special: umm_block[0] is the head of * the free block list. It's a part of the heap invariant. * * See the detailed explanation at the beginning of the file. * * umm_block[1] has pointers: * * - next `umm_block`: the last one umm_block[n] * - prev `umm_block`: umm_block[0] * * Plus, it's a free `umm_block`, so we need to apply `UMM_FREELIST_MASK` * * And it's the last free block, so the next free block is 0 which marks * the end of the list. The previous block and free block pointer are 0 * too, there is no need to initialize these values due to the init code * that memsets the entire umm_ space to 0. */ UMM_NBLOCK(1) = UMM_BLOCK_LAST | UMM_FREELIST_MASK; /* * Last umm_block[n] has the next block index at 0, meaning it's * the end of the list, and the previous block is umm_block[1]. * * The last block is a special block and can never be part of the * free list, so its pointers are left at 0 too. */ UMM_PBLOCK(UMM_BLOCK_LAST) = 1; // DBGLOG(true, "nblock(0) %04x pblock(0) %04x nfree(0) %04x pfree(0) %04x\n", UMM_NBLOCK(0) & UMM_BLOCKNO_MASK, UMM_PBLOCK(0), UMM_NFREE(0), UMM_PFREE(0)); // DBGLOG(true, "nblock(1) %04x pblock(1) %04x nfree(1) %04x pfree(1) %04x\n", UMM_NBLOCK(1) & UMM_BLOCKNO_MASK, UMM_PBLOCK(1), UMM_NFREE(1), UMM_PFREE(1)); } /* ------------------------------------------------------------------------ * Must be called only from within critical sections guarded by * UMM_CRITICAL_ENTRY(id) and UMM_CRITICAL_EXIT(id). */ static void umm_free_core(void *ptr) { uint32_t c; /* * FIXME: At some point it might be a good idea to add a check to make sure * that the pointer we're being asked to free up is actually within * the umm_heap! * */ /* Figure out which block we're in. Note the use of truncated division... */ c = (((uint8_t *)ptr) - (uint8_t *)(&(UMM_HEAP[0]))) / UMM_BLOCKSIZE; DBGLOG("Freeing block %6i\n", c); /* Now let's assimilate this block with the next one if possible. */ umm_assimilate_up(c); /* Then assimilate with the previous block if possible */ if (UMM_NBLOCK(UMM_PBLOCK(c)) & UMM_FREELIST_MASK) { DBGLOG("Assimilate down to previous block, which is FREE\n"); c = umm_assimilate_down(c, UMM_FREELIST_MASK); } else { /* * The previous block is not a free block, so add this one to the head * of the free list */ DBGLOG("Just add to head of free list\n"); UMM_PFREE(UMM_NFREE(0)) = c; UMM_NFREE(c) = UMM_NFREE(0); UMM_PFREE(c) = 0; UMM_NFREE(0) = c; UMM_NBLOCK(c) |= UMM_FREELIST_MASK; } } /* ------------------------------------------------------------------------ */ void umm_free(void *ptr) { UMM_CRITICAL_DECL(id_free); /* If we're being asked to free a NULL pointer, well that's just silly! */ if ((void *)0 == ptr) { DBGLOG("free a null pointer -> do nothing\n"); return; } /* Free the memory withing a protected critical section */ UMM_CRITICAL_ENTRY(id_free); umm_free_core(ptr); UMM_CRITICAL_EXIT(id_free); } /* ------------------------------------------------------------------------ * Must be called only from within critical sections guarded by * UMM_CRITICAL_ENTRY(id) and UMM_CRITICAL_EXIT(id). */ static void *umm_malloc_core(size_t size) { uint32_t blocks; uint32_t blockSize = 0; uint32_t bestSize; uint32_t bestBlock; uint32_t cf; blocks = umm_blocks(size); /* * Now we can scan through the free list until we find a space that's big * enough to hold the number of blocks we need. * * This part may be customized to be a best-fit, worst-fit, or first-fit * algorithm */ cf = UMM_NFREE(0); bestBlock = UMM_NFREE(0); bestSize = 0x7FFFFFFF; while (cf) { blockSize = (UMM_NBLOCK(cf) & UMM_BLOCKNO_MASK) - cf; DBGLOG("Looking at block %6i size %6i\n", cf, blockSize); #if defined UMM_BEST_FIT if ((blockSize >= blocks) && (blockSize < bestSize)) { bestBlock = cf; bestSize = blockSize; } #elif defined UMM_FIRST_FIT /* This is the first block that fits! */ if ((blockSize >= blocks)) { break; } #else #error "No UMM_*_FIT is defined - check umm_malloc_cfg.h" #endif cf = UMM_NFREE(cf); } if (0x7FFFFFFF != bestSize) { cf = bestBlock; blockSize = bestSize; } if (UMM_NBLOCK(cf) & UMM_BLOCKNO_MASK && blockSize >= blocks) { /* * This is an existing block in the memory heap, we just need to split off * what we need, unlink it from the free list and mark it as in use, and * link the rest of the block back into the freelist as if it was a new * block on the free list... */ if (blockSize == blocks) { /* It's an exact fit and we don't neet to split off a block. */ DBGLOG("Allocating %6i blocks starting at %6i - exact\n", blocks, cf); /* Disconnect this block from the FREE list */ umm_disconnect_from_free_list(cf); } else { /* It's not an exact fit and we need to split off a block. */ DBGLOG("Allocating %6i blocks starting at %6i - existing\n", blocks, cf); /* * split current free block `cf` into two blocks. The first one will be * returned to user, so it's not free, and the second one will be free. */ umm_split_block(cf, blocks, UMM_FREELIST_MASK /*new block is free*/); /* * `umm_split_block()` does not update the free pointers (it affects * only free flags), but effectively we've just moved beginning of the * free block from `cf` to `cf + blocks`. So we have to adjust pointers * to and from adjacent free blocks. */ /* previous free block */ UMM_NFREE(UMM_PFREE(cf)) = cf + blocks; UMM_PFREE(cf + blocks) = UMM_PFREE(cf); /* next free block */ UMM_PFREE(UMM_NFREE(cf)) = cf + blocks; UMM_NFREE(cf + blocks) = UMM_NFREE(cf); } } else { /* Out of memory */ DBGLOG("Can't allocate %5i blocks\n", blocks); return (void *)NULL; } return (void *)&UMM_DATA(cf); } /* ------------------------------------------------------------------------ */ void *umm_malloc(size_t size) { UMM_CRITICAL_DECL(id_malloc); void *ptr = NULL; /* * the very first thing we do is figure out if we're being asked to allocate * a size of 0 - and if we are we'll simply return a null pointer. if not * then reduce the size by 1 byte so that the subsequent calculations on * the number of blocks to allocate are easier... */ if (0 == size) { DBGLOG("malloc a block of 0 bytes -> do nothing\n"); return ptr; } /* Allocate the memory withing a protected critical section */ UMM_CRITICAL_ENTRY(id_malloc); ptr = umm_malloc_core(size); UMM_CRITICAL_EXIT(id_malloc); return ptr; } /* ------------------------------------------------------------------------ */ void *umm_realloc(void *ptr, size_t size) { UMM_CRITICAL_DECL(id_realloc); uint32_t blocks; uint32_t blockSize; uint32_t prevBlockSize = 0; uint32_t nextBlockSize = 0; uint32_t c; size_t curSize; /* * This code looks after the case of a NULL value for ptr. The ANSI C * standard says that if ptr is NULL and size is non-zero, then we've * got to work the same a malloc(). If size is also 0, then our version * of malloc() returns a NULL pointer, which is OK as far as the ANSI C * standard is concerned. */ if (((void *)NULL == ptr)) { DBGLOG("realloc the NULL pointer - call malloc()\n"); return umm_malloc(size); } /* * Now we're sure that we have a non_NULL ptr, but we're not sure what * we should do with it. If the size is 0, then the ANSI C standard says that * we should operate the same as free. */ if (0 == size) { DBGLOG("realloc to 0 size, just free the block\n"); umm_free(ptr); return (void *)NULL; } /* * Otherwise we need to actually do a reallocation. A naiive approach * would be to malloc() a new block of the correct size, copy the old data * to the new block, and then free the old block. * * While this will work, we end up doing a lot of possibly unnecessary * copying. So first, let's figure out how many blocks we'll need. */ blocks = umm_blocks(size); /* Figure out which block we're in. Note the use of truncated division... */ c = (((uint8_t *)ptr) - (uint8_t *)(&(UMM_HEAP[0]))) / UMM_BLOCKSIZE; /* Figure out how big this block is ... the free bit is not set :-) */ blockSize = (UMM_NBLOCK(c) - c); /* Figure out how many bytes are in this block */ curSize = (blockSize * UMM_BLOCKSIZE) - (sizeof(((umm_block *)0)->header)); /* Protect the critical section... */ UMM_CRITICAL_ENTRY(id_realloc); /* Now figure out if the previous and/or next blocks are free as well as * their sizes - this will help us to minimize special code later when we * decide if it's possible to use the adjacent blocks. * * We set prevBlockSize and nextBlockSize to non-zero values ONLY if they * are free! */ if ((UMM_NBLOCK(UMM_NBLOCK(c)) & UMM_FREELIST_MASK)) { nextBlockSize = (UMM_NBLOCK(UMM_NBLOCK(c)) & UMM_BLOCKNO_MASK) - UMM_NBLOCK(c); } if ((UMM_NBLOCK(UMM_PBLOCK(c)) & UMM_FREELIST_MASK)) { prevBlockSize = (c - UMM_PBLOCK(c)); } DBGLOG("realloc blocks %i blockSize %i nextBlockSize %i prevBlockSize %i\n", blocks, blockSize, nextBlockSize, prevBlockSize); /* * Ok, now that we're here we know how many blocks we want and the current * blockSize. The prevBlockSize and nextBlockSize are set and we can figure * out the best strategy for the new allocation as follows: * * 1. If the new block is the same size or smaller than the current block do * nothing. * 2. If the next block is free and adding it to the current block gives us * EXACTLY enough memory, assimilate the next block. This avoids unwanted * fragmentation of free memory. * * The following cases may be better handled with memory copies to reduce * fragmentation * * 3. If the previous block is NOT free and the next block is free and * adding it to the current block gives us enough memory, assimilate * the next block. This may introduce a bit of fragmentation. * 4. If the prev block is free and adding it to the current block gives us * enough memory, remove the previous block from the free list, assimilate * it, copy to the new block. * 5. If the prev and next blocks are free and adding them to the current * block gives us enough memory, assimilate the next block, remove the * previous block from the free list, assimilate it, copy to the new block. * 6. Otherwise try to allocate an entirely new block of memory. If the * allocation works free the old block and return the new pointer. If * the allocation fails, return NULL and leave the old block intact. * * TODO: Add some conditional code to optimise for less fragmentation * by simply allocating new memory if we need to copy anyways. * * All that's left to do is decide if the fit was exact or not. If the fit * was not exact, then split the memory block so that we use only the requested * number of blocks and add what's left to the free list. */ // Case 1 - block is same size or smaller if (blockSize >= blocks) { DBGLOG("realloc the same or smaller size block - %i, do nothing\n", blocks); /* This space intentionally left blank */ // Case 2 - block + next block fits EXACTLY } else if ((blockSize + nextBlockSize) == blocks) { DBGLOG("exact realloc using next block - %i\n", blocks); umm_assimilate_up(c); blockSize += nextBlockSize; // Case 3 - prev block NOT free and block + next block fits } else if ((0 == prevBlockSize) && (blockSize + nextBlockSize) >= blocks) { DBGLOG("realloc using next block - %i\n", blocks); umm_assimilate_up(c); blockSize += nextBlockSize; // Case 4 - prev block + block fits } else if ((prevBlockSize + blockSize) >= blocks) { DBGLOG("realloc using prev block - %i\n", blocks); umm_disconnect_from_free_list(UMM_PBLOCK(c)); c = umm_assimilate_down(c, 0); memmove((void *)&UMM_DATA(c), ptr, curSize); ptr = (void *)&UMM_DATA(c); blockSize += prevBlockSize; // Case 5 - prev block + block + next block fits } else if ((prevBlockSize + blockSize + nextBlockSize) >= blocks) { DBGLOG("realloc using prev and next block - %i\n", blocks); umm_assimilate_up(c); umm_disconnect_from_free_list(UMM_PBLOCK(c)); c = umm_assimilate_down(c, 0); memmove((void *)&UMM_DATA(c), ptr, curSize); ptr = (void *)&UMM_DATA(c); blockSize += (prevBlockSize + nextBlockSize); // Case 6 - default is we need to realloc a new block } else { DBGLOG("realloc a completely new block %i\n", blocks); void *oldptr = ptr; if ((ptr = umm_malloc_core(size))) { DBGLOG("realloc %i to a bigger block %i, copy, and free the old\n", blockSize, blocks); memcpy(ptr, oldptr, curSize); umm_free_core(oldptr); } else { DBGLOG("realloc %i to a bigger block %i failed - return NULL and leave the old block!\n", blockSize, blocks); /* This space intentionally left blnk */ } blockSize = blocks; } /* Now all we need to do is figure out if the block fit exactly or if we * need to split and free ... */ if (blockSize > blocks) { DBGLOG("split and free %i blocks from %i\n", blocks, blockSize); umm_split_block(c, blocks, 0); umm_free_core((void *)&UMM_DATA(c + blocks)); } /* Release the critical section... */ UMM_CRITICAL_EXIT(id_realloc); return ptr; } // The openssl library expects a version of malloc that takes // extra parameters 'file' and 'line'. We provide these for openssl. // The extra parameters are ignored. void *umm_malloc_ssl(size_t size, const char *file, int line) { return umm_malloc(size); } void *umm_realloc_ssl(void *ptr, size_t size, const char *file, int line) { return umm_realloc(ptr, size); } void umm_free_ssl(void *ptr, const char *file, int line) { umm_free(ptr); }