0
点赞
收藏
分享

微信扫一扫

glibc-ptmalloc sysmalloc函数代码分析

小美人鱼失去的腿 2022-04-25 阅读 86

基础知识:
   malloc_state 和 heap_info

   heap 就是一块大的内存区域,若不是main_arena,heap是mmap创建的,main_arena是sbrk拓展得到的(拓展失败也会调用mmap) (由于main_arena的heap在内存开始处没有保存heap的info,所以和非main_arena的heap还有稍微有点区别的)

malloc_state 结构体是用来管理bins和heap的结构体,(top chunk 指向的就是一个heap的可用区域)。

当我们从一个arena上分配内存的时候,top chunk指向的就是当前的heap可用区域,那么之前用过的heap储存在哪里呢? 其实空闲的chunk都在bins里面,只要在bins里面,就有机会被使用,所以不需要记录其他的heap,只要记录当前的heap (top chunk)即可,这样从Bins里面找不到合适的chunk的时候,就要从top chunk里面分割了。

//创建一个新的堆,页对齐
static heap_info * internal_function new_heap (size_t size, size_t top_pad)
{
    size_t pagesize = GLRO (dl_pagesize);
    char *p1, *p2;
    unsigned long ul;
    heap_info *h;
    if (size + top_pad < HEAP_MIN_SIZE)
      size = HEAP_MIN_SIZE;
    else if (size + top_pad <= HEAP_MAX_SIZE)
      size += top_pad;
    else if (size > HEAP_MAX_SIZE)
      return 0;
    else
      size = HEAP_MAX_SIZE;
    size = ALIGN_UP (size, pagesize);
    /* A memory region aligned to a multiple of HEAP_MAX_SIZE is needed.
     No swap space needs to be reserved for the following large
     mapping (on Linux, this is the case for all non-writable mappings
     anyway). */
    p2 = MAP_FAILED;
    if (aligned_heap_area)
    {
        p2 = (char *) MMAP (aligned_heap_area, HEAP_MAX_SIZE, PROT_NONE,
                          MAP_NORESERVE);
        aligned_heap_area = NULL;
        if (p2 != MAP_FAILED && ((unsigned long) p2 & (HEAP_MAX_SIZE - 1)))
        {
          //没有对齐,unmap掉内存.
          __munmap (p2, HEAP_MAX_SIZE);
          p2 = MAP_FAILED;
        }
    }
    if (p2 == MAP_FAILED)
    {
        //可能是为了提高HEAP_MAX_SIZE对齐的概率吧,先分配了两个MAX_SIZE的chunk
        p1 = (char *) MMAP (0, HEAP_MAX_SIZE << 1, PROT_NONE, MAP_NORESERVE);
        if (p1 != MAP_FAILED)
        {
          //p2是p1开始对齐HEAP_MAX_SIZE的地址
          p2 = (char *) (((unsigned long) p1 + (HEAP_MAX_SIZE - 1))
                         & ~(HEAP_MAX_SIZE - 1));
          //找到差值,这部分不会被使用,可以unmap了.
          ul = p2 - p1;
          if (ul)
            __munmap (p1, ul);
          else
            aligned_heap_area = p2 + HEAP_MAX_SIZE;
          //再把p2之后多余的部分unmap掉.
          __munmap (p2 + HEAP_MAX_SIZE, HEAP_MAX_SIZE - ul);
        }
        else
        {
          /* Try to take the chance that an allocation of only HEAP_MAX_SIZE
             is already aligned. */
          p2 = (char *) MMAP (0, HEAP_MAX_SIZE, PROT_NONE, MAP_NORESERVE);
          if (p2 == MAP_FAILED)
            return 0;
            //没有对齐HEAP_MAX_SIZE,看作是失败.
          if ((unsigned long) p2 & (HEAP_MAX_SIZE - 1))
          {
             __munmap (p2, HEAP_MAX_SIZE);
             return 0;
          }
        }
    }
    //设置虚拟内存属性失败,返回NULL.
    if (__mprotect (p2, size, PROT_READ | PROT_WRITE) != 0)
    {
      __munmap (p2, HEAP_MAX_SIZE);
      return 0;
    }
    //p2 就是HEAP_MAX_SIZE的内存地址,HEAP_MAX_SIZE对齐.
    //这块内存开始的位置储存的是HEAP_INFO.
    h = (heap_info *) p2;
    h->size = size;
    h->mprotect_size = size;
    LIBC_PROBE (memory_heap_new, 2, h, h->size);
    return h;
}

