目录
1. 基础类型的bins:fast、unsorted、small、large
3. 特殊类型的bins:top chunk、mmaped chunk、last remainder chunk
一、Linux系统的堆栈结构
Linux的内存空间地址从低到高一般分为五个部分:内核空间、栈区域、堆区域、BBS段、数据段和代码段
- 内核空间:我们在编写应用程序(非内核空间程序)的时候,这一块地址我们是不能够使用的
- 栈区域:程序中局部变量、函数参数、返回地址的地方地址。地址从低到高分配
- 堆区域:由malloc,calloc等创建的空间,是运行的时候由程序申请的。地址由高到低
- BBS段:未初始化或初值为0的全局变量和静态局部变量
- 数据段:已初始化且初值非0的全局变量和静态局部变量
- 代码段:可执行代码、字符串字面值、只读变量
所以,我们使用ptmalloc的内存分配的时候,一般都是在堆上进行操作的。说白了,ptmalloc就是通过管理堆上的内存分配,来管理整个用户层malloc函数发起的内存请求
- 通过brk方式分配的内存,一般都是从高地址到低地址;(堆的操作,操作系统提供了brk的函数,glibc提供了sbrk的函数)
- 通过mmap方式分配的内存,一般都是从低地址到高地址的。
#include <unistd.h>
int brk( const void *addr )
void* sbrk ( intptr_t incr );
-
Brk:参数设置为新的brk上界地址,成功返回1,失败返回0;
-
Sbrk:参数为申请内存的大小,返回heap新的上界brk的地址
#include <sys/mman.h>
void *mmap(void *addr, size\_t length, int prot, int flags, int fd, off\_t offset);
int munmap(void *addr, size_t length);
- mmap:第一种用法是映射此盘文件到内存中;第二种用法是匿名映射,不映射磁盘文件,而向映射区申请一块内存。
- munmap:函数用于释放内存。
二、主分配区数据结构malloc_state
ptmalloc通过malloc_state的状态机来管理内存的分配。malloc_state主要用来管理分配的内存块,比如是否有空闲的chunk,有什么大小的空闲chunk 等等。(chunk是内存管理的最小单元,后面一章会重点讲解)。当用户层调用malloc/free等函数的时候,都会通过ptmalloc内核模块进行内存的分配,每一块从操作系统上分配的内存,都会使用malloc_state结构体来管理。
我们先看一下malloc_state的关键数据结构
- mutex:线程锁,当多线程进行内存分配竞争的时候,需要首先拿到该锁才能进行分配区上的操作。
- flags:记录了分配区的一些标志,比如 bit0 记录了分配区是否有 fast bin chunk ,bit1 标识分配区是否能返回连续的虚拟地址空间
- have_fastchunks:用于标记是否有fast bins。
- fastbinsY:fast bins是bins的高速缓冲区,大约有10个定长队列。当用户释放一块不大于max_fast(默认值64)的chunk(一般小内存)的时候,会默认会被放到fast bins上。
- top:指向分配区的 top chunk。top chunk相当于分配区的顶部空闲内存,当bins上都不能满足内存分配要求的时候,就会来top chunk上分配。
- last_remainder:最新的 chunk 分割之后剩下的那部分
- bins:用于存储 unstored bin,small bins 和 large bins 的 chunk 链表。
- binmap:ptmalloc 用一个 bit 来标识某一个 bin 中是否包含空闲 chunk 。
- next:分配区全局链表,主分配区放头部,新加入的分配区放main_arean.next 位置。
- next_free:空闲的分配区
/**
* 全局malloc状态管理
*/
struct malloc_state
{
/* Serialize access. 同步访问互斥锁 */
__libc_lock_define (, mutex);
/* Flags (formerly in max_fast).
* 用于标记当前主分配区的状态
* */
int flags;
/* Set if the fastbin chunks contain recently inserted free blocks. */
/* Note this is a bool but not all targets support atomics on booleans. */
/* 用于标记是否有fastchunk */
int have_fastchunks;
/* Fastbins fast bins。
* fast bins是bins的高速缓冲区,大约有10个定长队列。
* 当用户释放一块不大于max_fast(默认值64)的chunk(一般小内存)的时候,会默认会被放到fast bins上。
* */
mfastbinptr fastbinsY[NFASTBINS];
/* Base of the topmost chunk -- not otherwise kept in a bin */
/* Top chunk :并不是所有的chunk都会被放到bins上。
* top chunk相当于分配区的顶部空闲内存,当bins上都不能满足内存分配要求的时候,就会来top chunk上分配。 */
mchunkptr top;
/* The remainder from the most recent split of a small request */
mchunkptr last_remainder;
/* Normal bins packed as described above
* 常规 bins chunk的链表数组
* 1. unsorted bin:是bins的一个缓冲区。当用户释放的内存大于max_fast或者fast bins合并后的chunk都会进入unsorted bin上
* 2. small bins和large bins。small bins和large bins是真正用来放置chunk双向链表的。每个bin之间相差8个字节,并且通过上面的这个列表,
* 可以快速定位到合适大小的空闲chunk。
* 3. 下标1是unsorted bin,2到63是small bin,64到126是large bin,共126个bin
* */
mchunkptr bins[NBINS * 2 - 2];
/* Bitmap of bins
* 表示bin数组当中某一个下标的bin是否为空,用来在分配的时候加速
* */
unsigned int binmap[BINMAPSIZE];
/* 分配区全局链表:分配区链表,主分配区放头部,新加入的分配区放main_arean.next 位置 Linked list */
struct malloc_state *next;
/* 分配区空闲链表 Linked list for free arenas. Access to this field is serialized
by free_list_lock in arena.c. */
struct malloc_state *next_free;
/* Number of threads attached to this arena. 0 if the arena is on
the free list. Access to this field is serialized by
free_list_lock in arena.c. */
INTERNAL_SIZE_T attached_threads;
/* Memory allocated from the system in this arena. */
INTERNAL_SIZE_T system_mem;
INTERNAL_SIZE_T max_system_mem;
};
三、状态机组织chunk的方式
ptmalloc的空闲chunk都是通过在malloc_state上的bins数组来管理的。
1. 基础类型的bins:fast、unsorted、small、large
一共分为四种类型的bins:fast bins、 unsorted bin、small bins和large bins。
- fast bins:fast bins是bins的高速缓冲区,大约有10个定长队列。当用户释放一块不大于max_fast(默认值64)的chunk(一般小内存)的时候,会默认会被放到fast bins上。当用户下次需要申请内存的时候首先会到fast bins上寻找是否有合适的chunk,然后才会到bins上空闲链表里面查找的chunk。ptmalloc会遍历fast bin,看是否有合适的chunk需要合并到bins上。主要放置在fastbinsY数组上。
- unsorted bin:是bins的一个缓冲区,bins数组下标为1的即是unstored bin。当用户释放的内存大于max_fast或者fast bins合并后的chunk都会进入unsorted bin上。当用户malloc的时候,先会到unsorted bin上查找是否有合适的bin,如果没有合适的bin,ptmalloc会将unsorted bin上的chunk放入bins上,然后到bins上查找合适的空闲chunk。
- small bins:小于512字节(64位机器1024字节)的chunk被称为small chunk,而保存small chunks的bin被称为small bin。数组从2开始编号到63,前62个bin为small bins,small bin每个bin之间相差8个字节(64位16字节),同一个small bin中的chunk具有相同大小。起始bin大小为16字节(64位系统32)。
- large bins:大于等于512字节(64位机器1024字节)的chunk被称为large chunk,而保存large chunks的bin被称为large bin。位于small bins后面,数组编号从64开始,后64个bin为large bins。同一个bin上的chunk,可以大小不一定相同。large bins都是通过等差步长的方式进行拆分。(以32位系统为例,前32个bin步长64,后16个bin步长512,后8个步长4096,后四个32768,后2个262144)(编号63到64的步长跟)。起始bin大小为512字节(64位系统1024)。
2. bins之间的步长关系
然后再看一下,small bin和large bin每个bin之间的步长等差数列。在32位系统下面,前63个bin(small bins)的步长为8,后32个bins(large bins)步长64,以此类推。
- small bins一个62个,每个bins之间的步长一致,32位系统步长8,64位系统步长16;
- large bins 一共32+16+8+4+2+1=63个 + 起始bin(512或者1024)。以32位系统为例,前32个bin步长64,后16个bin步长512,后8个步长4096,后四个32768,后2个262144,后一个不限制
Bins for sizes < 512 bytes contain chunks of all the same size, spaced
8 bytes apart. Larger bins are approximately logarithmically spaced:
64 bins of size 8
32 bins of size 64
16 bins of size 512
8 bins of size 4096
4 bins of size 32768
2 bins of size 262144
1 bin of size what's left
3. 特殊类型的bins:top chunk、mmaped chunk、last remainder chunk
除了上面可见的bins管理之外,还是三种例外的chunk管理方式:top chunk,mmaped chunk 和last remainder chunk
Top chunk:top chunk相当于分配区的顶部空闲内存,当bins上都不能满足内存分配要求的时候,就会来top chunk上分配。top chunk大小比用户所请求大小还大的时候,top chunk会分为两个部分:User chunk(用户请求大小)和Remainder chunk(剩余大小)。其中Remainder chunk成为新的top chunk。当top chunk大小小于用户所请求的大小时,top chunk就通过sbrk(main arena)或mmap(thread arena)系统调用来扩容。
mmaped chunk:当分配的内存非常大(大于分配阀值,默认128K)的时候,需要被mmap映射,则会放到mmaped chunk上,当释放mmaped chunk上的内存的时候会直接交还给操作系统。
Last remainder chunk:Last remainder chunk是另外一种特殊的chunk,就像top chunk和mmaped chunk一样,不会在任何bins中找到这种chunk。当需要分配一个small chunk,但在small bins中找不到合适的chunk,如果last remainder chunk的大小大于所需要的small chunk大小,last remainder chunk被分裂成两个chunk,其中一个chunk返回给用户,另一个chunk变成新的last remainder chunk。
四、多线程争夺问题的解决
ptmalloc的分配器为了解决多线程争夺问题,分为主分配区main_area和非主分配区thread_arena
- 每个进程有一个主分配区,也可以允许有多个非主分配区。
- 主分配区可以使用brk和mmap来分配,而非主分配区只能使用mmap来映射内存块
- 非主分配区的数量一旦增加,则不会减少。
- 主分配区和非主分配区形成一个环形链表进行管理。通过malloc_state->next来链接
我们可以看一下一个线程调用malloc的时候的流程以及分配区的状态:
- 当一个线程使用malloc分配内存的时候,首选会检查该线程环境中是否已经存在一个分配区,如果存在,则对该分配区进行加锁,并使用该分配区进行内存分配
- 如果分配失败,则遍历链表中获取的未加锁的分配区
- 如果整个链表都没有未加锁的分配区,则ptmalloc开辟一个新的分配区,假如malloc_state->next全局队列,并该线程在改内存分区上进行分配
- 当释放这块内存的时候,首先获取分配区的锁,然后释放内存,如果其他线程正在使用,则等待其他线程
/* 分配区全局链表:分配区链表,主分配区放头部,新加入的分配区放main_arean.next 位置 Linked list */
struct malloc_state *next;
/* 分配区空闲链表 Linked list for free arenas. Access to this field is serialized
by free_list_lock in arena.c. */
struct malloc_state *next_free;
五、状态机的初始化malloc_init_state
状态机的初始化,通过malloc_init_state。初始化过程主要做了三件事情:
- 将bins进行初始化,生成bins数组
- 处理fastchunk的状态
- 初始化Top chunk,默认指向了unsorted bin上的第一个chunk
static void
malloc_init_state (mstate av)
{
int i;
mbinptr bin;
/* Establish circular links for normal bins */
/* NBINS=128 */
/**
* 说明:ptmalloc通过bins数组来管理chunk双向链表,初始化的chunk链表指针都指向了自己
* 1. bins上管理三种bin:unsorted bin、small bins和large bins
* 2. 下标默认从1开始,其中下标为1的,则是unsorted bin
*/
for (i = 1; i < NBINS; ++i)
{
bin = bin_at (av, i);
bin->fd = bin->bk = bin;
}
#if MORECORE_CONTIGUOUS
if (av != &main_arena)
#endif
set_noncontiguous (av);
if (av == &main_arena)
set_max_fast (DEFAULT_MXFAST);
/* 默认fastchunk 是false的,没有被初始化的 */
atomic_store_relaxed (&av->have_fastchunks, false);
/* 初始化Top chunk,默认指向了unsorted bin上的第一个chunk */
av->top = initial_top (av);
}
//通过bin_at方法,找到bin对应位置
/* addressing -- note that bin_at(0) does not exist */
#define bin_at(m, i) \
(mbinptr) (((char *) &((m)->bins[((i) - 1) * 2])) \
- offsetof (struct malloc_chunk, fd))
六、BINS的宏定义和操作
前面说过,我们的空闲chunk,都会在bins上管理。ptmalloc提供了一些方便的宏函数,用于操作bin上的chunk。
1. BINS的基础定义字段
- NBINS:值为128,用于循环遍历bin的个数,实际bin的个数为127个,因为下标从1开始
- NSMALLBINS:定义了small bin的个数为64个,large bin个数为62个
- SMALLBIN_WIDTH:small bin的步长宽度,32位系统8个字节,64位系统16个字节
- MIN_LARGE_SIZE:large bin最小的值是64 * SMALLBIN_WIDTH,所以32位系统是512字节,64位系统是1024字节
#define NBINS 128 //默认128个bins
#define NSMALLBINS 64 //small bin的个数
#define SMALLBIN_WIDTH MALLOC_ALIGNMENT //small bin的长度
#define SMALLBIN_CORRECTION (MALLOC_ALIGNMENT > 2 * SIZE_SZ)
#define MIN_LARGE_SIZE ((NSMALLBINS - SMALLBIN_CORRECTION) * SMALLBIN_WIDTH)
2. BINS的基本查找函数
-
bin_at:通过下标找到bin
-
next_bin:通过bin上的一个chunk,找到物理地址的下一个chunk地址
-
first和last:获取空闲bins上的chunk双向链表指针,找到前后的chunk
-
bin_index:将 sz 大小转换成对应的数组下标。(判断属于 large bin 还是 small bin)。例:第一个largebin的起始大小为1024,那么1024>>6=16,所以其在bins数组中的下标为48+16=64
/* addressing -- note that bin_at(0) does not exist */
//通过下标找到bin
#define bin_at(m, i) \
(mbinptr) (((char *) &((m)->bins[((i) - 1) * 2])) \
- offsetof (struct malloc_chunk, fd))
/* analog of ++bin */
//通过bin上的一个chunk,找到物理地址的下一个chunk地址
#define next_bin(b) ((mbinptr) ((char *) (b) + (sizeof (mchunkptr) << 1)))
/* Reminders about list directionality within bins */
//在bins上管理的chunk是通过fd/bk双向链表指针链接起来的
//first=获取前一个指针地址 last=获取后一个指针地址
#define first(b) ((b)->fd)
#define last(b) ((b)->bk)
/* sz 大小属于 small bins 置 1,否则置 0 */
#define in_smallbin_range(sz) \
((unsigned long) (sz) < (unsigned long) MIN_LARGE_SIZE)
/* 将 sz 大小 转换成对应的数组下标。(对 samll bin) */
#define smallbin_index(sz) \
((SMALLBIN_WIDTH == 16 ? (((unsigned) (sz)) >> 4) : (((unsigned) (sz)) >> 3))\
+ SMALLBIN_CORRECTION)
/* 将 sz 大小 转换成对应的数组下标。(对 32 位 large bin) */
#define largebin_index_32(sz) \
(((((unsigned long) (sz)) >> 6) <= 38) ? 56 + (((unsigned long) (sz)) >> 6) :\
((((unsigned long) (sz)) >> 9) <= 20) ? 91 + (((unsigned long) (sz)) >> 9) :\
((((unsigned long) (sz)) >> 12) <= 10) ? 110 + (((unsigned long) (sz)) >> 12) :\
((((unsigned long) (sz)) >> 15) <= 4) ? 119 + (((unsigned long) (sz)) >> 15) :\
((((unsigned long) (sz)) >> 18) <= 2) ? 124 + (((unsigned long) (sz)) >> 18) :\
126)
#define largebin_index_32_big(sz) \
(((((unsigned long) (sz)) >> 6) <= 45) ? 49 + (((unsigned long) (sz)) >> 6) :\
((((unsigned long) (sz)) >> 9) <= 20) ? 91 + (((unsigned long) (sz)) >> 9) :\
((((unsigned long) (sz)) >> 12) <= 10) ? 110 + (((unsigned long) (sz)) >> 12) :\
((((unsigned long) (sz)) >> 15) <= 4) ? 119 + (((unsigned long) (sz)) >> 15) :\
((((unsigned long) (sz)) >> 18) <= 2) ? 124 + (((unsigned long) (sz)) >> 18) :\
126)
// XXX It remains to be seen whether it is good to keep the widths of
// XXX the buckets the same or whether it should be scaled by a factor
// XXX of two as well.
/* 将 sz 大小 转换成对应的数组下标。(对 64 位 large bin) */
/* 第一个largebin的起始大小为1024,那么1024>>6=16,所以其在bins数组中的下标为48+16=64 */
#define largebin_index_64(sz) \
(((((unsigned long) (sz)) >> 6) <= 48) ? 48 + (((unsigned long) (sz)) >> 6) :\
((((unsigned long) (sz)) >> 9) <= 20) ? 91 + (((unsigned long) (sz)) >> 9) :\
((((unsigned long) (sz)) >> 12) <= 10) ? 110 + (((unsigned long) (sz)) >> 12) :\
((((unsigned long) (sz)) >> 15) <= 4) ? 119 + (((unsigned long) (sz)) >> 15) :\
((((unsigned long) (sz)) >> 18) <= 2) ? 124 + (((unsigned long) (sz)) >> 18) :\
126)
/* 将 sz 大小 转换成对应的数组下标。(根据 SIZE_SZ 判断 large bin 转换宏)*/
#define largebin_index(sz) \
(SIZE_SZ == 8 ? largebin_index_64 (sz) \
: MALLOC_ALIGNMENT == 16 ? largebin_index_32_big (sz) \
: largebin_index_32 (sz))
/* 将 sz 大小 转换成对应的数组下标。(判断属于 large bin 还是 small bin) */
#define bin_index(sz) \
((in_smallbin_range (sz)) ? smallbin_index (sz) : largebin_index (sz))