目录
一、步骤1-判断边界情况,realloc也可以执行malloc和free的功能
二、步骤2-原chunk如果MMAP方式分配,申请新内存并拷贝实现
三、步骤3-非MMAP方式分配,则_int_realloc进行合并/裁剪等实现
1. _int_realloc函数:老chunk足够大,则裁剪
2. _int_realloc函数:尝试从Top chunk上进行扩展
3. _int_realloc函数:尝试合并nextchunk空间切割
4. _int_realloc函数:调用_int_malloc函数分配新内存
5. _int_realloc函数:裁剪返回Userchunk释放Remainder chunk
四、步骤4-如果_int_realloc分配失败,则尝试__libc_malloc分配一次
本章节主要讲解一下realloc()函数的实现。free函数的入口函数:__libc_realloc,该入口函数也在malloc.c的文件中。
前几章我们讲解了malloc的实现和free的实现,基本了解了ptmalloc是如何来管理内存结构的。此章节主要讲一下realloc()函数。该函数意思就是重置一个内存的大小。基本有以下几个步骤来实现该函数逻辑:
- 步骤1:判断边界情况,realloc也可以执行malloc和free的功能
- 步骤2:原chunk如果MMAP方式分配,则新申请内存并拷贝方式实现
- 步骤3:非MMAP方式分配,则_int_realloc进行合并/裁剪等方式实现
- 步骤4:如果_int_realloc分配失败,则尝试__libc_malloc分配一次
一、步骤1-判断边界情况,realloc也可以执行malloc和free的功能
边界情况主要有两种:
- 如果bytes为0的情况,相当于执行一次free操作
- 如果老的指针为空的情况下,相当于执行一次malloc操作
通过checked_request2size函数,检查bytes是否在分配合法区间内,并且将bytes进行对齐,最终得到对齐的nb大小的内存申请容量。
/*** 重新设置内存大小*/
void *
__libc_realloc (void *oldmem, size_t bytes)
{mstate ar_ptr;INTERNAL_SIZE_T nb; //字节对齐后需要分配的大小 /* padded request size */void *newp; //返回新的chunk地址 /* chunk to return */void *(*hook) (void *, size_t, const void *) =atomic_forced_read (__realloc_hook);if (__builtin_expect (hook != NULL, 0))return (*hook)(oldmem, bytes, RETURN_ADDRESS (0));//如果bytes==0 则相当于free,调用__libc_free函数
#if REALLOC_ZERO_BYTES_FREESif (bytes == 0 && oldmem != NULL){__libc_free (oldmem); return 0;}
#endif/* realloc of null is supposed to be same as malloc *//* 如果老的内存指针为空,则相当于重新分配一块内存,调用__libc_malloc函数 */if (oldmem == 0)return __libc_malloc (bytes);/* chunk corresponding to oldmem */const mchunkptr oldp = mem2chunk (oldmem); //获取老的chunk指针地址/* its size */const INTERNAL_SIZE_T oldsize = chunksize (oldp); //获取来的chunk的size/* 判断chunk是否为MMAP分配方式 IS_MMAPPED*/if (chunk_is_mmapped (oldp))ar_ptr = NULL; //分配区NULLelse{MAYBE_INIT_TCACHE ();ar_ptr = arena_for_chunk (oldp); //获取分配区}/* Little security check which won't hurt performance: the allocatornever wrapps around at the end of the address space. Thereforewe can exclude some size values which might appear here byaccident or by "design" from some intruder. We need to bypassthis check for dumped fake mmap chunks from the old main arenabecause the new malloc may provide additional alignment. */if ((__builtin_expect ((uintptr_t) oldp > (uintptr_t) -oldsize, 0)|| __builtin_expect (misaligned_chunk (oldp), 0))&& !DUMPED_MAIN_ARENA_CHUNK (oldp))malloc_printerr ("realloc(): invalid pointer");//检查bytes是否在合法区间内,最大小于<2147483647//并调用request2size函数,将bytes进行对齐,最终得到对齐的nb大小的内存申请容量if (!checked_request2size (bytes, &nb)) //XXX{__set_errno (ENOMEM);return NULL;}
二、步骤2-原chunk如果MMAP方式分配,申请新内存并拷贝实现
这里有几个逻辑:
- 如果是伪造的主分区的MMAP的chunk,则通过__lib_malloc函数申请一块新内存,将老数据拷贝memcpy到新内存上,老内存不free
- 如果是标准MMAP的chunk,则通过__lib_malloc函数申请一块新内存,将老数据拷贝到新内存上,最后需要调用munmap_chunk销毁这个chunk
- 如果老的内存块数据减去一个SIZE_SZ,还能比新的对齐后的内存块大,则直接返回老内存,不需要做任何改变
//如果是MMAP的方式if (chunk_is_mmapped (oldp)){/* If this is a faked mmapped chunk from the dumped main arena,always make a copy (and do not free the old chunk). *///如果是伪造的主分区的一个MMAP的chunk//重新申请一个内存块,并将老的内存块数据拷贝到新的内存块上if (DUMPED_MAIN_ARENA_CHUNK (oldp)){/* Must alloc, copy, free. */void *newmem = __libc_malloc (bytes);if (newmem == 0)return NULL;/* Copy as many bytes as are available from the old chunkand fit into the new size. NB: The overhead for fakedmmapped chunks is only SIZE_SZ, not 2 * SIZE_SZ as forregular mmapped chunks. */if (bytes > oldsize - SIZE_SZ)bytes = oldsize - SIZE_SZ;memcpy (newmem, oldmem, bytes); //内存拷贝return newmem;}void *newmem;#if HAVE_MREMAPnewp = mremap_chunk (oldp, nb);if (newp)return chunk2mem (newp);
#endif/* Note the extra SIZE_SZ overhead. *///如果老的内存块的size减去SIZE_SZ后,都比新申请的大,则什么都不做,返回老的指针if (oldsize - SIZE_SZ >= nb)return oldmem; /* do nothing *//* Must alloc, copy, free. */newmem = __libc_malloc (bytes); //新申请内存if (newmem == 0)return 0; /* propagate failure */memcpy (newmem, oldmem, oldsize - 2 * SIZE_SZ); //拷贝oldsize为chunk的大小,2 * SIZE_SZ存储chunk的结构munmap_chunk (oldp); //MMAP分配的,调用munmap_chunk所以释放老的内存地址return newmem;}
三、步骤3-非MMAP方式分配,则_int_realloc进行合并/裁剪等实现
单线程情况下直接调用_int_realloc函数。
多线程情况下需要加锁然后调用_int_realloc实现。
/* 单线程情况 */if (SINGLE_THREAD_P){newp = _int_realloc (ar_ptr, oldp, oldsize, nb); //直接调用_int_realloc核心函数assert (!newp || chunk_is_mmapped (mem2chunk (newp)) ||ar_ptr == arena_for_chunk (mem2chunk (newp)));return newp; //返回新的指针地址}/* 多线程模式下,非MMAP分配方式 */__libc_lock_lock (ar_ptr->mutex); //加锁newp = _int_realloc (ar_ptr, oldp, oldsize, nb); //调用_int_realloc核心函数__libc_lock_unlock (ar_ptr->mutex); //解锁assert (!newp || chunk_is_mmapped (mem2chunk (newp)) ||ar_ptr == arena_for_chunk (mem2chunk (newp)));
1. _int_realloc函数:老chunk足够大,则裁剪
老的chunk内存,大于新分配的,空间足够,则可以进行分割。将老的内存切割成2个chunk,一个返回给用户端,一个放入bins上进行管理。
oldsize需要大于nb,nb是经过checked_request2size函数进行字节对齐的内存大小。
/*** realloc核心函数*/
void*
_int_realloc(mstate av, mchunkptr oldp, INTERNAL_SIZE_T oldsize,INTERNAL_SIZE_T nb)
{mchunkptr newp; /* chunk to return */INTERNAL_SIZE_T newsize; /* its size */void* newmem; /* corresponding user mem */mchunkptr next; /* next contiguous chunk after oldp */mchunkptr remainder; /* extra space at end of newp */unsigned long remainder_size; /* its size *//* oldmem size */if (__builtin_expect (chunksize_nomask (oldp) <= 2 * SIZE_SZ, 0)|| __builtin_expect (oldsize >= av->system_mem, 0))malloc_printerr ("realloc(): invalid old size");check_inuse_chunk (av, oldp); //检查使用状态/* All callers already filter out mmap'ed chunks. */assert (!chunk_is_mmapped (oldp));next = chunk_at_offset (oldp, oldsize); //获取下一个chunkINTERNAL_SIZE_T nextsize = chunksize (next); //下一个chunk的sizeif (__builtin_expect (chunksize_nomask (next) <= 2 * SIZE_SZ, 0)|| __builtin_expect (nextsize >= av->system_mem, 0))malloc_printerr ("realloc(): invalid next size");//老的chunk内存,大于新分配的,空间足够,则可以进行分割if ((unsigned long) (oldsize) >= (unsigned long) (nb)){/* already big enough; split below */newp = oldp;newsize = oldsize;}
2. _int_realloc函数:尝试从Top chunk上进行扩展
如果 nextchunk 是Topchunk ,并且新的chunk大小大于申请的大小,则直接合并Topchunk进行切割。
调整av->top的值,设置Topchunk 标记物理相邻前一个chunk为使用中PREV_INUSE。
else{/* Try to expand forward into top *//* 如果 nextchunk 是Topchunk ,并且新的chunk大小大于申请的大小,则直接合并Topchunk进行切割*/if (next == av->top &&(unsigned long) (newsize = oldsize + nextsize) >=(unsigned long) (nb + MINSIZE)){set_head_size (oldp, nb | (av != &main_arena ? NON_MAIN_ARENA : 0)); //设置头av->top = chunk_at_offset (oldp, nb); //调整av->top的值set_head (av->top, (newsize - nb) | PREV_INUSE); //设置Topchunk 标记物理相邻前一个chunk为使用中check_inuse_chunk (av, oldp);return chunk2mem (oldp); //chunk指针地址 转到 内存指针地址}
3. _int_realloc函数:尝试合并nextchunk空间切割
如果nextchunk不是Top chunk ,并且next为空闲状态,则将nextchunk进行合并。合并完了之后,调用unlink_chunk,解除nextchunk的bins上的关系。
/* Try to expand forward into next chunk; split off remainder below *//* 如果nextchunk不是Top chunk ,并且next为空闲状态,则将nextchunk进行合并*/else if (next != av->top &&!inuse (next) &&(unsigned long) (newsize = oldsize + nextsize) >=(unsigned long) (nb)){newp = oldp;unlink_chunk (av, next); //解除空闲chunk的bins上的关系}
4. _int_realloc函数:调用_int_malloc函数分配新内存
首先调用_int_malloc函数,直接分配一个新的newmem。通过newmem,获取得到当前chunk的大小和chunk地址。如果新分配的正好是nextchunk,则直接合并到一起。如果不是,则释放老内存,拷贝就内存数据到新内存上。
/* allocate, copy, free *//* nextchunk为使用中,或其它情况 */else{newmem = _int_malloc (av, nb - MALLOC_ALIGN_MASK); //调用_int_malloc新分配一块内存if (newmem == 0)return 0; /* propagate failure */newp = mem2chunk (newmem); //返回新的指针地址newsize = chunksize (newp); //返回新的chunk的size/*Avoid copy if newp is next chunk after oldp.*//* 如果新分配的物理地址是老chunk的下一个chunk */if (newp == next){newsize += oldsize; //调整size,合并nextchunknewp = oldp;}else{memcpy (newmem, chunk2mem (oldp), oldsize - SIZE_SZ); //开始进行内存拷贝_int_free (av, oldp, 1); //释放老内存check_inuse_chunk (av, newp);return chunk2mem (newp); //返回内存地址}}}
5. _int_realloc函数:裁剪返回Userchunk释放Remainder chunk
老的内存块经过合并后,足够大,则需要进行裁剪操作。如果进行裁剪前,发现没有足够的空间了,则不裁剪了,设置一下nextchunk的标记位就行了;如果有足够空间,则裁剪出Remainder chunk,并通过_int_free函数,将裁剪出来的剩余chunk释放到bins上管理。
/* If possible, free extra space in old or extended chunk */assert ((unsigned long) (newsize) >= (unsigned long) (nb));/* 老的内存块经过合并后,足够大,则需要进行裁剪操作 */remainder_size = newsize - nb; //裁剪剩余的内存/* 进行裁剪前,如果没有足够的空间了,则不裁剪了 */if (remainder_size < MINSIZE) /* not enough extra to split off */{set_head_size (newp, newsize | (av != &main_arena ? NON_MAIN_ARENA : 0));set_inuse_bit_at_offset (newp, newsize); //下一个chunk标记前一个chunk使用中}else /* split remainder */{remainder = chunk_at_offset (newp, nb); //获取裁剪的chunkset_head_size (newp, nb | (av != &main_arena ? NON_MAIN_ARENA : 0));set_head (remainder, remainder_size | PREV_INUSE |(av != &main_arena ? NON_MAIN_ARENA : 0)); //设置裁剪chunk的状态/* Mark remainder as inuse so free() won't complain */set_inuse_bit_at_offset (remainder, remainder_size); //_int_free (av, remainder, 1); //释放裁剪后的chunk}check_inuse_chunk (av, newp);return chunk2mem (newp); //返回内存
}
四、步骤4-如果_int_realloc分配失败,则尝试__libc_malloc分配一次
如果调用_int_realloc失败,则尝试__libc_malloc,强制重新分配一次。
/* 分配失败 ,则调用__libc_malloc进行重试一次*/if (newp == NULL){/* Try harder to allocate memory in other arenas. */LIBC_PROBE (memory_realloc_retry, 2, bytes, oldmem);newp = __libc_malloc (bytes);/* 分配成功,调用memcpy拷贝数据, _int_free释放老的chunk*/if (newp != NULL){memcpy (newp, oldmem, oldsize - SIZE_SZ);_int_free (ar_ptr, oldp, 0);}}return newp;
}
libc_hidden_def (__libc_realloc)