// ---------------------------------------------------------------------------- // umm_malloc.c - a memory allocator for embedded systems (microcontrollers) // // See copyright notice in LICENSE.TXT // ---------------------------------------------------------------------------- // // R.Hempel 2007-09-22 - Original // R.Hempel 2008-12-11 - Added MIT License biolerplate // - realloc() now looks to see if previous block is free // - made common operations functions // R.Hempel 2009-03-02 - Added macros to disable tasking // - Added function to dump heap and check for valid free // pointer // R.Hempel 2009-03-09 - Changed name to umm_malloc to avoid conflicts with // the mm_malloc() library functions // - Added some test code to assimilate a free block // with the very block if possible. Complicated and // not worth the grief. // ---------------------------------------------------------------------------- // // This is a memory management library specifically designed to work with the // ARM7 embedded processor, but it should work on many other 32 bit processors, // as well as 16 and 8 bit devices. // // ACKNOWLEDGEMENTS // // Joerg Wunsch and the avr-libc provided the first malloc() implementation // that I examined in detail. // // http://www.nongnu.org/avr-libc // // Doug Lea's paper on malloc() was another excellent reference and provides // a lot of detail on advanced memory management techniques such as binning. // // http://g.oswego.edu/dl/html/malloc.html // // Bill Dittman provided excellent suggestions, including macros to support // using these functions in critical sections, and for optimizing realloc() // further by checking to see if the previous block was free and could be // used for the new block size. This can help to reduce heap fragmentation // significantly. // // Yaniv Ankin suggested that a way to dump the current heap condition // might be useful. I combined this with an idea from plarroy to also // allow checking a free pointer to make sure it's valid. // // ---------------------------------------------------------------------------- // // The memory manager assumes the following things: // // 1. The standard POSIX compliant malloc/realloc/free semantics are used // 2. All memory used by the manager is allocated at link time, it is aligned // on a 32 bit boundary, it is contiguous, and its extent (start and end // address) is filled in by the linker. // 3. All memory used by the manager is initialized to 0 as part of the // runtime startup routine. No other initialization is required. // // The fastest linked list implementations use doubly linked lists so that // its possible to insert and delete blocks in constant time. This memory // manager keeps track of both free and used blocks in a doubly linked list. // // Most memory managers use some kind of list structure made up of pointers // to keep track of used - and sometimes free - blocks of memory. In an // embedded system, this can get pretty expensive as each pointer can use // up to 32 bits. // // In most embedded systems there is no need for managing large blocks // of memory dynamically, so a full 32 bit pointer based data structure // for the free and used block lists is wasteful. A block of memory on // the free list would use 16 bytes just for the pointers! // // This memory management library sees the malloc heap as an array of blocks, // and uses block numbers to keep track of locations. The block numbers are // 15 bits - which allows for up to 32767 blocks of memory. The high order // bit marks a block as being either free or in use, which will be explained // later. // // The result is that a block of memory on the free list uses just 8 bytes // instead of 16. // // In fact, we go even one step futher when we realize that the free block // index values are available to store data when the block is allocated. // // The overhead of an allocated block is therefore just 4 bytes. // // Each memory block holds 8 bytes, and there are up to 32767 blocks // available, for about 256K of heap space. If that's not enough, you // can always add more data bytes to the body of the memory block // at the expense of free block size overhead. // // There are a lot of little features and optimizations in this memory // management system that makes it especially suited to small embedded, but // the best way to appreciate them is to review the data structures and // algorithms used, so let's get started. // // ---------------------------------------------------------------------------- // // We have a general notation for a block that we'll use to describe the // different scenarios that our memory allocation algorithm must deal with: // // +----+----+----+----+ // c |* n | p | nf | pf | // +----+----+----+----+ // // Where - c is the index of this block // * is the indicator for a free block // n is the index of the next block in the heap // p is the index of the previous block in the heap // nf is the index of the next block in the free list // pf is the index of the previous block in the free list // // The fact that we have forward and backward links in the block descriptors // means that malloc() and free() operations can be very fast. It's easy // to either allocate the whole free item to a new block or to allocate part // of the free item and leave the rest on the free list without traversing // the list from front to back first. // // The entire block of memory used by the heap is assumed to be initialized // to 0. The very first block in the heap is special - it't the head of the // free block list. It is never assimilated with a free block (more on this // later). // // Once a block has been allocated to the application, it looks like this: // // +----+----+----+----+ // c | n | p | ... | // +----+----+----+----+ // // Where - c is the index of this block // n is the index of the next block in the heap // p is the index of the previous block in the heap // // Note that the free list information is gone, because it's now being used to // store actual data for the application. It would have been nice to store // the next and previous free list indexes as well, but that would be a waste // of space. If we had even 500 items in use, that would be 2,000 bytes for // free list information. We simply can't afford to waste that much. // // The address of the ... area is what is returned to the application // for data storage. // // The following sections describe the scenarios encountered during the // operation of the library. There are two additional notation conventions: // // ?? inside a pointer block means that the data is irrelevant. We don't care // about it because we don't read or modify it in the scenario being // described. // // ... between memory blocks indicates zero or more additional blocks are // allocated for use by the upper block. // // And while we're talking about "upper" and "lower" blocks, we should make // a comment about adresses. In the diagrams, a block higher up in the // picture is at a lower address. And the blocks grow downwards their // block index increases as does their physical address. // // Finally, there's one very important characteristic of the individual // blocks that make up the heap - there can never be two consecutive free // memory blocks, but there can be consecutive used memory blocks. // // The reason is that we always want to have a short free list of the // largest possible block sizes. By always assimilating a newly freed block // with adjacent free blocks, we maximize the size of each free memory area. // //--------------------------------------------------------------------------- // // Operation of malloc right after system startup // // As part of the system startup code, all of the heap has been cleared. // // During the very first malloc operation, we start traversing the free list // starting at index 0. The index of the next free block is 0, which means // we're at the end of the list! // // At this point, the malloc has a special test that checks if the current // block index is 0, which it is. This special case initializes the free // list to point at block index 1. // // BEFORE AFTER // // +----+----+----+----+ +----+----+----+----+ // 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | // +----+----+----+----+ +----+----+----+----+ // +----+----+----+----+ // 1 | 0 | 0 | 0 | 0 | // +----+----+----+----+ // // The heap is now ready to complete the first malloc operation. // // ---------------------------------------------------------------------------- // // Operation of malloc when we have reached the end of the free list and // there is no block large enough to accommodate the request. // // This happens at the very first malloc operation, or any time the free // list is traversed and no free block large enough for the request is // found. // // The current block pointer will be at the end of the free list, and we // know we're at the end of the list because the nf index is 0, like this: // // BEFORE AFTER // // +----+----+----+----+ +----+----+----+----+ // pf |*?? | ?? | cf | ?? | pf |*?? | ?? | lf | ?? | // +----+----+----+----+ +----+----+----+----+ // ... ... // +----+----+----+----+ +----+----+----+----+ // p | cf | ?? | ... | p | cf | ?? | ... | // +----+----+----+----+ +----+----+----+----+ // +----+----+----+----+ +----+----+----+----+ // cf | 0 | p | 0 | pf | c | lf | p | ... | // +----+----+----+----+ +----+----+----+----+ // +----+----+----+----+ // lf | 0 | cf | 0 | pf | // +----+----+----+----+ // // As we walk the free list looking for a block of size b or larger, we get // to cf, which is the last item in the free list. We know this because the // next index is 0. // // So we're going to turn cf into the new block of memory, and then create // a new block that represents the last free entry (lf) and adjust the prev // index of lf to point at the block we just created. We also need to adjust // the next index of the new block (c) to point to the last free block. // // Note that the next free index of the pf block must point to the new lf // because cf is no longer a free block! // // ---------------------------------------------------------------------------- // // Operation of malloc when we have found a block (cf) that will fit the // current request of b units exactly. // // This one is pretty easy, just clear the free list bit in the current // block and unhook it from the free list. // // BEFORE AFTER // // +----+----+----+----+ +----+----+----+----+ // pf |*?? | ?? | cf | ?? | pf |*?? | ?? | nf | ?? | // +----+----+----+----+ +----+----+----+----+ // ... ... // +----+----+----+----+ +----+----+----+----+ // p | cf | ?? | ... | p | cf | ?? | ... | // +----+----+----+----+ +----+----+----+----+ // +----+----+----+----+ +----+----+----+----+ Clear the free // cf |* n | p | nf | pf | cf | n | p | .. | list bit here // +----+----+----+----+ +----+----+----+----+ // +----+----+----+----+ +----+----+----+----+ // n | ?? | cf | ... | n | ?? | cf | ... | // +----+----+----+----+ +----+----+----+----+ // ... ... // +----+----+----+----+ +----+----+----+----+ // nf |*?? | ?? | ?? | cf | nf | ?? | ?? | ?? | pf | // +----+----+----+----+ +----+----+----+----+ // // Unhooking from the free list is accomplished by adjusting the next and // prev free list index values in the pf and nf blocks. // // ---------------------------------------------------------------------------- // // Operation of malloc when we have found a block that will fit the current // request of b units with some left over. // // We'll allocate the new block at the END of the current free block so we // don't have to change ANY free list pointers. // // BEFORE AFTER // // +----+----+----+----+ +----+----+----+----+ // pf |*?? | ?? | cf | ?? | pf |*?? | ?? | cf | ?? | // +----+----+----+----+ +----+----+----+----+ // ... ... // +----+----+----+----+ +----+----+----+----+ // p | cf | ?? | ... | p | cf | ?? | ... | // +----+----+----+----+ +----+----+----+----+ // +----+----+----+----+ +----+----+----+----+ // cf |* n | p | nf | pf | cf |* c | p | nf | pf | // +----+----+----+----+ +----+----+----+----+ // +----+----+----+----+ This is the new // c | n | cf | .. | block at cf+b // +----+----+----+----+ // +----+----+----+----+ +----+----+----+----+ // n | ?? | cf | ... | n | ?? | c | ... | // +----+----+----+----+ +----+----+----+----+ // ... ... // +----+----+----+----+ +----+----+----+----+ // nf |*?? | ?? | ?? | cf | nf | ?? | ?? | ?? | pf | // +----+----+----+----+ +----+----+----+----+ // // This one is prety easy too, except we don't need to mess with the // free list indexes at all becasue we'll allocate the new block at the // end of the current free block. We do, however have to adjust the // indexes in cf, c, and n. // // ---------------------------------------------------------------------------- // // That covers the initialization and all possible malloc scenarios, so now // we need to cover the free operation possibilities... // // The operation of free depends on the position of the current block being // freed relative to free list items immediately above or below it. The code // works like this: // // if next block is free // assimilate with next block already on free list // if prev block is free // assimilate with prev block already on free list // else // put current block at head of free list // // ---------------------------------------------------------------------------- // // Step 1 of the free operation checks if the next block is free, and if it // is then insert this block into the free list and assimilate the next block // with this one. // // Note that c is the block we are freeing up, cf is the free block that // follows it. // // BEFORE AFTER // // +----+----+----+----+ +----+----+----+----+ // pf |*?? | ?? | cf | ?? | pf |*?? | ?? | nf | ?? | // +----+----+----+----+ +----+----+----+----+ // ... ... // +----+----+----+----+ +----+----+----+----+ // p | c | ?? | ... | p | c | ?? | ... | // +----+----+----+----+ +----+----+----+----+ // +----+----+----+----+ +----+----+----+----+ This block is // c | cf | p | ... | c | nn | p | ... | disconnected // +----+----+----+----+ +----+----+----+----+ from free list, // +----+----+----+----+ assimilated with // cf |*nn | c | nf | pf | the next, and // +----+----+----+----+ ready for step 2 // +----+----+----+----+ +----+----+----+----+ // nn | ?? | cf | ?? | ?? | nn | ?? | c | ... | // +----+----+----+----+ +----+----+----+----+ // ... ... // +----+----+----+----+ +----+----+----+----+ // nf |*?? | ?? | ?? | cf | nf |*?? | ?? | ?? | pf | // +----+----+----+----+ +----+----+----+----+ // // Take special note that the newly assimilated block (c) is completely // disconnected from the free list, and it does not have its free list // bit set. This is important as we move on to step 2 of the procedure... // // ---------------------------------------------------------------------------- // // Step 2 of the free operation checks if the prev block is free, and if it // is then assimilate it with this block. // // Note that c is the block we are freeing up, pf is the free block that // precedes it. // // BEFORE AFTER // // +----+----+----+----+ +----+----+----+----+ This block has // pf |* c | ?? | nf | ?? | pf |* n | ?? | nf | ?? | assimilated the // +----+----+----+----+ +----+----+----+----+ current block // +----+----+----+----+ // c | n | pf | ... | // +----+----+----+----+ // +----+----+----+----+ +----+----+----+----+ // n | ?? | c | ... | n | ?? | pf | ?? | ?? | // +----+----+----+----+ +----+----+----+----+ // ... ... // +----+----+----+----+ +----+----+----+----+ // nf |*?? | ?? | ?? | pf | nf |*?? | ?? | ?? | pf | // +----+----+----+----+ +----+----+----+----+ // // Nothing magic here, except that when we're done, the current block (c) // is gone since it's been absorbed into the previous free block. Note that // the previous step guarantees that the next block (n) is not free. // // ---------------------------------------------------------------------------- // // Step 3 of the free operation only runs if the previous block is not free. // it just inserts the current block to the head of the free list. // // Remember, 0 is always the first block in the memory heap, and it's always // head of the free list! // // BEFORE AFTER // // +----+----+----+----+ +----+----+----+----+ // 0 | ?? | ?? | nf | 0 | 0 | ?? | ?? | c | 0 | // +----+----+----+----+ +----+----+----+----+ // ... ... // +----+----+----+----+ +----+----+----+----+ // p | c | ?? | ... | p | c | ?? | ... | // +----+----+----+----+ +----+----+----+----+ // +----+----+----+----+ +----+----+----+----+ // c | n | p | .. | c |* n | p | nf | 0 | // +----+----+----+----+ +----+----+----+----+ // +----+----+----+----+ +----+----+----+----+ // n | ?? | c | ... | n | ?? | c | ... | // +----+----+----+----+ +----+----+----+----+ // ... ... // +----+----+----+----+ +----+----+----+----+ // nf |*?? | ?? | ?? | 0 | nf |*?? | ?? | ?? | c | // +----+----+----+----+ +----+----+----+----+ // // Again, nothing spectacular here, we're simply adjusting a few pointers // to make the most recently freed block the first item in the free list. // // That's because finding the previous free block would mean a reverse // traversal of blocks until we found a free one, and it's just easier to // put it at the head of the list. No traversal is needed. // // ---------------------------------------------------------------------------- // // Finally, we can cover realloc, which has the following basic operation. // // The first thing we do is assimilate up with the next free block of // memory if possible. This step might help if we're resizing to a bigger // block of memory. It also helps if we're downsizing and creating a new // free block with the leftover memory. // // First we check to see if the next block is free, and we assimilate it // to this block if it is. If the previous block is also free, and if // combining it with the current block would satisfy the request, then we // assimilate with that block and move the current data down to the new // location. // // Assimilating with the previous free block and moving the data works // like this: // // BEFORE AFTER // // +----+----+----+----+ +----+----+----+----+ // pf |*?? | ?? | cf | ?? | pf |*?? | ?? | nf | ?? | // +----+----+----+----+ +----+----+----+----+ // ... ... // +----+----+----+----+ +----+----+----+----+ // cf |* c | ?? | nf | pf | c | n | ?? | ... | The data gets // +----+----+----+----+ +----+----+----+----+ moved from c to // +----+----+----+----+ the new data area // c | n | cf | ... | in cf, then c is // +----+----+----+----+ adjusted to cf // +----+----+----+----+ +----+----+----+----+ // n | ?? | c | ... | n | ?? | c | ?? | ?? | // +----+----+----+----+ +----+----+----+----+ // ... ... // +----+----+----+----+ +----+----+----+----+ // nf |*?? | ?? | ?? | cf | nf |*?? | ?? | ?? | pf | // +----+----+----+----+ +----+----+----+----+ // // // Once we're done that, there are three scenarios to consider: // // 1. The current block size is exactly the right size, so no more work is // needed. // // 2. The current block is bigger than the new required size, so carve off // the excess and add it to the free list. // // 3. The current block is still smaller than the required size, so malloc // a new block of the correct size and copy the current data into the new // block before freeing the current block. // // The only one of these scenarios that involves an operation that has not // yet been described is the second one, and it's shown below: // // BEFORE AFTER // // +----+----+----+----+ +----+----+----+----+ // p | c | ?? | ... | p | c | ?? | ... | // +----+----+----+----+ +----+----+----+----+ // +----+----+----+----+ +----+----+----+----+ // c | n | p | ... | c | s | p | ... | // +----+----+----+----+ +----+----+----+----+ // +----+----+----+----+ This is the // s | n | c | .. | new block at // +----+----+----+----+ c+blocks // +----+----+----+----+ +----+----+----+----+ // n | ?? | c | ... | n | ?? | s | ... | // +----+----+----+----+ +----+----+----+----+ // // Then we call free() with the adress of the data portion of the new // block (s) which adds it to the free list. // // ---------------------------------------------------------------------------- #include #include #include #include #include "umm_malloc.h" // ---------------------------------------------------------------------------- // // There are a number of defines you can set at compile time that affect how // the memory allocator will operate. In GNU C, you set these compile time // defines like this: // // -D UMM_TEST_MAIN // // Set this if you want to compile in the test suite at the end of this file. // If you do set this variable, then the function names are left alone as // umm_malloc() umm_free() and umm_realloc() so that they cannot be confused // with the C runtime functions malloc() free() and realloc() // // If you leave this variable unset, then the function names become malloc() // free() and realloc() so that they can be used as the C runtime functions // in an embedded environment. // // -D UMM_BEST_FIT (defualt) // // Set this if you want to use a best-fit algorithm for allocating new // blocks // // -D UMM_FIRST_FIT // // Set this if you want to use a first-fit algorithm for allocating new // blocks // // -D UMM_DBG_LOG_LEVEL=n // // Set n to a value from 0 to 6 depending on how verbose you want the debug // log to be // // ---------------------------------------------------------------------------- // // Support for this library in a multitasking environment is provided when // you add bodies to the UMM_CRITICAL_ENTRY and UMM_CRITICAL_EXIT macros // in umm_malloc.h // // ---------------------------------------------------------------------------- #ifndef UMM_FIRST_FIT # ifndef UMM_BEST_FIT # define UMM_BEST_FIT # endif #endif #ifndef UMM_DBG_LOG_LEVEL # undef DBG_LOG_LEVEL # define DBG_LOG_LEVEL 0 #else # undef DBG_LOG_LEVEL # define DBG_LOG_LEVEL UMM_DBG_LOG_LEVEL #endif #include "dbglog.h" // ---------------------------------------------------------------------------- UMM_H_ATTPACKPRE typedef struct umm_ptr_t { unsigned short int next; unsigned short int prev; } UMM_H_ATTPACKSUF umm_ptr; UMM_H_ATTPACKPRE typedef struct umm_block_t { union { umm_ptr used; } header; union { umm_ptr free; unsigned char data[4]; } body; } UMM_H_ATTPACKSUF umm_block; #define UMM_FREELIST_MASK (0x8000) #define UMM_BLOCKNO_MASK (0x7FFF) // ---------------------------------------------------------------------------- #ifndef UMM_TEST_MAIN #define umm_free free #define umm_malloc malloc #define umm_realloc realloc extern umm_block umm_heap[]; // Note that _UMM_NUMBLOCKS is a value that is computed at link time, and // it represents the number of blocks available for the memory manager. extern unsigned short int _UMM_NUMBLOCKS; // Link time calculations assign values to symbols, but you can't take // the value of something filled in at link time, you can only get its // address. // // That's why we take the address of _UMM_NUMBLOCKS and assign it to // the constant value umm_numblocks. const unsigned int umm_numblocks = (unsigned int)(&_UMM_NUMBLOCKS); #define UMM_NUMBLOCKS (umm_numblocks) #else umm_block umm_heap[2600]; const unsigned short int umm_numblocks = sizeof(umm_heap)/sizeof(umm_block); #define UMM_NUMBLOCKS (umm_numblocks) #endif // ---------------------------------------------------------------------------- #define UMM_BLOCK(b) (umm_heap[b]) #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) #define UMM_DATA(b) (UMM_BLOCK(b).body.data) // ---------------------------------------------------------------------------- // 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 heapInfo structure. // ---------------------------------------------------------------------------- UMM_HEAP_INFO heapInfo; void *umm_info( void *ptr, int force ) { unsigned short int blockNo = 0; // Protect the critical section... // UMM_CRITICAL_ENTRY(); // Clear out all of the entries in the heapInfo structure before doing // any calculations.. // memset( &heapInfo, 0, sizeof( heapInfo ) ); DBG_LOG_FORCE( force, "\n\nDumping the umm_heap...\n" ); DBG_LOG_FORCE( force, "|0x%08x|B %5i|NB %5i|PB %5i|Z %5i|NF %5i|PF %5i|\n", &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 ) { ++heapInfo.totalEntries; heapInfo.totalBlocks += (UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK )-blockNo; // Is this a free block? if( UMM_NBLOCK(blockNo) & UMM_FREELIST_MASK ) { ++heapInfo.freeEntries; heapInfo.freeBlocks += (UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK )-blockNo; DBG_LOG_FORCE( force, "|0x%08x|B %5i|NB %5i|PB %5i|Z %5i|NF %5i|PF %5i|\n", &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) ); // Does this block address match the ptr we may be trying to free? if( ptr == &UMM_BLOCK(blockNo) ) { // Release the critical section... // UMM_CRITICAL_EXIT(); return( ptr ); } } else { ++heapInfo.usedEntries; heapInfo.usedBlocks += (UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK )-blockNo; DBG_LOG_FORCE( force, "|0x%08x|B %5i|NB %5i|PB %5i|Z %5i|\n", &UMM_BLOCK(blockNo), blockNo, UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK, UMM_PBLOCK(blockNo), (UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK )-blockNo ); } blockNo = UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK; } // Update the accounting totals with information from the last block, the // rest must be free! heapInfo.freeBlocks += UMM_NUMBLOCKS-blockNo; heapInfo.totalBlocks += UMM_NUMBLOCKS-blockNo; DBG_LOG_FORCE( force, "|0x%08x|B %5i|NB %5i|PB %5i|Z %5i|NF %5i|PF %5i|\n", &UMM_BLOCK(blockNo), blockNo, UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK, UMM_PBLOCK(blockNo), UMM_NUMBLOCKS-blockNo, UMM_NFREE(blockNo), UMM_PFREE(blockNo) ); DBG_LOG_FORCE( force, "Total Entries %5i Used Entries %5i Free Entries %5i\n", heapInfo.totalEntries, heapInfo.usedEntries, heapInfo.freeEntries ); DBG_LOG_FORCE( force, "Total Blocks %5i Used Blocks %5i Free Blocks %5i\n", heapInfo.totalBlocks, heapInfo.usedBlocks, heapInfo.freeBlocks ); // Release the critical section... // UMM_CRITICAL_EXIT(); return( NULL ); } // ---------------------------------------------------------------------------- static unsigned short int 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. 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. size -= ( 1 + (sizeof(((umm_block *)0)->body)) ); return( 2 + size/(sizeof(umm_block)) ); } // ---------------------------------------------------------------------------- static void umm_make_new_block( unsigned short int c, unsigned short int blocks, unsigned short int freemask ) { UMM_NBLOCK(c+blocks) = UMM_NBLOCK(c) & UMM_BLOCKNO_MASK; UMM_PBLOCK(c+blocks) = c; UMM_PBLOCK(UMM_NBLOCK(c) & UMM_BLOCKNO_MASK) = (c+blocks); UMM_NBLOCK(c) = (c+blocks) | freemask; } // ---------------------------------------------------------------------------- static void umm_disconnect_from_free_list( unsigned short int 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); } // ---------------------------------------------------------------------------- static int foo = 0; static void umm_assimilate_up( unsigned short int 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 DBG_LOG_DEBUG( "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; } } // ---------------------------------------------------------------------------- static unsigned short int umm_assimilate_down( unsigned short int c, unsigned short int freemask ) { UMM_NBLOCK(UMM_PBLOCK(c)) = UMM_NBLOCK(c) | freemask; UMM_PBLOCK(UMM_NBLOCK(c)) = UMM_PBLOCK(c); return( UMM_PBLOCK(c) ); } // ---------------------------------------------------------------------------- void umm_free( void *ptr ) { unsigned short int c; // If we're being asked to free a NULL pointer, well that's just silly! if( (void *)0 == ptr ) { DBG_LOG_DEBUG( "free a null pointer -> do nothing\n" ); return; } // 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! // // NOTE: See the new umm_info() function that you can use to see if a ptr is // on the free list! // Protect the critical section... // UMM_CRITICAL_ENTRY(); // Figure out which block we're in. Note the use of truncated division... c = (ptr-(void *)(&(umm_heap[0])))/sizeof(umm_block); DBG_LOG_DEBUG( "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 ) { DBG_LOG_DEBUG( "Assimilate down to next 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 DBG_LOG_DEBUG( "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; } #if(0) // The following is experimental code that checks to see if the block we just // freed can be assimilated with the very last block - it's pretty convoluted in // terms of block index manipulation, and has absolutely no effect on heap // fragmentation. I'm not sure that it's worth including but I've left it // here for posterity. if( 0 == UMM_NBLOCK(UMM_NBLOCK(c) & UMM_BLOCKNO_MASK ) ) { if( UMM_PBLOCK(UMM_NBLOCK(c) & UMM_BLOCKNO_MASK) != UMM_PFREE(UMM_NBLOCK(c) & UMM_BLOCKNO_MASK) ) { UMM_NFREE(UMM_PFREE(UMM_NBLOCK(c) & UMM_BLOCKNO_MASK)) = c; UMM_NFREE(UMM_PFREE(c)) = UMM_NFREE(c); UMM_PFREE(UMM_NFREE(c)) = UMM_PFREE(c); UMM_PFREE(c) = UMM_PFREE(UMM_NBLOCK(c) & UMM_BLOCKNO_MASK); } UMM_NFREE(c) = 0; UMM_NBLOCK(c) = 0; } #endif // Release the critical section... // UMM_CRITICAL_EXIT(); } // ---------------------------------------------------------------------------- void *umm_malloc( size_t size ) { unsigned short int blocks; unsigned short int blockSize; unsigned short int bestSize; unsigned short int bestBlock; unsigned short int cf; // 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 ) { DBG_LOG_DEBUG( "malloc a block of 0 bytes -> do nothing\n" ); return( (void *)NULL ); } // Protect the critical section... // UMM_CRITICAL_ENTRY(); 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 = 0x7FFF; while( UMM_NFREE(cf) ) { blockSize = (UMM_NBLOCK(cf) & UMM_BLOCKNO_MASK) - cf; DBG_LOG_TRACE( "Looking at block %6i size %6i\n", cf, blockSize ); #if defined UMM_FIRST_FIT // This is the first block that fits! if( (blockSize >= blocks) ) break; #elif defined UMM_BEST_FIT if( (blockSize >= blocks) && (blockSize < bestSize) ) { bestBlock = cf; bestSize = blockSize; } #endif cf = UMM_NFREE(cf); } if( 0x7FFF != bestSize ) { cf = bestBlock; blockSize = bestSize; } if( UMM_NBLOCK(cf) & UMM_BLOCKNO_MASK ) { // 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. DBG_LOG_DEBUG( "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. DBG_LOG_DEBUG( "Allocating %6i blocks starting at %6i - existing\n", blocks, cf ); umm_make_new_block( cf, blockSize-blocks, UMM_FREELIST_MASK ); cf += blockSize-blocks; } } else { // We're at the end of the heap - allocate a new block, but check to see if // there's enough memory left for the requested block! Actually, we may need // one more than that if we're initializing the umm_heap for the first // time, which happens in the next conditional... if( UMM_NUMBLOCKS <= cf+blocks+1 ) { DBG_LOG_DEBUG( "Can't allocate %5i blocks at %5i\n", blocks, cf ); // Release the critical section... // UMM_CRITICAL_EXIT(); return( (void *)NULL ); } // Now check to see if we need to initialize the free list...this assumes // that the BSS is set to 0 on startup. We should rarely get to the end of // the free list so this is the "cheapest" place to put the initialization! if( 0 == cf ) { DBG_LOG_DEBUG( "Initializing malloc free block pointer\n" ); UMM_NBLOCK(0) = 1; UMM_NFREE(0) = 1; cf = 1; } DBG_LOG_DEBUG( "Allocating %6i blocks starting at %6i - new \n", blocks, cf ); UMM_NFREE(UMM_PFREE(cf)) = cf+blocks; memcpy( &UMM_BLOCK(cf+blocks), &UMM_BLOCK(cf), sizeof(umm_block) ); UMM_NBLOCK(cf) = cf+blocks; UMM_PBLOCK(cf+blocks) = cf; } // Release the critical section... // UMM_CRITICAL_EXIT(); return( (void *)&UMM_DATA(cf) ); } // ---------------------------------------------------------------------------- void *umm_realloc( void *ptr, size_t size ) { unsigned short int blocks; unsigned short int blockSize; unsigned short int 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) ) { DBG_LOG_DEBUG( "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 ) { DBG_LOG_DEBUG( "realloc to 0 size, just free the block\n" ); umm_free( ptr ); return( (void *)NULL ); } // Protect the critical section... // UMM_CRITICAL_ENTRY(); // 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 = (ptr-(void *)(&(umm_heap[0])))/sizeof(umm_block); // Figure out how big this block is... blockSize = (UMM_NBLOCK(c) - c); // Figure out how many bytes are in this block curSize = (blockSize*sizeof(umm_block))-(sizeof(((umm_block *)0)->header)); // Ok, now that we're here, we know the block number of the original chunk // of memory, and we know how much new memory we want, and we know the original // block size... if( blockSize == blocks ) { // This space intentionally left blank - return the original pointer! DBG_LOG_DEBUG( "realloc the same size block - %i, do nothing\n", blocks ); // Release the critical section... // UMM_CRITICAL_EXIT(); return( ptr ); } // Now we have a block size that could be bigger or smaller. Either // way, try to assimilate up to the next block before doing anything... // // If it's still too small, we have to free it anyways and it will save the // assimilation step later in free :-) umm_assimilate_up( c ); // Now check if it might help to assimilate down, but don't actually // do the downward assimilation unless the resulting block will hold the // new request! If this block of code runs, then the new block will // either fit the request exactly, or be larger than the request. if( (UMM_NBLOCK(UMM_PBLOCK(c)) & UMM_FREELIST_MASK) && (blocks <= (UMM_NBLOCK(c)-UMM_PBLOCK(c))) ) { // Check if the resulting block would be big enough... DBG_LOG_DEBUG( "realloc() could assimilate down %i blocks - fits!\n\r", c-UMM_PBLOCK(c) ); // Disconnect the previous block from the FREE list umm_disconnect_from_free_list( UMM_PBLOCK(c) ); // Connect the previous block to the next block ... and then // realign the current block pointer c = umm_assimilate_down(c, 0); // Move the bytes down to the new block we just created, but be sure to move // only the original bytes. memmove( (void *)&UMM_DATA(c), ptr, curSize ); // And don't forget to adjust the pointer to the new block location! ptr = (void *)&UMM_DATA(c); } // Now calculate the block size again...and we'll have three cases blockSize = (UMM_NBLOCK(c) - c); if( blockSize == blocks ) { // This space intentionally left blank - return the original pointer! DBG_LOG_DEBUG( "realloc the same size block - %i, do nothing\n", blocks ); } else if (blockSize > blocks ) { // New block is smaller than the old block, so just make a new block // at the end of this one and put it up on the free list... DBG_LOG_DEBUG( "realloc %i to a smaller block %i, shrink and free the leftover bits\n", blockSize, blocks ); umm_make_new_block( c, blocks, 0 ); umm_free( (void *)&UMM_DATA(c+blocks) ); } else { // New block is bigger than the old block... void *oldptr = ptr; DBG_LOG_DEBUG( "realloc %i to a bigger block %i, make new, copy, and free the old\n", blockSize, blocks ); // Now umm_malloc() a new/ one, copy the old data to the new block, and // free up the old block, but only if the malloc was sucessful! if( ptr = umm_malloc( size ) ) { memcpy( ptr, oldptr, curSize ); } umm_free( oldptr ); } // Release the critical section... // UMM_CRITICAL_EXIT(); return( ptr ); } // ---------------------------------------------------------------------------- #ifdef UMM_TEST_MAIN main() { void * ptr_array[256]; size_t i; int idx; printf( "Size of umm_heap is %i\n", sizeof(umm_heap) ); printf( "Size of header is %i\n", sizeof(umm_heap[0]) ); printf( "Size of nblock is %i\n", sizeof(umm_heap[0].header.used.next) ); printf( "Size of pblock is %i\n", sizeof(umm_heap[0].header.used.prev) ); printf( "Size of nfree is %i\n", sizeof(umm_heap[0].body.free.next) ); printf( "Size of pfree is %i\n", sizeof(umm_heap[0].body.free.prev) ); memset( umm_heap, 0, sizeof(umm_heap) ); umm_info( NULL, 1 ); for( idx=0; idx<256; ++idx ) ptr_array[idx] = (void *)NULL; for( idx=0; idx<6553500; ++idx ) { i = rand()%256; switch( rand() % 16 ) { case 0: case 1: case 2: case 3: case 4: case 5: case 6: ptr_array[i] = umm_realloc(ptr_array[i], 0); break; case 7: case 8: ptr_array[i] = umm_realloc(ptr_array[i], rand()%40 ); break; case 9: case 10: case 11: case 12: ptr_array[i] = umm_realloc(ptr_array[i], rand()%100 ); break; case 13: case 14: ptr_array[i] = umm_realloc(ptr_array[i], rand()%200 ); break; default: ptr_array[i] = umm_realloc(ptr_array[i], rand()%400 ); break; } } umm_info( NULL, 1 ); } #endif