个人为了秋招整理一些知识点,理清思路,大佬们发现不对可以指正。
图片和一部分文字来自:
https://openeuler.org/zh/blog/wangshuo/glibc+malloc%E5%8E%9F%E7%90%86%E7%AE%80%E6%9E%90/glibc+malloc%E5%8E%9F%E7%90%86%E7%AE%80%E6%9E%90.html
https://segmentfault.com/a/1190000005118060
先对涉及的数据结构做一个简单的介绍:
chunk,是glibc管理的最小单位:
struct malloc_chunk {
INTERNAL_SIZE_T mchunk_prev_size; /* Size of previous chunk (if free). */
INTERNAL_SIZE_T mchunk_prev_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的mchunk_prev_size和mchunk_size适用于隐式链表技术,由于内存对齐的原因,mchunk_size的低三位用来作信息标识。另外fd、bk以及fd_nextsize、bk_nextsize在chunk未分配时做链表管理,分配后用于存储用户数据。
bin,用来管理free chunk的链表:
在分配的heap上,针对不同大小的bin,将其分成四类:
fast bin、unsorted bin、small bin、large bin,
struct malloc_state
{
/* Serialize access. */
mutex_t mutex;
/* Flags (formerly in max_fast). */
int flags;
/* 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. */
struct malloc_state *next_free;
/* Memory allocated from the system in this arena. */
INTERNAL_SIZE_T system_mem;
INTERNAL_SIZE_T max_system_mem;
};
其中fastbinY用于连接所有小块chunk,大小无规律;
bin 数组: 这也是一个数组,用于记录颗粒相对较大的chunk。默认有126个元素(bin[0]未使用),分别是:
[1] 为 unsorted bin
[2~63] 为 small bin
[64~126] 为 large bin
fastbin链表数目为10,默认情况下大小为16到128字节,公差为16的chunk将被分类到fast chunk。每个fast bin都是一个单链表,内存申请释放时按照LIFO(后入先出)算法进行操作,添加操作(free 内存)就是将新的fast chunk加入链表尾,删除操作(malloc内存)就是将链表尾部的fast chunk删除。另外,为了保证小块内存申请释放的性能,fast bin不会对free chunk进行合并操作。
smallbin用于管理相对小块的chunk,smallbin的特点如下:
1、smallbin共有62个。每个smallbin为循环双链表,采用FIFO(先入先出)算法:内存释放操作就将新释放的chunk添加到链表的前端,分配操作就从链表的尾端中获取chunk。
2、同一个smallbin中所有chunk大小是一样的。
3、相邻的freechunk需要进行合并操作,即合并成一个大的freechunk。
largebin用于管理相对大块的chunk,largebin管理的chunk大小大于等于1024字节的chunk,largebin的特点如下:
1、large bin 总共有63个。largebin类似于smallbin,只是需要注意两点:一是同一个largebin中每个chunk的大小可以不一样,但必须处于某个给定的范围;二是largechunk可以添加、删除在largebin的任何一个位置。
2、largebin的合并操作类似于smallbin。
另外largebin上链表chunk大小不再是递增的关系,内存块的大小在逐步增大。
当用户请求的大小超过smallbin的最大值时将进入largebin的判断逻辑,具体来说就是找到用户请求的大小落在哪个largebin的区间,然后判断该largebin中最大的chunk的size是否大于用户请求的大小。如果大于,就从尾部开始遍历该largebin,找到第一个大小相等或接近的chunk,分配给用户;如果尾端最小的chunk大于用户请求的大小的话,就将该chunk拆分为两个chunk:前者返回给用户,大小等同于用户请求的大小,剩余的部分做为一个新的chunk添加到unsortedbin中;如果该largebin中最大的chunk的大小小于用户请求的大小的话,那么就依次查看后续的largebin中是否有满足需求的chunk。
为了加快查找合适大小bin的速度,还引入了binmap,记录各个bin上是否为空。
unsortbin即大小无规律的chunk,用来存放分配时额外内存块。
top chunk作为一个应急内存,当前面的free chunk无法满足请求内存大小时,就切割topchunk,不然只能通过系统调用brk和mmap去申请扩展了。
tcache给每个线程创建的缓存,实现无锁分配。
#if USE_TCACHE
/* We overlay this structure on the user-data portion of a chunk when
the chunk is stored in the per-thread cache. */
typedef struct tcache_entry
{
struct tcache_entry *next;
/* This field exists to detect double frees. */
struct tcache_perthread_struct *key;
} tcache_entry;
/* There is one of these for each thread, which contains the
per-thread cache (hence "tcache_perthread_struct"). Keeping
overall size low is mildly important. Note that COUNTS and ENTRIES
are redundant (we could have just counted the linked list each
time), this is for performance reasons. */
typedef struct tcache_perthread_struct
{
char counts[TCACHE_MAX_BINS];
tcache_entry *entries[TCACHE_MAX_BINS];
} tcache_perthread_struct;
static __thread bool tcache_shutting_down = false;
static __thread tcache_perthread_struct *tcache = NULL;
...
#endif
cache_perthread_struct包含一个数组entries,用于放置bins,bins的数目定义如下,可知默认可存放64个bins,数组 counts存放每个bins中的chunk数量。每个被放入相应bins中的chunk都会在其用户数据中包含一个tcache_entry(FD指针),指向同 bins 中的下一个 chunk,构成单链表。
以及malloc内存申请流程:
个人感觉通过局部线程存储直接把之前的通过锁机制申请内存的方式直接停用了(从流程图中tcache下面的syscall中可以看出),除非到了迫不得已的时刻才通过fastbin及以下的bins去获取内存(保留大概是为了兼容不支持局部线程存储的操作系统?)。
释放流程: