arena与top chunk简介
ptmalloc中一个重要的概念就是arena(分配区)。arena分为主分配区(main arena)和非主分配区(non main arena),主分配区与非主分配区用环形链表进行管理,每个分配区利用互斥锁使线程对于该分配区的访问互斥。每个进程中只有一个主分配区,但可能存在多个非主分配区,ptmalloc根据系统对分配区的竞争情况动态增加非主分配区的数量,非主分配区的数量一旦增加,就不会再减少了。主分配区可以访问进程的heap区域和mmap映射区域,这意味着主分配区可以使用sbrk和mmap向操作系统申请内存,而非主分配区只能访问进程的mmap映射区域。
当某一线程需要调用malloc分配内存空间时,该线程先查看线程私有变量中是否已经存在一个分配区,如果存在,则尝试对该分配区进行加锁,如果加锁成功,则使用该分配区分配内存;如果失败,那么malloc会开辟一个新的分配区,并将该分配区加入到全局分配区循环链表中,然后使用该分配区分配内存。释放内存时,线程同样需要获得内存块所在分配区的锁,如果该分配区正在被别的线程使用,则需要等待直到其他线程释放该分配区的互斥锁才可以进行释放操作。
内存基本上都是从低向高进行分配的,在每个arena空闲内存的最高处一定会存在着一块空闲的chunk,这个chunk就是top chunk。前面提到了,分配内存时会先从fastbins,small bins,large bins中查找合适的chunk,如果都不能满足需要的时候,ptmalloc会从top chunk中分配一个chunk返回给用户。
下面的程序验证从top chunk中分配内存
void show MallocInfo()
{
printf("========= malloc info =========\n");
struct mallinfo info;
info = mallinfo();
printf("free chunk count: %d\n", info.ordblks);
printf("fastbin blocks count: %d\n", info.smblks);
printf("total space in fastbin: %d\n", info.fsmblks);
printf("total allocated space: %d\n", info.uordblks);
printf("total free space: %d\n", info.fordblks);
printf("===============================\n");
}
void test_topchunk()
{
char * p1 = (char*)malloc(132);
char * pChunkAddr = p1 - 16;
size_t nChunkSize = *((size_t*)(p1-8)) & (~(0x7));
printf("chunk1 addr: %p\n", pChunkAddr);
printf("chunk1 size: %d\n", nChunkSize);
char * p2 = (char*)malloc(1);
pChunkAddr = p2 - 16;
nChunkSize - *((size_t*)(p2-8)) & (~(0x7));
printf("chunk2 addr: %p\n", pChunkAddr);
printf("chunk2 size: %d\n", nChunkSize);
size_t topChunkSize = *((size_t*)(pChunkAddr+nChunkSize+8)) & (~(0x7));
printf("top chunk addr: %p\n", (pChunkAddr+nChunkSize));
printf("top chunk size: %d\n", topChunkSize);
showMallocInfo();
free(p1);
free(p2);
}
运行结果为:
chunk1 addr: 0x751000
chunk1 size: 144
chunk2 addr: 0x751090
chunk2 size: 32
top chunk addr: 0x7510b0
top chunk size: 134992
========= malloc info =========
free chunk count: 1
fastbin blocks count: 0
total space in fastbin: 0
total allocated space: 176
total free space: 134992
===============================
chunk的合并
从前面的例子,不难理解,几乎所有的chunk(mmap chunk除外)都是从top chunk中分离出来的,并且这些chunk都是相邻的,因此当两个相邻的chunk都处于空闲时,在特定的情况下就可以进行合并。
《ptmalloc——chunk》一文中提到过,表示chunk长度的最低一个P,标示前一个chunk是否正在使用,当这个chunk释放时,就可以根据自身记录的标志位,决定是否需要和前一个chunk进行合并。
例如:
相邻的A,B,C,D四个chunk,当chunk A释放后,会更新chunk B的相关字段(因为B的前一个chunk是A),当chunk B释放时,先更新chunk C的相关字段,然后根据自身长度中的标志位,得知A是空闲,同时根据记录的chunk A的长度找到chunk A的起始位置,与chunk A进行合并。在ptmalloc的实现中,对于chunk B释放的情况,除了根据标志位判断是否要与chunk A进行合并,同时还会找到chunk D,判断chunk C是否空闲,如果空闲则同时与chunk C也进行合并。合并后新的chunk的起始地址就是chunk A的起始地址,而长度就是chunk A+B的长度(如果C也空闲,则再加上C的长度)。
需要注意的是:如果chunk的长度属于fastbins的范围,该chunk释放时不会更新相邻的下一个chunk对应的字段,这样该chunk就无法进行合并。
另外,如果chunk相邻的下一个chunk是top chunk的话,那个该chunk释放时会直接合并到top chunk中(如下图所示)。
chunk合并的时机:
- chunk释放时,其长度不属于fastbins的范围,则合并前后相邻的chunk。
- 首次分配的长度在large bin的范围,并且fast bins中有空闲chunk,则将fastbins中的chunk与相邻空闲的chunk进行合并,然后将合并后的chunk放到unsorted bin中,如果fastbin中的chunk相邻的chunk并非空闲无法合并,仍旧将该chunk放到unsorted bin中,即能合并的话就进行合并,但最终都会放到unsorted bin中。
- fastbins,small bin中都没有合适的chunk,top chunk的长度也不能满足需要,则对fast bin中的chunk进行合并
下面的程序验证从chunk的合并
void test_chunkConsolidate()
{
char * p1[4];
int i = 0;
// 连续申请多个长度在fastbins范围内的chunk
for( i = 0; i < 4; i++ ) {
p1[i] = (char*)malloc(10);
}
for( i = 0; i < 3; i++ ) {
if( p1[i] != NULL ) {
free( p1[i] );
}
}
// 此时 fastbins中有3个chunk
showMallocInfo();
// 申请长度在large bin范围内的chunk, 触发fastbin中的chunk进行合并
char * tmp = (char*)malloc(1100);
// fastbin中的chunk是相邻的, 因此可以合并成1个chunk
// 加上top chunk 总共有2个空闲的chunk
showMallocInfo();
// 连续申请多个长度在small bins范围内的chunk
char * p2[4];
for( i = 0; i < 4; i++ ) {
p2[i] = (char*)malloc(600);
}
// 释放时会进行chunk合并
for( i = 0; i < 3; i++ ) {
if( p2[i] != NULL ) {
free( p2[i] );
}
}
showMallocInfo();
}
程序执行结果为:
========= malloc info =========
free chunk count: 1
fastbin blocks count: 3
total space in fastbin: 96
total allocated space: 32
total free space: 135132
===============================
========= malloc info =========
free chunk count: 2
fastbin blocks count: 0
total space in fastbin: 0
total allocated space: 1152
total free space: 134016
===============================
========= malloc info =========
free chunk count: 3
fastbin blocks count: 0
total space in fastbin: 0
total allocated space: 1760
total free space: 133408
===============================
PS:除了使用mallinfo获取内存分配的情况外,还可以使用malloc_info获取更详细的信息。
chunk的分离
前面讲了相邻的chunk可以合并成一个大的chunk,反过来,一个大的chunk也可以分裂成两个小的chunk。chunk的分裂与从top chunk中分配新的chunk是一样的。需要注意的一点是:分裂后的两个chunk其长度必须均大于chunk的最小长度(对于64位系统是32字节),即保证分裂后的两个chunk仍旧是可以被分配使用的,否则则不进行分裂,而是将整个chunk返回给用户。
下面的程序验证chunk的分离
void test_chunkSplit()
{
char * p1 = (char*)malloc(1100);
size_t nChunkSize = *((size_t*)(p1-8)) & (~(0x7));
printf("chunk1 addr: %p chunk size: %d\n", (p1-16), nChunkSize);
// 避免p1释放时与top chunk合并, 同时查看正常申请(1100-16)字节时chunk的长度
char * tmp = (char*)malloc(1100-16);
nChunkSize = *((size_t*)(tmp-8)) & (~(0x7));
printf("tmp addr: %p chunk size: %d\n", (tmp-16), nChunkSize);
free(p1);
// p1 chunk分离后的剩余长度小于32字节, 即不进行分裂全部返回
char * p2 = (char*)malloc(1100-16);
nChunkSize = *((size_t*)(p2-8)) & (~(0x7));
printf("chunk2 addr: %p chunk size: %d\n", (p2-16), nChunkSize);
free(p2);
// p2 chunk分离后的剩余长度大于等于32字节, 可进行分裂
char * p3 = (char*)malloc(1100-32);
nChunkSize = *((size_t*)(p3-8)) & (~(0x7));
printf("chunk3 addr: %p chunk size: %d\n", (p3-16), nChunkSize);
// p2分裂后的chunk放到unsorted bin中 加上top chunk 共两个free chunk
showMallocInfo();
free(p3);
free(tmp);
}
程序运行结果:
chunk1 addr: 0x233d000 chunk size: 1120
tmp addr: 0x233d460 chunk size: 1104
chunk2 addr: 0x233d000 chunk size: 1120
chunk3 addr: 0x233d000 chunk size: 1088
========= malloc info =========
free chunk count: 2
fastbin blocks count: 0
total space in fastbin: 0
total allocated space: 2192
total free space: 132976
===============================