从上到下看linux内存管理--glibc malloc
Posted hahajeff
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从上到下看linux内存管理--glibc malloc相关的知识,希望对你有一定的参考价值。
Rerfences
实验平台:
- x86_64 GNU/Linux
- Linux version 3.10.0
32 bit linux 虚拟内存布局
fig 1. 32bit linux 虚拟内存布局
fig 1展示了linux 32bit系统上虚拟内存布局,代码段(text segment)从0x0804800开始,接下来的分别是
-
数据段(Data segment):存放由程序员初始为非0的静态变量,全局变量;
-
BSS segment:存放未经初始化或初始化为0的静态变量,全局变量;
-
Heap区:存放经动态分配内存的变量;
-
内存映射区:文件映射和匿名映射区域(不与任何文件相关的映射区域,如果malloc的请求超过MMAP_THRESHOLD(默认128KB),c库则会创建一个匿名映射而不是直接在heap区域分配);
-
stack区:存放函数参数,局部变量以及函数相关的其他信息;
-
内核空间:存储内核相关数据,用户没有读写权限。
glibc malloc系统调用
常用的内存分配:
-
dlmalloc – General purpose allocator
-
ptmalloc2 – glibc
-
jemmalloc – FreeBSD and FireFox
-
tcmalloc – Google
-
libumem – Solaris
-
…
c标准库提供的malloc使用的是ptmalloc2,经过以下的测试程序观察一下malloc内存分配的具体情况:
1 /* Per thread arena example. */ 2 #include <stdio.h> 3 #include <stdlib.h> 4 #include <pthread.h> 5 #include <unistd.h> 6 #include <sys/types.h> 7 8 void* threadFunc(void* arg) { 9 printf("Before malloc in thread 1\\n"); 10 getchar(); 11 char* addr = (char*) malloc(1000); 12 printf("After malloc and before free in thread 1\\n"); 13 getchar(); 14 free(addr); 15 printf("After free in thread 1\\n"); 16 getchar(); 17 } 18 19 int main() { 20 pthread_t t1; 21 void* s; 22 int ret; 23 char* addr; 24 25 printf("Welcome to per thread arena example::%d\\n",getpid()); 26 printf("Before malloc in main thread\\n"); 27 getchar(); 28 addr = (char*) malloc(1000); 29 printf("After malloc and before free in main thread\\n"); 30 getchar(); 31 free(addr); 32 printf("After free in main thread\\n"); 33 getchar(); 34 ret = pthread_create(&t1, NULL, threadFunc, NULL); 35 if(ret) 36 { 37 printf("Thread creation error\\n"); 38 return -1; 39 } 40 ret = pthread_join(t1, &s); 41 if(ret) 42 { 43 printf("Thread join error\\n"); 44 return -1; 45 } 46 return 0; 47 }
测试程序摘自[Understanding glibc malloc]
主线程调用malloc分配1000byte,从线程分配1000byte,在主线程与分线程之间通过getchar与控制台交互;
结果分析(通过cat /proc/pid/maps可以查看进程内存布局)
-
在主线程调用malloc之前:程序输出如下图所示,此时,程序没有出现heap segment和从线程stack segment(因为thread1没有被创建):
-
主线程调用malloc操作之后:heap segment被创建,其位置位于data segment之上(0x00600000-0x00601000),而且位置信息表明了该malloc分配的内存是由brk系统调用产生的。值得注意的是程序只是请求了1000byte,但是却开辟了132KB的内存空间,这一块连续的内存空间叫做arena,在main thread中的arena又叫做main arena。main thread中后续的malloc操作将会在main arena中进行分配,直至main arena耗尽或者malloc请求超过MMAP_THRESHOLD(128KB)时,才会另辟它径:
-
当main arena耗尽时,通过增加program break(如fig 1中的program break所示) 位置扩大内存;
-
当malloc请求超过MMAP_THRESHOLD时,会调用mmap从memory mapping segment请求内存(这里就会产生上文提到的匿名映射区域)。
-
主线程调用free后:原先malloc分配的内存区域依旧没有被操作系统回收。该区域被添加到glibc自己的维护的空间内存链表中,方便以后时候,相当于glic维护了一个内存池,用户程序只是从内存池中取出可用的内存,而不用从操作系统获取,提高了内存分配以及释放效率:
-
在thread1调用malloc之前:thread1还没有heap segment,但是thread1的stack已经被创建(我的系统的stack segment默认大小为8MB):
-
在thread1调用malloc之后:thread1的heap segment被创建,其位置位于memory mapping segment区域(0x7f9c1c000000-0x7f9c1c021000),共132KB,该位置叫做thread arena,同时表明了malloc调用的mmap系统调用。同时,malloc请求的是1000byte,而得到的内存大小却是64MB()(32bit系统,thread heap第一次分配默认是1MB,64bit系统默认是64MB)
注意:当用户请求大于128KB时(比如说malloc(132*1024))并且此时是没有足够的内存空间来满足用户请求的,此时,无论是从main arena中或是从thread arena中的内存分配采用的都是mmap系统调用(而不是brk系统调用)
-
在thread1调用free操作之后:同主线程调用free操作一样,free掉的内存不会返还之操作系统,而是由glibc进行管理,从而加速内存分配操作:
上述内容可以概括为:
-
malloc调用底层有两种实现方式,分别是:
-
以brk实现,该系统调用直接在heap segment通过增加program break的位置开辟空间;
-
以mmap实现,该系统调用采用mmap在memory mmaping segment中开辟空间。
-
main thread中由malloc申请的内存在heap segment,slave thread中由malloc申请的内存在memory mmaping segment;
-
只要malloc申请的内存大于MAP_THRESHOLD(128KB),都是采用mmap 的方式申请内存空间,即内存在memor mmaping segment。
Arena
-
Arena numbers
在上面的例子中,main thread中包含main arena,thread1中包含thread arena。这里会引申出一个问题:是不是有多少线程就会有多少arena,即arena和thread之间的关系时一一对应的?答案当然是否定的:
对于32bit系统:
arena个数 = 2*core个数 + 1;(在[Understanding glibc malloc]写成了2*number of cores)
对于64bit系统:
arena个数 = 8*core个数 + 1;
-
Multiple Arena
举例来说,对于一个运行在32bit单核系统上的4threads的多线程应用程序(3 slave thread + main thread)。由上述公式可以得到:arena个数为3:
- 当主线程第一次调用malloc的时候,创建一个main arena,此时不会发生竞争;
- 当thread1和thread2第一次调用malloc时,两个thread arena分别被创建,且不会发生竞争;
- 当thread3第一次调用malloc时,arena已经超出限制了,所以thread3将会共用已经创建好的arena(可能是main arena,thread1 arena或thread2 arena):
- 重用:
- 遍历所有可用的arena,并尝试对其上锁;
- 如果成功的上锁(比如main arena成功被锁住),则将main arena返回给用户;
- 如果此时没有可用的arena,则阻塞这次的malloc调用。
- 当thread3再次调用malloc时,malloc将会尝试使用上次使用过的arena(main arena),如果main arena是空闲的,则直接使用,否则,阻塞;
-
Multiple Heaps
glibc的堆内存管理主要涉及三个数据结构:
- heap_info:即heap header,每一个thread arena可以有多个heap。每一个heap有自己的heap header。为什么会有多个heap?在开始的时候,thread arena只会有一个heap,但是如果该heap用尽了,glibc会通过mmap系统调用会申请一段新的heap,并将其加入到thread arena中,便于管理(mmap不同sbrk,mmap映射的空间不一定与上一段mmap映射的空间连续):
1 typedef struct _heap_info 2 { 3 mstate ar_ptr; /* Arena for this heap. */ 4 struct _heap_info *prev; /* Previous heap. */ 5 size_t size; /* Current size in bytes. */ 6 size_t mprotect_size; /* Size in bytes that has been mprotected PROT_READ|PROT_WRITE. */ 7 /* Make sure the following data is properly aligned, particularly that sizeof (heap_info) + 2 * SIZE_SZ is a multiple of MALLOC_ALIGNMENT. */ 8 char pad[-6 * SIZE_SZ & MALLOC_ALIGN_MASK]; 9 } heap_info;
- malloc_state:arnea header,每一个thread arena可以有多个heap,但是对于所有的heaps只会有一个arena header。arena header包括:bins,top chunk,last remainder chunk...:
1 struct malloc_state 2 { 3 /* Serialize access. */ 4 mutex_t mutex; 5 /* Flags (formerly in max_fast). */ 6 int flags; 7 /* Fastbins */ 8 mfastbinptr fastbinsY[NFASTBINS]; 9 /* Base of the topmost chunk -- not otherwise kept in a bin */ 10 mchunkptr top; 11 /* The remainder from the most recent split of a small request */ 12 mchunkptr last_remainder; 13 /* Normal bins packed as described above */ 14 mchunkptr bins[NBINS * 2 - 2]; 15 /* Bitmap of bins */ 16 unsigned int binmap[BINMAPSIZE]; 17 /* Linked list */ 18 struct malloc_state *next; 19 /* Linked list for free arenas. */ 20 struct malloc_state *next_free; 21 /* Memory allocated from the system in this arena. */ 22 INTERNAL_SIZE_T system_mem; 23 INTERNAL_SIZE_T max_system_mem; 24 };
- malloc_chunk:chunk header,一个heap被分成了许多chunks(基于用户的请求)。每一个chunk有自己的chunk header:
1 struct malloc_chunk { 2 /* #define INTERNAL_SIZE_T size_t */ 3 INTERNAL_SIZE_T prev_size; /* Size of previous chunk (if free). */ 4 INTERNAL_SIZE_T size; /* Size in bytes, including overhead. */ 5 struct malloc_chunk* fd; /* double links -- used only if free. 这两个指针只在free chunk中存在*/ 6 struct malloc_chunk* bk; 7 /* Only used for large blocks: pointer to next larger size. */ 8 struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */ 9 struct malloc_chunk* bk_nextsize; 10 };
- notes
-
main arena只有一个heap,因此没有heap_info结构。当main arena用尽了所有空间时,会通过sbrk系统调用增加heap空间(连续的空间),直到碰到mmap段;
-
与thread arena不同的是,main arena的arena header不是sbrk heap segment的一部分。它是一个全局变量,因此,它存在于libc.so的data segment。
-
-
heap segment与arena的关系
-
__heap_info中的ar_ptr指向当前thread的thread arena(malloc_state),prev指向前一个thread的heap_info结构体(main thread没有heap_info结构体);
-
- thread arena含有多个heap segments的情况,观察每个heap segment的地址,可以看到是不连续的;
-
从图中可以看到,thread arena只含有一个malloc_state(即arena header),却有两个heap_info(即heap_header);
-
由于两个heap segments是通过mmap分配的内存,两者在内存布局上并不相邻,而是属于不同的内存区间,所以为了便于管理,malloc将第二个heap_info结构体的prev成员指向了第一个heap_info结构的其实位置(即ar_ptr),而第一个heap_info结构体的ar_ptr成员指向了malloc_state,这样就构成了单链表,方便后续管理。
-
Chunk
-
在glibic malloc中将整个堆内存空间分成了连续的,大小不一的chunk,即对于堆内存管理而言,chunk就是最小操作单位:
-
-
- Allocated chunk
-
Free chunk
-
Top chunk
-
Last Remainder chunk
-
Allocated chunk:
-
prev_sive:如果前一个chunk是空间的,那么该field会包含前一段chunk的size。否则,该field会包含前一个chunk的用户数据;
-
PREV_INUSE(P):当 前一个chunk已经被分配时,置位;
-
IS_MMAPPED(M):当chunk是被mmap的时候,置位;
-
NON_MAIN_ARENA(N):当chunk是属于thread arena时,置位;
-
-
NOTE:
-
malloc_chunk结构体的其他field(如下图),这些field可以用来存储用户数据;
-
-
-
-
- 用户请求的大小不一定是内部表示的大小,出于效率考虑以及malloc_chunk相关的数据结构,所以请求大小会被转换成内部大小;
-
-
Free chunk:
-
prev_size:没有相邻的两个空间chunk(因为会被合并)。
-
size:包含free chunk的大小;
-
fd:指向双向链表中下一个chunk;
-
bk:指向双向链表中上一个chunk;
-
-
BINS:存储freelist的数据结构。每一个bin中的chunk大小不同
-
Fast bin
-
Unsorted bin
-
Small bin
-
Large bin
-
-
fastbinsY: the array hold fast bins
-
bins: the array hold unsorted, small and large bins. 总共有126个bins并且被分成以下:
-
Bin1:unsorted bin
-
Bin2 - Bin63:small bin
-
Bin64 - Bin126:Large bin
-
1 struct malloc_state 2 { 3 …… 4 /* Fastbins */ 5 mfastbinptr fastbinsY[NFASTBINS]; 6 …… 7 /* Normal bins packed as described above */ 8 mchunkptr bins[NBINS * 2 - 2]; // #define NBINS 128 9 …… 10 }; 11 这里mfastbinptr的定义:typedef struct malloc_chunk *mfastbinptr; 12 mchunkptr的定义:typedef struct malloc_chunk* mchunkptr;
1 struct malloc_chunk { 2 /* #define INTERNAL_SIZE_T size_t */ 3 INTERNAL_SIZE_T prev_size; /* Size of previous chunk (if free). */ 4 INTERNAL_SIZE_T size; /* Size in bytes, including overhead. */ 5 struct malloc_chunk* fd; /* 这两个指针只在free chunk中存在*/ 6 struct malloc_chunk* bk; 7 /* Only used for large blocks: pointer to next larger size. */ 8 struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */ 9 struct malloc_chunk* bk_nextsize; 10 };
其中fd和bk指针就是指向当前chunk
-
Fast bin:在内存分配和释放过程中,fast bin是所有bin中操作速度最快的。 减少对大内存块的切割次数
-
chunk size表示malloc_chunk的实际整体大小;
-
chunk unused size表示malloc chunk中刨除诸如prev_size,size,fd和bk这类辅助成员之后的实际可用大小。因此,对于free chunk而言,其实际可用大小比实际整体大小少16字节;
-
fast bin 的个数:10个;
-
每个fast bin都是一个单链表(只使用fd指针)?为什么是单链表呢?因为在fast bin中无论是添加还是移除fast chunk,都是对”链表尾“进行操作,而不会对其中某个中间的fast chunk进行操作。更具体点就是LIFO(后入先出)算法:添加操作(free内存)就是将心的fast chunk加入链表尾,删除操作(malloc内存)就是将链表尾部的fast chunk删除。需要注意的是,为了实现LIFO算法,fastbinsY数组中每个fastbin元素均指向了该链表的rear end(尾节点),而尾节点通过其fd指针指向前一个节点,依次类推;
-
chunk size:10个fast bin中所包含的fast chunk size是按照步进8字节排列的,即第一个fast bin中所有fast chunk size均为16字节,第二个fast bin中为24字节,以此类推。在进行malloc初始化的时候,最大的fast chunk size被设置为80字节(chunk unused size为64字节),因此默认情况下大小为16到80字节的chunk被分类到fast chunk;
-
不会对free chunk进行合并操作。鉴于设计fast bin的初衷就是进行快速的小内存分配和释放,因此系统将属于fast bin的chunk的P(未使用标志位)总是设置为1,这样即使当fast bin中有某个chunk同一个free chunk相邻的时候,系统也不会自动进行合并操作,而是保留两位。虽然这样做可能会造成额外的碎片化问题,但瑕不掩瑜;
-
malloc(fast chunk)操作:即用户通过malloc请求的大小属于fast chunk的大小范围(注意:用户请求size+16字节就是实际内存chunk size)。在初始化的时候fast bin支持的最大内存以及所有fast bin链表都是空的,所以当最开始使用malloc申请内存的时候,即使申请的内存大小属于fast chunk的内存大小(即16字节到80字节),它不会交由fast bin来处理,而是向下传递交由small bin来处理,如果small bin也为空的话就交给unsorted bin处理:
-
1 /* Maximum size of memory handled in fastbins. */ 2 static INTERNAL_SIZE_T global_max_fast; 3 /* offset 2 to use otherwise unindexable first 2 bins */ 4 /*这里SIZE_SZ就是sizeof(size_t),在32位系统为4,64位为8,fastbin_index就是根据要malloc的size来快速计算该size应该属于哪一个fast bin,即该fast bin的索引。因为fast bin中chunk是从16字节开始的,所有这里以8字节为单位(32位系统为例)有减2*8 = 16的操作!*/ 5 #define fastbin_index(sz) 6 ((((unsigned int) (sz)) >> (SIZE_SZ == 8 ? 4 : 3)) - 2) 7 /* The maximum fastbin request size we support */ 8 #define MAX_FAST_SIZE (80 * SIZE_SZ / 4) 9 #define NFASTBINS (fastbin_index (request2size (MAX_FAST_SIZE)) + 1)
-
small bins:ptmalloc使用smalls管理空闲小chunk,每个small bin中的chunk大小与bin的index有如下关系:
-
chunk size = 2 * SIZE_SZ * index,SIZE_SZ为4B的平台上,small bins中的chunk大小是以8B为公差的等差数列,在SIZE_SZ为8B的平台上,small bins的chunk大小是以16B为公差的等差数列;
-
-
unsorted bins:回收的的chunk不会直接返回到对应的bin,而是先返回到unsorted bins中,从而提高内存利用的局部性;可以将unsorted bin看作是small bins和large bins的cache,只有一个unsorted bin,以双链表形式管理空闲chunk,空闲chunk不排序,所有的chunk在回收时都要先放到unsorted bin中,分配时,如果在unsorted bin中没有合适的chunk的,就会把cunsorted bin中所有的chunk分别加入到所属的bin中,然后再在bin中分配合适的chunk;
-
large bins:chunk size = 512 + 64 * index;
以上是关于从上到下看linux内存管理--glibc malloc的主要内容,如果未能解决你的问题,请参考以下文章
Linux more(功能类似 cat ,cat命令是整个文件的内容从上到下显示在屏幕上)