上面这段代码是new_heap函数的代码,上面这段代码主要的作用就是mmap一块MAX_HEAP_SIZE大小的内存区域,在内存开始的地方保存heap_info, (参考heap_info结构体)。这个函数具体的细节这里就不过多分析了。

sysmalloc代码分析:
    在看sysmalloc代码之前,我们先要知道什么情况下会调用sysmalloc,看一下_int_malloc的源码,在最后top_chunk上无法分割出需要的内存的时候,就会调用sysmalloc来分配内存。

sysmalloc 流程 :

  1. 判断所需内存是否大于mmap_threshold,大于这个值才会直接调用mmap来分配内存 (还有一个是限制 mmap的内存数量 要小于 n_mmaps_max)

  2. 在当前的arena上分配内存
    先看三行代码:

	old_top = av->top;										//当前的top_chunk
  	old_size = chunksize(old_top);								//chunk_size
  	old_end = (char *)(chunk_at_offset(old_top, old_size));			//

			a)	非main_arena
						获取当前的heap:
						heap_info *old_heap, *heap;
						size_t old_heap_size;
						old_heap = heap_for_ptr(old_top);
						old_heap_size = old_heap->size;
						这里的heap_for_ptr就是old_top(top_chunk)地址向下对齐到MAX_HEAP_SIZE,
						得到的就是heap的开始地址 (参考前面的new_heap函数) 		(从这个操作我们可
						以看出,top chunk指向的就是当前heap的剩余可用区域)
						
						i). 尝试向后拓展当前的heap (要拓展的大小大约是need bytes)
					如果拓展成功了,这时候该arena的top chunk上就有足够的内存来分配了
						
						j). 没有拓展成功,就调用new_heap来创建一个新的heap,并把当前arena的
						top_chunk指向当前这个新的heap 可用区域处。那么剩余的那个old_chunk呢???
						old_chunk也有一定的大小,难道就被浪费了吗?? 这里也没有其他的东西来记录这
						个chunk呀?
						 其实后面马上就会调用_int_free把这个chunk丢到bins里面,只要在bins里面,这个
						 chunk之后就还能得到利用。
						 
					    l). 上面两步都失败了,跳到最开始mmap那里尝试mmap一块内存.


			b).	 main_arena
					由于main_arena一开始top_chunk是bss段之后的那里的区域,不是new_heap创
					建的,main_arena的处理有点区别.
					
					i).	先尝试调用sbrk向后拓展.拓展失败调用mmap 分配一块内存
						brk 保存了新内存的地址
						
					j).	
						1).	若brk和old_end是一样的,说明新的内存和旧的是连续的,相当于是拓展了
								main_arena的top chunk
						
						2).	 brk < old_end 错误
						
						3).	brk 与old_end不连续的情况,	
								处理contiguous的情况
										计算出brk和old_end直接的差值,给再给old_chunk拓展相应的大小
										(这里没有太明白,有大佬看懂了求教教)
										对齐处理.aligned_brk
								处理非contiguous的情况
										对齐brk->aligned_brk
							 aligned_brk现在就是新分配内存的地址了,把top_chunk设置为aligned_brk,同样把	
							 	old_chunk  free掉,这样old_chunk之后才有机会得到利用。
			c).	经过前两步那么多的操作,现在top chunk获取有足够的内存来供我们使用了,他可能是
			之前的top chunk向后拓展了一段区域,也有可能是一个新的heap.接下来就可以在这
			个top chunk来分配出我们所需要的内存了。

最后总结一下,其实sysmalloc就是做了两件事:

  1. 检查是否要通过 mmap来开辟一块内存
  2. 调整当前arena的top_chunk,通过向后拓展或者创建一个新的heap(mmap),之后在这个arena上分配需要的内存

具体的细节这里没有讲到太多,只分析了大致的流程。

参考:
《glibc-2.23源码》

举报

相关推荐

0 条评论