ptmalloc堆概述
1 概述
堆的概念
在程序运行过程中,堆可以提供动态分配的内存,允许程序申请大小未知的内存。堆其实就是程序虚拟地址空间的一块连续的线性区域,它由低地址向高地址方向增长。
堆管理器
我们一般称管理堆的那部分程序为堆管理器。
· dlmalloc – General purpose allocator
· ptmalloc2 – glibc
· jemalloc – FreeBSD and Firefox
· tcmalloc – Google
· libumem – Solaris
堆管理器处于用户程序与内核中间,主要做以下工作
1. 响应用户的申请内存请求,向操作系统申请内存,然后将其返回给用户程序。同时,为了保持内存管理的高效性,内核一般都会预先分配很大的一块连续的内存,然后让堆管理器通过某种算法管理这块内存。只有当出现了堆空间不足的情况,堆管理器才会再次与操作系统进行交互。
2. 管理用户所释放的内存。一般来说,用户释放的内存并不是直接返还给操作系统的,而是由堆管理器进行管理。这些释放的内存可以来响应用户新申请的内存的请求。
Dlmalloc
Linux 中早期的堆管理器。在并行处理多个线程时,会共享进程的堆内存空间。
Ptmalloc
Wolfram Gloger 在 Doug Lea 的基础上进行改进使其可以支持多线程,这个堆分配器就是 ptmalloc 。在 glibc-2.3.x. 之后,glibc 中集成了ptmalloc2。
可以下载glibc源码查看ptmalloc
查看glibc版本
millionsky@ubuntu-16:~/tmp$ ldd --version ldd (Ubuntu GLIBC 2.23-0ubuntu9) 2.23 |
堆的基本操作
#include <stdlib.h>
/* ** 为0时返回最小的chunk,32位系统为16,64位系统为24/32。 */ void *malloc(size_t size);
/* **除非被mallopt禁用,释放很大的内存空间时,程序会将这些内存空间还给系统。 */ void free(void *ptr); |
系统调用
brk/mmap/munmap
glibc函数
brk/sbrk/mmap/munmap
多线程支持
相对于dlmalloc,在glibc的ptmalloc实现中,比较好的一点就是支持了多线程的快速访问。在新的实现中,所有的线程共享多个堆。
2 ptmalloc堆数据结构
2.1 Chunk
2.1.1 Chunk概述
1. malloc 申请的内存为 chunk 。这块内存在 ptmalloc 内部用 malloc_chunk 结构管理chunk。当程序申请的 chunk 被 free 后,会被加入到相应的空闲管理列表中。
l Chunk的对齐:32位为8字节;64位为16字节
l Chunk的最小尺寸:32位为16字节;64位为24/32字节
/* The smallest possible chunk */
#define MIN_CHUNK_SIZE (offsetof(struct malloc_chunk, fd_nextsize))
2. Chunk有4种类型:
· Allocated chunk
· Free chunk
· Top chunk
· Last Remainder chunk
2.1.2 malloc_chunk数据结构
1. Ptmalloc使用malloc_chunk结构管理chunk。
这个结构跨越了两个chunks
第1个字段属于之前的chunk,第2/3/4字段属于当前的chunk;
只有当(size&1)==0时,prev_size字段才会被定义;
#define INTERNAL_SIZE_T size_t #define SIZE_SZ (sizeof (INTERNAL_SIZE_T))
struct malloc_chunk {
INTERNAL_SIZE_T mchunk_prev_size; /* Size of previous chunk (if free). */ INTERNAL_SIZE_T mchunk_size; /* Size in bytes, including overhead. */
struct malloc_chunk *fd; /* double links -- used only if free. */ struct malloc_chunk *bk;
/* Only used for large blocks: pointer to next larger size. */ struct malloc_chunk *fd_nextsize; /* double links -- used only if free. */ struct malloc_chunk *bk_nextsize; }; |
l fd 指向下一个(非物理相邻)空闲的 chunk,free chunk中才有
l bk 指向上一个(非物理相邻)空闲的 chunk,free chunk中才有
通过 fd 和 bk 可以将空闲的 chunk 块加入到空闲的 chunk 块链表进行统一管理
l fd_nextsize 指向前一个与当前 chunk 大小不同的第一个空闲块,不包含 bin 的头指针。只用于large chunk。
l bk_nextsize 指向后一个与当前 chunk 大小不同的第一个空闲块,不包含 bin 的头指针。只用于large chunk。
l 一般空闲的 large chunk 在 fd 的遍历顺序中,按照由大到小的顺序排列。这样做可以避免在寻找合适chunk 时挨个遍历。
2. 一个已经分配的 chunk 的样子如下。我们称前两个字段称为 chunk header,后面的部分称为user data。每次 malloc 申请得到的内存指针,其实指向user data的起始处。
3. Chunk的标志位
² NON_MAIN_ARENA(A),当前 chunk 是否不属于主线程,1表示不属于,0表示属于。
² IS_MAPPED(M),当前 chunk 是否是由 mmap 分配的。
如果M位被设置,则其它位被忽略(因为mapped chunks既不在arena中,也不和free chunk相邻);(PS:这里没有怎么理解)
² PREV_INUSE(P),前一个 chunk 块是否被分配。
如果为0,则prev_size包含前一个chunk的大小;可计算出前一个chunk的位置;
如果为1,则不能确定前一个chunk的大小;
分配的第一个chunk通常会设置此位,防止访问不存在或不拥有的内存;
4. chunk中的空间复用
当一个 chunk 处于使用状态时,它的下一个 chunk 的 prev_size 域无效,所以下一个 chunk 的该部分也可以被当前chunk使用。这就是chunk中的空间复用。
chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Size of previous chunk, if unallocated (P clear) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Size of chunk, in bytes |A|M|P|
mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| User data starts here... .
. .
. (malloc_usable_size() bytes) .
next . |
chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| (size of chunk, but used for application data) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Size of next chunk, in bytes |A|0|1|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
2.1.3 Allocated chunk
l prev_size
前一个chunk(物理相邻)如果为free chunk(size&1==0),则指示其大小;
l Size
该 chunk 的大小,大小必须是 2 * SIZE_SZ 的整数倍。
由于chunk必须是8的整数倍,低