Nuttx之mm_realloc
声明:此处代码分析,来源与 nuttx 12.8.0版本。
/***************************************************************************** Name: mm_realloc** Description:* If the reallocation is for less space, then:** (1) the current allocation is reduced in size* (2) the remainder at the end of the allocation is returned to the* free list.** If the request is for more space and the current allocation can be* extended, it will be extended by:** (1) Taking the additional space from the following free chunk, or* (2) Taking the additional space from the preceding free chunk.* (3) Or both** If the request is for more space but the current chunk cannot be* extended, then malloc a new buffer, copy the data into the new buffer,* and free the old buffer.*****************************************************************************/FAR void *mm_realloc(FAR struct mm_heap_s *heap, FAR void *oldmem,size_t size)
{FAR struct mm_allocnode_s *oldnode;FAR struct mm_freenode_s *prev = NULL;FAR struct mm_freenode_s *next;size_t newsize;size_t oldsize;size_t prevsize = 0;size_t nextsize = 0;FAR void *newmem;/* If oldmem is NULL, then realloc is equivalent to malloc */if (oldmem == NULL){return mm_malloc(heap, size);}DEBUGASSERT(mm_heapmember(heap, oldmem));#ifdef CONFIG_MM_HEAP_MEMPOOLif (heap->mm_mpool){newmem = mempool_multiple_realloc(heap->mm_mpool, oldmem, size);if (newmem != NULL){return newmem;}else if (size <= heap->mm_threshold ||mempool_multiple_alloc_size(heap->mm_mpool, oldmem) >= 0){newmem = mm_malloc(heap, size);if (newmem != NULL){memcpy(newmem, oldmem,MIN(size, mm_malloc_size(heap, oldmem)));mm_free(heap, oldmem);}return newmem;}}
#endif/* Adjust the size to account for (1) the size of the allocated node and* (2) to make sure that it is aligned with MM_ALIGN and its size is at* least MM_MIN_CHUNK.*/if (size < MM_MIN_CHUNK - MM_ALLOCNODE_OVERHEAD){size = MM_MIN_CHUNK - MM_ALLOCNODE_OVERHEAD;}newsize = MM_ALIGN_UP(size + MM_ALLOCNODE_OVERHEAD);if (newsize < size){/* There must have been an integer overflow */DEBUGPANIC();return NULL;}/* Map the memory chunk into an allocated node structure */oldnode = (FAR struct mm_allocnode_s *)((FAR char *)kasan_reset_tag(oldmem) - MM_SIZEOF_ALLOCNODE);/* We need to hold the MM mutex while we muck with the nodelist. */DEBUGVERIFY(mm_lock(heap));DEBUGASSERT(MM_NODE_IS_ALLOC(oldnode));/* Check if this is a request to reduce the size of the allocation. */oldsize = MM_SIZEOF_NODE(oldnode);if (newsize <= oldsize){/* Handle the special case where we are not going to change the size* of the allocation.*/if (newsize < oldsize){heap->mm_curused += newsize - oldsize;mm_shrinkchunk(heap, oldnode, newsize);kasan_poison((FAR char *)oldnode + MM_SIZEOF_NODE(oldnode) +sizeof(mmsize_t), oldsize - MM_SIZEOF_NODE(oldnode));}/* Then return the original address */mm_unlock(heap);MM_ADD_BACKTRACE(heap, oldnode);return oldmem;}/* This is a request to increase the size of the allocation, Get the* available sizes before and after the oldnode so that we can make the* best decision*/next = (FAR struct mm_freenode_s *)((FAR char *)oldnode + oldsize);if (MM_NODE_IS_FREE(next)){DEBUGASSERT(MM_PREVNODE_IS_ALLOC(next));nextsize = MM_SIZEOF_NODE(next);}if (MM_PREVNODE_IS_FREE(oldnode)){prev = (FAR struct mm_freenode_s *)((FAR char *)oldnode - oldnode->preceding);DEBUGASSERT(MM_NODE_IS_FREE(prev));prevsize = MM_SIZEOF_NODE(prev);}/* Now, check if we can extend the current allocation or not */if (nextsize + prevsize + oldsize >= newsize){size_t needed = newsize - oldsize;size_t nodesize = oldsize;size_t takeprev;size_t takenext;/* Check if we can extend into the previous chunk and if the* previous chunk is smaller than the next chunk.*/if (nextsize > prevsize){/* Can we get everything we need from the previous chunk? */if (needed > prevsize){/* No, take the whole previous chunk and get the* rest that we need from the next chunk.*/takeprev = prevsize;takenext = needed - prevsize;}else{/* Yes, take what we need from the previous chunk */takeprev = needed;takenext = 0;}}/* Check if we can extend into the next chunk and if we still need* more memory.*/else{/* Can we get everything we need from the next chunk? */if (needed > nextsize){/* No, take the whole next chunk and get the rest that we* need from the previous chunk.*/takeprev = needed - nextsize;takenext = nextsize;}else{/* Yes, take what we need from the previous chunk */takeprev = 0;takenext = needed;}}/* Extend into the previous free chunk */newmem = oldmem;if (takeprev){FAR struct mm_allocnode_s *newnode;/* Remove the previous node. There must be a predecessor, but* there may not be a successor node.*/DEBUGASSERT(prev && prev->blink);prev->blink->flink = prev->flink;if (prev->flink){prev->flink->blink = prev->blink;}/* Make sure the new previous node has enough space */if (prevsize < takeprev + MM_MIN_CHUNK){heap->mm_curused += prevsize - takeprev;takeprev = prevsize;}/* Extend the node into the previous free chunk */newnode = (FAR struct mm_allocnode_s *)((FAR char *)oldnode - takeprev);/* Did we consume the entire preceding chunk? */if (takeprev < prevsize){/* No.. just take what we need from the previous chunk and put* it back into the free list*/prevsize -= takeprev;prev->size = prevsize | (prev->size & MM_MASK_BIT);nodesize += takeprev;newnode->size = nodesize | MM_ALLOC_BIT | MM_PREVFREE_BIT;newnode->preceding = prevsize;/* Return the previous free node to the nodelist* (with the new size)*/mm_addfreechunk(heap, prev);}else{/* Yes.. update its size (newnode->preceding is already set) */nodesize += prevsize;newnode->size = nodesize | MM_ALLOC_BIT |(newnode->size & MM_MASK_BIT);}newmem = (FAR void *)((FAR char *)newnode + MM_SIZEOF_ALLOCNODE);/* Now we want to return newnode */oldnode = newnode;}/* Extend into the next free chunk */if (takenext){FAR struct mm_freenode_s *newnode;FAR struct mm_allocnode_s *andbeyond;/* Get the chunk following the next node (which could be the tail* chunk)*/andbeyond = (FAR struct mm_allocnode_s *)((FAR char *)next + nextsize);/* Remove the next node. There must be a predecessor, but there* may not be a successor node.*/DEBUGASSERT(next->blink);next->blink->flink = next->flink;if (next->flink){next->flink->blink = next->blink;}/* Make sure the new next node has enough space */if (nextsize < takenext + MM_MIN_CHUNK){heap->mm_curused += nextsize - takenext;takenext = nextsize;}/* Extend the node into the next chunk */nodesize += takenext;oldnode->size = nodesize | (oldnode->size & MM_MASK_BIT);/* Did we consume the entire preceding chunk? */if (takenext < nextsize){/* No, take what we need from the next chunk and return it to* the free nodelist.*/newnode = (FAR struct mm_freenode_s *)((FAR char *)oldnode + nodesize);newnode->size = nextsize - takenext;andbeyond->preceding = newnode->size;/* Add the new free node to the nodelist (with the new size) */mm_addfreechunk(heap, newnode);}else{/* Yes, just update some pointers. */andbeyond->size &= ~MM_PREVFREE_BIT;}}/* Update heap statistics */heap->mm_curused += newsize - oldsize;if (heap->mm_curused > heap->mm_maxused){heap->mm_maxused = heap->mm_curused;}sched_note_heap(NOTE_HEAP_FREE, heap, oldmem, oldsize,heap->mm_curused - newsize);sched_note_heap(NOTE_HEAP_ALLOC, heap, newmem, newsize,heap->mm_curused);mm_unlock(heap);MM_ADD_BACKTRACE(heap, (FAR char *)newmem - MM_SIZEOF_ALLOCNODE);newmem = kasan_unpoison(newmem, MM_SIZEOF_NODE(oldnode) -MM_ALLOCNODE_OVERHEAD);if (kasan_reset_tag(newmem) != kasan_reset_tag(oldmem)){/* Now we have to move the user contents 'down' in memory. memcpy* should be safe for this.*/memcpy(newmem, oldmem, oldsize - MM_ALLOCNODE_OVERHEAD);}return newmem;}/* The current chunk cannot be extended.* Just allocate a new chunk and copy*/else{/* Allocate a new block. On failure, realloc must return NULL but* leave the original memory in place.*/mm_unlock(heap);newmem = mm_malloc(heap, size);if (newmem){memcpy(newmem, oldmem, oldsize - MM_ALLOCNODE_OVERHEAD);mm_free(heap, oldmem);}return newmem;}
}
显然,此函数用于改变获取的堆空间的大小。那空间大小怎么个变法呢?当然是伸缩自如。
/***************************************************************************** Name: mm_realloc** Description:* If the reallocation is for less space, then:** (1) the current allocation is reduced in size* (2) the remainder at the end of the allocation is returned to the* free list.** If the request is for more space and the current allocation can be* extended, it will be extended by:** (1) Taking the additional space from the following free chunk, or* (2) Taking the additional space from the preceding free chunk.* (3) Or both** If the request is for more space but the current chunk cannot be* extended, then malloc a new buffer, copy the data into the new buffer,* and free the old buffer.*****************************************************************************/
对于第一种情况,缩小已分配空间。判断是此情况后,调用函数mm_shrinkchunk即可完成。
if (newsize <= oldsize){......mm_shrinkchunk(heap, oldnode, newsize);......}
mm_shrinkchunk会从oldnode的空间,将缩小的部分空间放入mm_heap_s 的mm_nodelist数组中。
对于第二种情况,扩展已分配空间。那,具体怎么个扩展法呢?正如注释所言,可向低地址扩展,也可向高地址扩展,还可以向高低两头扩展。我们现在说的都是在需要被调整的空间附近进行扩展,有人就要问了,附近是有多近?附近就是与被调整空间直接连着的空间。即扩展方向的空间与被调整空间在虚拟地址上是连续的。如果附近的空间不够怎么办呢?这就是扩展空间的另外一种情形,此时需要从mm_nodelist中完全重新找一段新的符合条件的空间。
反映到代码上是什么样呢。
首先判断确定附近的空间大小是否符合条件。
if (nextsize + prevsize + oldsize >= newsize)
这个判断包含了几种情况,具体是,nextsize == 0,只使用prevsize,prevsize == 0,只使用nextsize,nextsize与prevsize各使用一部分空间。因此接下需要对这几种情况进行区分。
/* Check if we can extend into the previous chunk and if the* previous chunk is smaller than the next chunk.*/ if (nextsize > prevsize){/* Can we get everything we need from the previous chunk? */if (needed > prevsize){/* No, take the whole previous chunk and get the* rest that we need from the next chunk.*/......}else{/* Yes, take what we need from the previous chunk */......}}/* Check if we can extend into the next chunk and if we still need* more memory.*/else{/* Can we get everything we need from the next chunk? */if (needed > nextsize){/* No, take the whole next chunk and get the rest that we* need from the previous chunk.*/......}else{/* Yes, take what we need from the previous chunk */......}}
上述的if (nextsize > prevsize)自然包含了prevsize == 0的情况,且与if (needed > prevsize)实现了另外的一种效果,那就是,优先使用小空间。不得不说,这几个if站的很高。艺术水平至少三层楼那么高。
确定完 前后需要扩展的空间大小之后,接下来就是具体的空间合并过程。此时还有一个调整即将被合并的空间大小的操作。
/* Make sure the new previous node has enough space */if (prevsize < takeprev + MM_MIN_CHUNK){heap->mm_curused += prevsize - takeprev;takeprev = prevsize;}/* Extend the node into the previous free chunk */newnode = (FAR struct mm_allocnode_s *)((FAR char *)oldnode - takeprev);
此处的if判断用于确定prevsize被合并后,剩余的空间是否能够有资格形成一个新的mm_freenode_s。何以见得呢? mm_addfreechunk函数会给出答案。
static inline_function void mm_addfreechunk(FAR struct mm_heap_s *heap,FAR struct mm_freenode_s *node)
{......size_t nodesize = MM_SIZEOF_NODE(node);......DEBUGASSERT(nodesize >= MM_MIN_CHUNK);......
}
同时当if(prevsize < takeprev + MM_MIN_CHUNK)成立时,mm_realloc最终扩展后返回给用户的空间,可能比mm_realloc的参数size要求的空间更大。
最后,扩展后的空间,如果起始地址发生发生改变,就需要memcpy来进行内容拷贝了。
if (kasan_reset_tag(newmem) != kasan_reset_tag(oldmem)){/* Now we have to move the user contents 'down' in memory. memcpy* should be safe for this.*/memcpy(newmem, oldmem, oldsize - MM_ALLOCNODE_OVERHEAD);}
那, 附近空间不够用怎么办呢?交给mm_malloc就行了。
newmem = mm_malloc(heap, size);if (newmem){memcpy(newmem, oldmem, oldsize - MM_ALLOCNODE_OVERHEAD);mm_free(heap, oldmem);}