前言:
- 堆的glibc实现主要包括struct _heap_info,struct malloc_state,struct malloc_chunk这3个结构体
- 在介绍结构体之前需要介绍一下非常重要的arena(竞技场)
一,什么是Arena
- 我们知道一个线程申请的1个/多个堆包含很多的信息:二进制位信息,多个malloc_chunk信息等这些堆需要东西来进行管理,那么Arena就是来管理线程中的这些堆的
二,Arena的特性
- 一个线程只有一个arnea,并且这些线程的arnea都是独立的不是相同的
- 主线程的arnea称为“main_arena”。子线程的arnea称为“thread_arena”
三,Arena的数量限制与多线程管理
- 每个程序中arnea的数量是有限制的,因为过多的线程也不会产生过多的arnea,而是由以下规则制定的:
32位系统中:
Number of arena = 2 * number of cores + 1.
64位系统中:
Number of arena = 8 * number of cores + 1
从上面的公式可以知道,当线程数量超出系统可以提供的arnea数量时,就需要一些在概念上类似于在Linux的中锁的机制来实现线程对不同arnea的共享,下面进行举例:
演示案例:
一台只含有一个处理器核心的PC机安装有32位操作系统,其上运行了一个多线程应用程序,共含有4个线程 - 主线程和三个用户线程显然线程个数大于系统能维护的最大竞技场个数(2 *核心数+ 1 = 3),那么此时glibc malloc就需要确保这4个线程能够正确地共享这3个竞技场
- 第一步:当主线程首次调用malloc的时候,glibc malloc会直接为它分配一个主竞技场,不发生其他操作
- 第二步:当用户线程1和用户线程2首次调用malloc的时候,glibc malloc会分别为每个用户线程创建一个新的线程竞技场。此时,各个线程与arena是一一对应的
- 第三步:当用户线程3调用malloc的时候,就出现问题了。因为此时glibc malloc能维护的arena个数已经达到上限,无法再为线程3分配新的arena了,那么就需要重复使用已经分配好的3个竞技场中的一个(主竞技场,竞技场1或者竞技场2)
此时glibc malloc会做以下操作:
- 首先,glibc malloc循环遍历所有可用的竞技场,在遍历的过程中,它会尝试锁该竞技场。如果成功锁(该竞技场当前对应的线程并未使用堆内存则表示可锁),那么该竞技场就可以被线程3所使用
- 而如果没能找到可用的舞台上,那么就将线程3的malloc的操作阻塞,直到有可用的舞台为止
- 当线程3的malloc阻塞解除时,glibc malloc就会先尝试使用最近访问的竞技场(此时为主竞技场)。如果此时主竞技场可用的话,就直接使用,否则就将线程3再次阻塞,直到主舞台上再次可用为止
其他情况以此类推
四,struct malloc_state(Arena的实现)
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. */
int have_fastchunks;
/* Fastbins */
mfastbinptr fastbinsY[NFASTBINS];
/* Base of the topmost chunk -- not otherwise kept in a bin */
mchunkptr top;
/* The remainder from the most recent split of a small request */
mchunkptr last_remainder;
/* Normal bins packed as described above */
mchunkptr bins[NBINS * 2 - 2];
/* Bitmap of bins */
unsigned int binmap[BINMAPSIZE];
/* 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;
};
- glibc的中arnea就是用这个结构体表示的
- 其中包含很多的信息:各种bins的信息,top chunk以及最后一个剩余chunk等
- bins数组为什么是NBINS*2-2个大小,见文章:https://blog.csdn.net/qq_41453285/article/details/97613588
成员介绍:
- last_remainder:当次arena中产生last_remainder时,此成员被初始化,并且指向arnea中的last_remainder
- fastbinsY数组:存储的是该领域管理的fastbins
- bins数组:存储的是该领域管理的smallbins,unsortedbin,largebins
- binmap变量:系统查看有哪些垃圾箱链中有块时,不可能去fastbinsY和箱数组一个一个的遍历通过binmap变量,采用二进制存储,将二进制位与数组的索引相对,系统查找箱链时可以。通过按位与来查询,这样更高效。虽然unsigned int的二进制位比数组总元素少,但是系统不会有那么多的bin链,不需要考虑这个问题
五,struct _heap_info(堆信息结构体)
- 我们知道一个线程可以包含多个堆段,这些堆段同属于一个舞台来管理。每个堆段的信息就是用这个结构体来表示的
- 注意:这个不是存储堆块的数据,而是来解释说明这个堆段的
typedef struct _heap_info
{
mstate ar_ptr; /* Arena for this heap. */
struct _heap_info *prev; /* Previous heap. */
size_t size; /* Current size in bytes. */
size_t mprotect_size; /* Size in bytes that has been mprotected
PROT_READ|PROT_WRITE. */
/* Make sure the following data is properly aligned, particularly
that sizeof (heap_info) + 2 * SIZE_SZ is a multiple of
MALLOC_ALIGNMENT. */
char pad[-6 * SIZE_SZ & MALLOC_ALIGN_MASK];
} heap_info;
相关成员:
- ar_ptr:此堆段归属于哪一个arnea管理
- prev:前一个堆段
六,struct malloc_chunk
- 一个堆块被分为多个块,这些块就是用这些结构体表示的,这个才是我们在glibc的正真存储堆数据信息的结构体
- 结构体详细简介,见文章:https://blog.csdn.net/qq_41453285/article/details/97135257
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;
};
七,Main_arena与堆的宏观结构图
- 注意:主竞技场没有多个堆,因此没有heap_info结构。当主竞技场所指向的堆内存不足时,sbrk的堆段被扩展(连续区域),直到它碰到内存映射段
- 不同于主题竞技场,主竞技场的竞技场标题并不是sbrk heap segment 的一部分,而是一个全局变量!因此它属于libc.so 的数据段
- 可以看到main arena的顶级成员指向堆块的malloc_chunk结构体
- 因为主竞技场没有多个堆,所以没有heap_info结构体
八,Thead_arena与堆的宏观结构图
当一个线程只有一个堆段时:
可以看到该图被分为3部分:
- heap_info:表示该堆段的信息,并且该结构体的ar_ptr成员指针指向于该堆段所属的thread arena
- malloc_state:该堆段的竞技场并且顶部指向于最顶端的个malloc_chunk
- chunk块:就是该堆段所存储的区域
当一个线程含有多个堆段时:
相对于一个堆段,做了如下改变:
- heap_info:因为有两个堆段,所以每个堆段都有自己的heap_info并且两个堆段在内存中不是物理相邻的,因此第二个heap_info的上一个指针指向于第一个heap_info的ar_ptr成员,而第一个heap_info结构体的ar_ptr成员指向了malloc_state,这样就构成了一个单链表,方便后续管理
- malloc_state:虽然有多个堆段,但是只有一个arena