mirror of
https://github.com/esp8266/Arduino.git
synced 2025-04-21 10:26:06 +03:00
1747 lines
58 KiB
C
1747 lines
58 KiB
C
/* ----------------------------------------------------------------------------
|
|
* 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.
|
|
* D.Frank 2014-04-02 - Fixed heap configuration when UMM_TEST_MAIN is NOT set,
|
|
* added user-dependent configuration file umm_malloc_cfg.h
|
|
* ----------------------------------------------------------------------------
|
|
*
|
|
* Note: when upgrading this file with upstream code, replace all %i with %d in
|
|
* printf format strings. ets_printf doesn't handle %i.
|
|
*
|
|
* ----------------------------------------------------------------------------
|
|
*
|
|
* 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 <stdio.h>
|
|
#include <string.h>
|
|
#include <pgmspace.h>
|
|
|
|
#include "umm_malloc.h"
|
|
|
|
#include "umm_malloc_cfg.h" /* user-dependent */
|
|
|
|
#ifndef UMM_FIRST_FIT
|
|
# ifndef UMM_BEST_FIT
|
|
# define UMM_BEST_FIT
|
|
# endif
|
|
#endif
|
|
|
|
#ifndef DBG_LOG_LEVEL
|
|
# undef DBG_LOG_LEVEL
|
|
# define DBG_LOG_LEVEL 0
|
|
#else
|
|
# undef DBG_LOG_LEVEL
|
|
# define DBG_LOG_LEVEL DBG_LOG_LEVEL
|
|
#endif
|
|
|
|
// Macro to place constant strings into PROGMEM and print them properly
|
|
#define printf(fmt, ...) do { static const char fstr[] PROGMEM = fmt; char rstr[sizeof(fmt)]; for (size_t i=0; i<sizeof(rstr); i++) rstr[i] = fstr[i]; printf(rstr, ##__VA_ARGS__); } while (0)
|
|
|
|
/* -- dbglog {{{ */
|
|
|
|
/* ----------------------------------------------------------------------------
|
|
* A set of macros that cleans up code that needs to produce debug
|
|
* or log information.
|
|
*
|
|
* See copyright notice in LICENSE.TXT
|
|
* ----------------------------------------------------------------------------
|
|
*
|
|
* There are macros to handle the following decreasing levels of detail:
|
|
*
|
|
* 6 = TRACE
|
|
* 5 = DEBUG
|
|
* 4 = CRITICAL
|
|
* 3 = ERROR
|
|
* 2 = WARNING
|
|
* 1 = INFO
|
|
* 0 = FORCE - The printf is always compiled in and is called only when
|
|
* the first parameter to the macro is non-0
|
|
*
|
|
* ----------------------------------------------------------------------------
|
|
*
|
|
* The following #define should be set up before this file is included so
|
|
* that we can be sure that the correct macros are defined.
|
|
*
|
|
* #define DBG_LOG_LEVEL x
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
|
|
#undef DBG_LOG_TRACE
|
|
#undef DBG_LOG_DEBUG
|
|
#undef DBG_LOG_CRITICAL
|
|
#undef DBG_LOG_ERROR
|
|
#undef DBG_LOG_WARNING
|
|
#undef DBG_LOG_INFO
|
|
#undef DBG_LOG_FORCE
|
|
|
|
/* ------------------------------------------------------------------------- */
|
|
|
|
#if DBG_LOG_LEVEL >= 6
|
|
# define DBG_LOG_TRACE( format, ... ) printf( format, ## __VA_ARGS__ )
|
|
#else
|
|
# define DBG_LOG_TRACE( format, ... )
|
|
#endif
|
|
|
|
#if DBG_LOG_LEVEL >= 5
|
|
# define DBG_LOG_DEBUG( format, ... ) printf( format, ## __VA_ARGS__ )
|
|
#else
|
|
# define DBG_LOG_DEBUG( format, ... )
|
|
#endif
|
|
|
|
#if DBG_LOG_LEVEL >= 4
|
|
# define DBG_LOG_CRITICAL( format, ... ) printf( format, ## __VA_ARGS__ )
|
|
#else
|
|
# define DBG_LOG_CRITICAL( format, ... )
|
|
#endif
|
|
|
|
#if DBG_LOG_LEVEL >= 3
|
|
# define DBG_LOG_ERROR( format, ... ) printf( format, ## __VA_ARGS__ )
|
|
#else
|
|
# define DBG_LOG_ERROR( format, ... )
|
|
#endif
|
|
|
|
#if DBG_LOG_LEVEL >= 2
|
|
# define DBG_LOG_WARNING( format, ... ) printf( format, ## __VA_ARGS__ )
|
|
#else
|
|
# define DBG_LOG_WARNING( format, ... )
|
|
#endif
|
|
|
|
#if DBG_LOG_LEVEL >= 1
|
|
# define DBG_LOG_INFO( format, ... ) printf( format, ## __VA_ARGS__ )
|
|
#else
|
|
# define DBG_LOG_INFO( format, ... )
|
|
#endif
|
|
|
|
#define DBG_LOG_FORCE( force, format, ... ) {if(force) {printf( format, ## __VA_ARGS__ );}}
|
|
|
|
/* }}} */
|
|
|
|
/* ------------------------------------------------------------------------- */
|
|
|
|
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)
|
|
|
|
/* ------------------------------------------------------------------------- */
|
|
|
|
#ifdef UMM_REDEFINE_MEM_FUNCTIONS
|
|
# define umm_free free
|
|
# define umm_malloc malloc
|
|
# define umm_calloc calloc
|
|
# define umm_realloc realloc
|
|
#endif
|
|
|
|
umm_block *umm_heap = NULL;
|
|
unsigned short int umm_numblocks = 0;
|
|
|
|
#define UMM_NUMBLOCKS (umm_numblocks)
|
|
|
|
/* ------------------------------------------------------------------------ */
|
|
|
|
#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)
|
|
|
|
/* integrity check (UMM_INTEGRITY_CHECK) {{{ */
|
|
#if defined(UMM_INTEGRITY_CHECK)
|
|
/*
|
|
* Perform integrity check of the whole heap data. Returns 1 in case of
|
|
* success, 0 otherwise.
|
|
*
|
|
* First of all, iterate through all free blocks, and check that all backlinks
|
|
* match (i.e. if block X has next free block Y, then the block Y should have
|
|
* previous free block set to X).
|
|
*
|
|
* Additionally, we check that each free block is correctly marked with
|
|
* `UMM_FREELIST_MASK` on the `next` pointer: during iteration through free
|
|
* list, we mark each free block by the same flag `UMM_FREELIST_MASK`, but
|
|
* on `prev` pointer. We'll check and unmark it later.
|
|
*
|
|
* Then, we iterate through all blocks in the heap, and similarly check that
|
|
* all backlinks match (i.e. if block X has next block Y, then the block Y
|
|
* should have previous block set to X).
|
|
*
|
|
* But before checking each backlink, we check that the `next` and `prev`
|
|
* pointers are both marked with `UMM_FREELIST_MASK`, or both unmarked.
|
|
* This way, we ensure that the free flag is in sync with the free pointers
|
|
* chain.
|
|
*/
|
|
static int integrity_check(void) {
|
|
int ok = 1;
|
|
unsigned short int prev;
|
|
unsigned short int cur;
|
|
|
|
if (umm_heap == NULL) {
|
|
umm_init();
|
|
}
|
|
|
|
/* Iterate through all free blocks */
|
|
prev = 0;
|
|
while(1) {
|
|
cur = UMM_NFREE(prev);
|
|
|
|
/* Check that next free block number is valid */
|
|
if (cur >= UMM_NUMBLOCKS) {
|
|
printf("heap integrity broken: too large next free num: %d "
|
|
"(in block %d, addr 0x%lx)\n", cur, prev,
|
|
(unsigned long)&UMM_NBLOCK(prev));
|
|
ok = 0;
|
|
goto clean;
|
|
}
|
|
if (cur == 0) {
|
|
/* No more free blocks */
|
|
break;
|
|
}
|
|
|
|
/* Check if prev free block number matches */
|
|
if (UMM_PFREE(cur) != prev) {
|
|
printf("heap integrity broken: free links don't match: "
|
|
"%d -> %d, but %d -> %d\n",
|
|
prev, cur, cur, UMM_PFREE(cur));
|
|
ok = 0;
|
|
goto clean;
|
|
}
|
|
|
|
UMM_PBLOCK(cur) |= UMM_FREELIST_MASK;
|
|
|
|
prev = cur;
|
|
}
|
|
|
|
/* Iterate through all blocks */
|
|
prev = 0;
|
|
while(1) {
|
|
cur = UMM_NBLOCK(prev) & UMM_BLOCKNO_MASK;
|
|
|
|
/* Check that next block number is valid */
|
|
if (cur >= UMM_NUMBLOCKS) {
|
|
printf("heap integrity broken: too large next block num: %d "
|
|
"(in block %d, addr 0x%lx)\n", cur, prev,
|
|
(unsigned long)&UMM_NBLOCK(prev));
|
|
ok = 0;
|
|
goto clean;
|
|
}
|
|
if (cur == 0) {
|
|
/* No more blocks */
|
|
break;
|
|
}
|
|
|
|
/* make sure the free mark is appropriate, and unmark it */
|
|
if ((UMM_NBLOCK(cur) & UMM_FREELIST_MASK)
|
|
!= (UMM_PBLOCK(cur) & UMM_FREELIST_MASK))
|
|
{
|
|
printf("heap integrity broken: mask wrong at addr 0x%lx: n=0x%x, p=0x%x\n",
|
|
(unsigned long)&UMM_NBLOCK(cur),
|
|
(UMM_NBLOCK(cur) & UMM_FREELIST_MASK),
|
|
(UMM_PBLOCK(cur) & UMM_FREELIST_MASK)
|
|
);
|
|
ok = 0;
|
|
goto clean;
|
|
}
|
|
|
|
/* unmark */
|
|
UMM_PBLOCK(cur) &= UMM_BLOCKNO_MASK;
|
|
|
|
/* Check if prev block number matches */
|
|
if (UMM_PBLOCK(cur) != prev) {
|
|
printf("heap integrity broken: block links don't match: "
|
|
"%d -> %d, but %d -> %d\n",
|
|
prev, cur, cur, UMM_PBLOCK(cur));
|
|
ok = 0;
|
|
goto clean;
|
|
}
|
|
|
|
prev = cur;
|
|
}
|
|
|
|
clean:
|
|
if (!ok){
|
|
UMM_HEAP_CORRUPTION_CB();
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
#define INTEGRITY_CHECK() integrity_check()
|
|
#else
|
|
/*
|
|
* Integrity check is disabled, so just define stub macro
|
|
*/
|
|
#define INTEGRITY_CHECK() 1
|
|
#endif
|
|
/* }}} */
|
|
|
|
/* poisoning (UMM_POISON) {{{ */
|
|
#if defined(UMM_POISON)
|
|
#define POISON_BYTE (0xa5)
|
|
|
|
/*
|
|
* Yields a size of the poison for the block of size `s`.
|
|
* If `s` is 0, returns 0.
|
|
*/
|
|
#define POISON_SIZE(s) ( \
|
|
(s) ? \
|
|
(UMM_POISON_SIZE_BEFORE + UMM_POISON_SIZE_AFTER + \
|
|
sizeof(UMM_POISONED_BLOCK_LEN_TYPE) \
|
|
) : 0 \
|
|
)
|
|
|
|
/*
|
|
* Print memory contents starting from given `ptr`
|
|
*/
|
|
static void dump_mem ( const unsigned char *ptr, size_t len ) {
|
|
while (len--) {
|
|
printf(" 0x%.2x", (unsigned int)(*ptr++));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Put poison data at given `ptr` and `poison_size`
|
|
*/
|
|
static void put_poison( unsigned char *ptr, size_t poison_size ) {
|
|
memset(ptr, POISON_BYTE, poison_size);
|
|
}
|
|
|
|
/*
|
|
* Check poison data at given `ptr` and `poison_size`. `where` is a pointer to
|
|
* a string, either "before" or "after", meaning, before or after the block.
|
|
*
|
|
* If poison is there, returns 1.
|
|
* Otherwise, prints the appropriate message, and returns 0.
|
|
*/
|
|
static int check_poison( const unsigned char *ptr, size_t poison_size,
|
|
const char *where) {
|
|
size_t i;
|
|
int ok = 1;
|
|
|
|
for (i = 0; i < poison_size; i++) {
|
|
if (ptr[i] != POISON_BYTE) {
|
|
ok = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!ok) {
|
|
printf("there is no poison %s the block. "
|
|
"Expected poison address: 0x%lx, actual data:",
|
|
where, (unsigned long)ptr);
|
|
dump_mem(ptr, poison_size);
|
|
printf("\n");
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
/*
|
|
* Check if a block is properly poisoned. Must be called only for non-free
|
|
* blocks.
|
|
*/
|
|
static int check_poison_block( umm_block *pblock ) {
|
|
int ok = 1;
|
|
|
|
if (pblock->header.used.next & UMM_FREELIST_MASK) {
|
|
printf("check_poison_block is called for free block 0x%lx\n",
|
|
(unsigned long)pblock);
|
|
} else {
|
|
/* the block is used; let's check poison */
|
|
unsigned char *pc = (unsigned char *)pblock->body.data;
|
|
unsigned char *pc_cur;
|
|
|
|
pc_cur = pc + sizeof(UMM_POISONED_BLOCK_LEN_TYPE);
|
|
if (!check_poison(pc_cur, UMM_POISON_SIZE_BEFORE, "before")) {
|
|
printf("block start: %08x\n", pc + sizeof(UMM_POISONED_BLOCK_LEN_TYPE) + UMM_POISON_SIZE_BEFORE);
|
|
UMM_HEAP_CORRUPTION_CB();
|
|
ok = 0;
|
|
goto clean;
|
|
}
|
|
|
|
pc_cur = pc + *((UMM_POISONED_BLOCK_LEN_TYPE *)pc) - UMM_POISON_SIZE_AFTER;
|
|
if (!check_poison(pc_cur, UMM_POISON_SIZE_AFTER, "after")) {
|
|
printf("block start: %08x\n", pc + sizeof(UMM_POISONED_BLOCK_LEN_TYPE) + UMM_POISON_SIZE_BEFORE);
|
|
UMM_HEAP_CORRUPTION_CB();
|
|
ok = 0;
|
|
goto clean;
|
|
}
|
|
}
|
|
|
|
clean:
|
|
return ok;
|
|
}
|
|
|
|
/*
|
|
* Iterates through all blocks in the heap, and checks poison for all used
|
|
* blocks.
|
|
*/
|
|
static int check_poison_all_blocks(void) {
|
|
int ok = 1;
|
|
unsigned short int blockNo = 0;
|
|
|
|
if (umm_heap == NULL) {
|
|
umm_init();
|
|
}
|
|
|
|
/* Now iterate through the blocks list */
|
|
blockNo = UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK;
|
|
|
|
while( UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK ) {
|
|
if ( !(UMM_NBLOCK(blockNo) & UMM_FREELIST_MASK) ) {
|
|
/* This is a used block (not free), so, check its poison */
|
|
ok = check_poison_block(&UMM_BLOCK(blockNo));
|
|
if (!ok){
|
|
break;
|
|
}
|
|
}
|
|
|
|
blockNo = UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK;
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
/*
|
|
* Takes a pointer returned by actual allocator function (`_umm_malloc` or
|
|
* `_umm_realloc`), puts appropriate poison, and returns adjusted pointer that
|
|
* should be returned to the user.
|
|
*
|
|
* `size_w_poison` is a size of the whole block, including a poison.
|
|
*/
|
|
static void *get_poisoned( unsigned char *ptr, size_t size_w_poison ) {
|
|
if (size_w_poison != 0 && ptr != NULL) {
|
|
|
|
/* Put exact length of the user's chunk of memory */
|
|
memcpy(ptr, &size_w_poison, sizeof(UMM_POISONED_BLOCK_LEN_TYPE));
|
|
|
|
/* Poison beginning and the end of the allocated chunk */
|
|
put_poison(ptr + sizeof(UMM_POISONED_BLOCK_LEN_TYPE),
|
|
UMM_POISON_SIZE_BEFORE);
|
|
put_poison(ptr + size_w_poison - UMM_POISON_SIZE_AFTER,
|
|
UMM_POISON_SIZE_AFTER);
|
|
|
|
/* Return pointer at the first non-poisoned byte */
|
|
return ptr + sizeof(UMM_POISONED_BLOCK_LEN_TYPE) + UMM_POISON_SIZE_BEFORE;
|
|
} else {
|
|
return ptr;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Takes "poisoned" pointer (i.e. pointer returned from `get_poisoned()`),
|
|
* and checks that the poison of this particular block is still there.
|
|
*
|
|
* Returns unpoisoned pointer, i.e. actual pointer to the allocated memory.
|
|
*/
|
|
static void *get_unpoisoned( unsigned char *ptr ) {
|
|
if (ptr != NULL) {
|
|
unsigned short int c;
|
|
|
|
ptr -= (sizeof(UMM_POISONED_BLOCK_LEN_TYPE) + UMM_POISON_SIZE_BEFORE);
|
|
|
|
/* Figure out which block we're in. Note the use of truncated division... */
|
|
c = (((char *)ptr)-(char *)(&(umm_heap[0])))/sizeof(umm_block);
|
|
|
|
check_poison_block(&UMM_BLOCK(c));
|
|
}
|
|
|
|
return ptr;
|
|
}
|
|
|
|
#define CHECK_POISON_ALL_BLOCKS() check_poison_all_blocks()
|
|
#define GET_POISONED(ptr, size) get_poisoned(ptr, size)
|
|
#define GET_UNPOISONED(ptr) get_unpoisoned(ptr)
|
|
|
|
#else
|
|
/*
|
|
* Integrity check is disabled, so just define stub macros
|
|
*/
|
|
#define POISON_SIZE(s) 0
|
|
#define CHECK_POISON_ALL_BLOCKS() 1
|
|
#define GET_POISONED(ptr, size) (ptr)
|
|
#define GET_UNPOISONED(ptr) (ptr)
|
|
#endif
|
|
/* }}} */
|
|
|
|
/* ----------------------------------------------------------------------------
|
|
* 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.
|
|
* ----------------------------------------------------------------------------
|
|
*/
|
|
|
|
UMM_HEAP_INFO ummHeapInfo;
|
|
|
|
void ICACHE_FLASH_ATTR *umm_info( void *ptr, int force ) {
|
|
|
|
unsigned short int blockNo = 0;
|
|
|
|
if (umm_heap == NULL) {
|
|
umm_init();
|
|
}
|
|
|
|
/* Protect the critical section... */
|
|
UMM_CRITICAL_ENTRY();
|
|
|
|
/*
|
|
* Clear out all of the entries in the ummHeapInfo structure before doing
|
|
* any calculations..
|
|
*/
|
|
memset( &ummHeapInfo, 0, sizeof( ummHeapInfo ) );
|
|
|
|
DBG_LOG_FORCE( force, "\n\nDumping the umm_heap...\n" );
|
|
|
|
DBG_LOG_FORCE( force, "|0x%08lx|B %5d|NB %5d|PB %5d|Z %5d|NF %5d|PF %5d|\n",
|
|
(unsigned long)(&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;
|
|
|
|
if (ummHeapInfo.maxFreeContiguousBlocks < curBlocks) {
|
|
ummHeapInfo.maxFreeContiguousBlocks = curBlocks;
|
|
}
|
|
|
|
DBG_LOG_FORCE( force, "|0x%08lx|B %5d|NB %5d|PB %5d|Z %5u|NF %5d|PF %5d|\n",
|
|
(unsigned long)(&UMM_BLOCK(blockNo)),
|
|
blockNo,
|
|
UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK,
|
|
UMM_PBLOCK(blockNo),
|
|
(unsigned int)curBlocks,
|
|
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 {
|
|
++ummHeapInfo.usedEntries;
|
|
ummHeapInfo.usedBlocks += curBlocks;
|
|
|
|
DBG_LOG_FORCE( force, "|0x%08lx|B %5d|NB %5d|PB %5d|Z %5u|\n",
|
|
(unsigned long)(&UMM_BLOCK(blockNo)),
|
|
blockNo,
|
|
UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK,
|
|
UMM_PBLOCK(blockNo),
|
|
(unsigned int)curBlocks );
|
|
}
|
|
|
|
blockNo = UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK;
|
|
}
|
|
|
|
/*
|
|
* Update the accounting totals with information from the last block, the
|
|
* rest must be free!
|
|
*/
|
|
|
|
{
|
|
size_t curBlocks = UMM_NUMBLOCKS-blockNo;
|
|
ummHeapInfo.freeBlocks += curBlocks;
|
|
ummHeapInfo.totalBlocks += curBlocks;
|
|
|
|
if (ummHeapInfo.maxFreeContiguousBlocks < curBlocks) {
|
|
ummHeapInfo.maxFreeContiguousBlocks = curBlocks;
|
|
}
|
|
}
|
|
|
|
DBG_LOG_FORCE( force, "|0x%08lx|B %5d|NB %5d|PB %5d|Z %5d|NF %5d|PF %5d|\n",
|
|
(unsigned long)(&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 %5d Used Entries %5d Free Entries %5d\n",
|
|
ummHeapInfo.totalEntries,
|
|
ummHeapInfo.usedEntries,
|
|
ummHeapInfo.freeEntries );
|
|
|
|
DBG_LOG_FORCE( force, "Total Blocks %5d Used Blocks %5d Free Blocks %5d\n",
|
|
ummHeapInfo.totalBlocks,
|
|
ummHeapInfo.usedBlocks,
|
|
ummHeapInfo.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)) );
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------ */
|
|
|
|
/*
|
|
* Split the block `c` into two blocks: `c` and `c + blocks`.
|
|
*
|
|
* - `cur_freemask` should be `0` if `c` used, or `UMM_FREELIST_MASK`
|
|
* otherwise.
|
|
* - `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_make_new_block( unsigned short int c,
|
|
unsigned short int blocks,
|
|
unsigned short int cur_freemask, unsigned short int 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) | cur_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 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_init( void ) {
|
|
/* init heap pointer and size, and memset it to 0 */
|
|
umm_heap = (umm_block *)UMM_MALLOC_CFG__HEAP_ADDR;
|
|
umm_numblocks = (UMM_MALLOC_CFG__HEAP_SIZE / sizeof(umm_block));
|
|
memset(umm_heap, 0x00, UMM_MALLOC_CFG__HEAP_SIZE);
|
|
|
|
/* setup initial blank heap structure */
|
|
{
|
|
/* index of the 0th `umm_block` */
|
|
const unsigned short int block_0th = 0;
|
|
/* index of the 1st `umm_block` */
|
|
const unsigned short int block_1th = 1;
|
|
/* index of the latest `umm_block` */
|
|
const unsigned short int block_last = UMM_NUMBLOCKS - 1;
|
|
|
|
/* setup the 0th `umm_block`, which just points to the 1st */
|
|
UMM_NBLOCK(block_0th) = block_1th;
|
|
UMM_NFREE(block_0th) = block_1th;
|
|
|
|
/*
|
|
* Now, we need to set the whole heap space as a huge free block. We should
|
|
* not touch the 0th `umm_block`, since it's special: the 0th `umm_block`
|
|
* 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.
|
|
*/
|
|
|
|
/*
|
|
* 1th `umm_block` has pointers:
|
|
*
|
|
* - next `umm_block`: the latest one
|
|
* - prev `umm_block`: the 0th
|
|
*
|
|
* 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.
|
|
*/
|
|
UMM_NBLOCK(block_1th) = block_last | UMM_FREELIST_MASK;
|
|
UMM_NFREE(block_1th) = 0;
|
|
UMM_PBLOCK(block_1th) = block_0th;
|
|
UMM_PFREE(block_1th) = block_0th;
|
|
|
|
/*
|
|
* latest `umm_block` has pointers:
|
|
*
|
|
* - next `umm_block`: 0 (meaning, there are no more `umm_blocks`)
|
|
* - prev `umm_block`: the 1st
|
|
*
|
|
* It's not a free block, so we don't touch NFREE / PFREE at all.
|
|
*/
|
|
UMM_NBLOCK(block_last) = 0;
|
|
UMM_PBLOCK(block_last) = block_1th;
|
|
}
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------ */
|
|
|
|
static 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 = (((char *)ptr)-(char *)(&(umm_heap[0])))/sizeof(umm_block);
|
|
|
|
DBG_LOG_DEBUG( "Freeing block %6d\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();
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------ */
|
|
|
|
static void *_umm_malloc( size_t size ) {
|
|
unsigned short int blocks;
|
|
unsigned short int blockSize = 0;
|
|
|
|
unsigned short int bestSize;
|
|
unsigned short int bestBlock;
|
|
|
|
unsigned short int cf;
|
|
|
|
if (umm_heap == NULL) {
|
|
umm_init();
|
|
}
|
|
|
|
/*
|
|
* 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( cf ) {
|
|
blockSize = (UMM_NBLOCK(cf) & UMM_BLOCKNO_MASK) - cf;
|
|
|
|
DBG_LOG_TRACE( "Looking at block %6d size %6d\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 && 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. */
|
|
DBG_LOG_DEBUG( "Allocating %6d blocks starting at %6d - 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 %6d blocks starting at %6d - 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_make_new_block( cf, blocks,
|
|
0/*`cf` is not free*/,
|
|
UMM_FREELIST_MASK/*new block is free*/);
|
|
|
|
/*
|
|
* `umm_make_new_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 */
|
|
|
|
DBG_LOG_DEBUG( "Can't allocate %5d blocks\n", blocks );
|
|
|
|
/* Release the critical section... */
|
|
UMM_CRITICAL_EXIT();
|
|
|
|
return( (void *)NULL );
|
|
}
|
|
|
|
/* Release the critical section... */
|
|
UMM_CRITICAL_EXIT();
|
|
|
|
return( (void *)&UMM_DATA(cf) );
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------ */
|
|
|
|
static void *_umm_realloc( void *ptr, size_t size ) {
|
|
|
|
unsigned short int blocks;
|
|
unsigned short int blockSize;
|
|
|
|
unsigned short int c;
|
|
|
|
size_t curSize;
|
|
|
|
if (umm_heap == NULL) {
|
|
umm_init();
|
|
}
|
|
|
|
/*
|
|
* 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 = (((char *)ptr)-(char *)(&(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 - %d, 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 %d 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 - %d, 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 %d to a smaller block %d, shrink and free the leftover bits\n", blockSize, blocks );
|
|
|
|
umm_make_new_block( c, blocks, 0, 0 );
|
|
_umm_free( (void *)&UMM_DATA(c+blocks) );
|
|
} else {
|
|
/* New block is bigger than the old block... */
|
|
|
|
void *oldptr = ptr;
|
|
|
|
DBG_LOG_DEBUG( "realloc %d to a bigger block %d, 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 );
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------ */
|
|
|
|
void *umm_malloc( size_t size ) {
|
|
void *ret;
|
|
|
|
/* check poison of each blocks, if poisoning is enabled */
|
|
if (!CHECK_POISON_ALL_BLOCKS()) {
|
|
return NULL;
|
|
}
|
|
|
|
/* check full integrity of the heap, if this check is enabled */
|
|
if (!INTEGRITY_CHECK()) {
|
|
return NULL;
|
|
}
|
|
|
|
size += POISON_SIZE(size);
|
|
|
|
ret = _umm_malloc( size );
|
|
|
|
ret = GET_POISONED(ret, size);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------ */
|
|
|
|
void *umm_calloc( size_t num, size_t item_size ) {
|
|
void *ret;
|
|
size_t size = item_size * num;
|
|
|
|
/* check poison of each blocks, if poisoning is enabled */
|
|
if (!CHECK_POISON_ALL_BLOCKS()) {
|
|
return NULL;
|
|
}
|
|
|
|
/* check full integrity of the heap, if this check is enabled */
|
|
if (!INTEGRITY_CHECK()) {
|
|
return NULL;
|
|
}
|
|
|
|
size += POISON_SIZE(size);
|
|
ret = _umm_malloc(size);
|
|
memset(ret, 0x00, size);
|
|
|
|
ret = GET_POISONED(ret, size);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------ */
|
|
|
|
void *umm_realloc( void *ptr, size_t size ) {
|
|
void *ret;
|
|
|
|
ptr = GET_UNPOISONED(ptr);
|
|
|
|
/* check poison of each blocks, if poisoning is enabled */
|
|
if (!CHECK_POISON_ALL_BLOCKS()) {
|
|
return NULL;
|
|
}
|
|
|
|
/* check full integrity of the heap, if this check is enabled */
|
|
if (!INTEGRITY_CHECK()) {
|
|
return NULL;
|
|
}
|
|
|
|
size += POISON_SIZE(size);
|
|
ret = _umm_realloc( ptr, size );
|
|
|
|
ret = GET_POISONED(ret, size);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------ */
|
|
|
|
void umm_free( void *ptr ) {
|
|
|
|
ptr = GET_UNPOISONED(ptr);
|
|
|
|
/* check poison of each blocks, if poisoning is enabled */
|
|
if (!CHECK_POISON_ALL_BLOCKS()) {
|
|
return;
|
|
}
|
|
|
|
/* check full integrity of the heap, if this check is enabled */
|
|
if (!INTEGRITY_CHECK()) {
|
|
return;
|
|
}
|
|
|
|
_umm_free( ptr );
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------ */
|
|
|
|
size_t ICACHE_FLASH_ATTR umm_free_heap_size( void ) {
|
|
umm_info(NULL, 0);
|
|
return (size_t)ummHeapInfo.freeBlocks * sizeof(umm_block);
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------ */
|