概述
Linux中malloc的早期版本是由Doug Lea实现的,Wolfram Gloger在Doug Lea的基础上改进使得Glibc的malloc可以支持多线程–ptmalloc。
ptmalloc实现了malloc(),free()以及一组其他的函数,以提供动态内存管理的支持。
main arena和non main arena
在Doug Lea实现的内存分配器中值有一个主分配区(main arena),每次分配内存都必须对主分配区加锁,分配完后释放锁,在多线程的环境下因为对锁的竞争导致分配效率比较低。
为此,Wolfram Gloger进行改进增加了非主分配区(non main arena)使得Glibc的malloc可以支持多线程。
每个进程只有一个主分配区,但是可能存在多个非主分配区。
主分配区可以访问进程的heap区域和mmap映射区域
非主分配区只能访问进程的mmap映射区域,不可以访问heap区域
chunk
通常,用户请求分配的空间在ptmalloc中使用一个chunk来表示,当用户调用free()
函数的时候也不是立即归还给操作系统,同样也表示为一个chunk,由内存分配器ptmalloc进行管理。
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;
};
使用中的chunk
ptmalloc在给用户分配的空间的前后加上一些控制信息,这样可以方便分配和释放工作。
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) .
. |
nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| (size of chunk, but used for application data) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Size of next chunk, in bytes |A|0|1|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
chunk指针指向一个chunk的开始,一个chunk包含了用户请求的内存区域和相关控制信息
mem指针才是真正返回给用户的指针,也就是通常我们使用malloc()
返回的指针
chunk的第二个域size的第三位分别为AMP
- P 表示前一个chunk是否在使用,使用为1,没有使用为0
- M 表示当前chunk是否是mmap映射区域分配的,是为1,不是为0
- A 表示当前chunk是否属于非主分配区,是为1,不是为0
空闲的chunk
chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Size of previous chunk, if unallocated (P clear) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
`head:' | Size of chunk, in bytes |A|0|P|
mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Forward pointer to next chunk in list |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Back pointer to previous chunk in list |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Unused space (may be 0 bytes long) .
. .
. |
nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
`foot:' | Size of chunk, in bytes |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Size of next chunk, in bytes |A|0|0|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
当我们使用free()
函数释放内存时候,释放的内存就表示为一个空闲的chunk
空闲的chunk中,不存在M位,只存在AP位,还有一个与使用中的chunk不一样的地方在于原来的用户数据区的地方存放了四个指针
- fd指针指向后一个空闲的chunk
- bk指针指向前一个空闲的chunk
- fd_nextsize
- bk_nextsize
chunk的空间复用
其实同一个chunk的两种状态,就是空间复用的一种表现,四个指针在用户区域的指针在用户使用内存的时候是不存在的,还有下一个chunk的prev_size也是不存在的,这些都划为用户区域;在chunk空闲的时候,这四个指针和prev_size就有效了,方便管理空闲的chunk。
空闲chunk管理
Fast Bins
通常情况下,程序在运行时会经常申请和释放一些较小的内存空间,ptmalloc为了加快分配的效率,引入了fast bins,fast bins中的chunk并不改变它的使用标志位P,当用户需要小内存的时候,优先从fast bins中查找合适的chunk。
Bins
ptmalloc将相似大小的chunk用双向链表链接起来,这样的一个链表称为一个bin。
ptmalloc一共维护了128个bin,并使用一个数组来存储这些bin。
数组中第一个bin为Unsorted Bin,从数组的第二个开始共64个bin为small bin,剩下的称为large bin
Unsorted Bin
unsorted bin使用bins数组的第一个,当用户释放在chunk大小不在fast bins范围,这些chunk就会首先被放到unsorted bin中