基础知识:
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 流程 :
-
判断所需内存是否大于mmap_threshold,大于这个值才会直接调用mmap来分配内存 (还有一个是限制 mmap的内存数量 要小于 n_mmaps_max)
-
在当前的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就是做了两件事:
- 检查是否要通过 mmap来开辟一块内存
- 调整当前arena的top_chunk,通过向后拓展或者创建一个新的heap(mmap),之后在这个arena上分配需要的内存
具体的细节这里没有讲到太多,只分析了大致的流程。
参考:
《glibc-2.23源码》