Ptmalloc2内存管理分析


基本概念

各种平台有不同的内存分配器可用:

  • dlmalloc – 通用分配器
  • PTMALLOC2 – glibc
  • jemalloc – FreeBSD 和 Firefox
  • tcmalloc – Google
  • libumem – Solaris

历史上ptmalloc2 是从 dlmalloc 派生而来的。在 fork 之后,添加了线程支持,并于 2006 年发布。在正式发布后,ptmalloc2 被集成到 glibc 源代码中。集成后,将直接对 glibc malloc 源代码本身进行代码更改。因此,ptmalloc2 和 glibc 的 malloc 实现之间可能会有很多变化。

线程: 在 Linux 的早期,dlmalloc 被用作默认内存分配器。但后来由于 ptmalloc2 的多线程支持,它成为 linux 的默认内存分配器。线程支持有助于提高内存分配器性能,从而提高应用程序性能。在 dlmalloc 中,当两个线程同时调用 malloc 时,只有一个线程可以进入关键部分,因为空闲列表数据结构在所有可用线程之间共享。因此,在多线程应用程序中,内存分配需要时间,从而导致性能下降。而在 ptmalloc2 中,当两个线程同时调用 malloc 时,会立即分配内存,因为每个线程都维护一个单独的堆段,因此维护这些堆的自由列表数据结构也是独立的。这种为每个线程维护单独的堆和空闲列表数据结构的行为称为 per thread arena

  • 静态内存管理

静态内存管理是指在编译时确定内存分配,并且内存分配的大小和生命周期在程序运行时保持不变。比如我们编程时的静态变量和全局变量就属于静态内存管理,好处是快速,缺点是不够灵活。

  • 动态内存管理

动态内存管理是指在程序运行时根据需要进行内存分配和释放,由程序员显式管理。这个最显著的例子就是我们的mallocfree函数。好处是灵活,缺点是复杂。

我们在编写 C 语言程序时malloc一块内存是由堆管理器(ptmalloc)分配了一块内存给我们使用,malloc返回的是指向那块内存的指针。

例如下面的代码,会将malloc分配的内存地址保存在ptr指针中,我们通过ptr指针来使用这块内存。

1
char *ptr=(char *)malloc(0x10);

malloc通过 ptmalloc 向我们分配这块内存,而内存分配多少是由 ptmalloc 的分配机制来决定的。

内存布局

  • 32位程序经典内存布局:
    • 最高地址的 1G 内存空间属于内核空间不能使用。
    • 内核空间以下的空间属于栈,栈自顶向下增长,最大大小为 8M
    • 栈以下便是mmap区域,空间向上增长,和栈相对。
    • heap段自底向上增长。
    • 剩下的空间属于可执行文件空间,即bss段、data段、text

  • 64位程序经典内存布局:
    • 最高地址的 128TB 内存空间属于内核空间不能使用。
    • 内核空间以下的空间属于栈,栈自顶向下增长,最大大小为 8M
    • 栈以下便是mmap区域,空间向上增长,和栈相对。
    • heap段自底向上增长。
    • 剩下的空间属于可执行文件空间,即bss段、data段、text

![[Pasted image 20240630112911.png]]

C语言内存操作函数

  • malloc
1
void* malloc(size_t size);
  • realloc

如果ptr为空,则realloc的行为类似于malloc。如果size为0,则它的行为类似于free

1
void* realloc(void* ptr, size_t size);
  • calloc

malloc不同的是,calloc分配的内存会自动清零。

1
void* calloc(size_t num, size_t size);
  • free

释放由malloccallocrealloc分配的内存块。

1
void free(void* ptr);

内存分配背后的系统调用

brk和sbrk

brk是系统调用,sbrk为库函数。系统调用负责提供一种最小功能,而库函数则提供比较复杂的功能。malloc函数族就是调用sbrk函数将数据段的下界移动分配内存。

1
2
3
#include <unistd.h>
int brk(void *end_data_segment);
void *sbrk(intptr_t increment);

mmap

mmap函数在 Linux 系统中主要作用是将文件或设备映射到进程的虚拟地址空间,也可以用于在不依赖文件的情况下分配匿名内存区域。

mmap在内存分配中用于匿名映射,直接向操作系统请求内存,而不改变进程的堆区域。这样分配方式要比brk更灵活一点,适合分配较大的内存块。malloc函数的实现中,通过使用mmap分配超过一定大小(如128kb)的内存块。

munmap函数用于将特定地址区域的对象映射删除掉。

1
2
3
#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);

gdb调试

  • 常用命令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
vmmap #查看进程的内存布局
heap #查看所有堆
bins #查看所有bin
heap -v #查看堆块的详细信息
vis #可视化堆
arena #显示arena的详细信息
x/32gx &main_arena #查看main_arena上的值
call malloc_stats() #打印malloc分配的内存
p malloc(0x100) #打印分配的堆块
heapbase #查看堆的起始地址
heapinfo #显示堆的信息
parseheap #显示堆结构
info threads #多线程调试
tracemalloc #会提示所有操作堆的地方
arenas #显示所有arena的基本信息
arenainfo #好看的显示所有arena的信息
p &__free_hook #查看某个函数的真实地址
p *__free_hook #查看某个函数的指向

个人比较喜欢通过patchelf修改程序ldlibc进行调试。

数据结构

glibc 的数据结构中有tcache机制和没有tcache机制是存在一些区别的。

这里我们会对比没有tcache机制的 glibc2.23 和 有tcache机制的 glibc2.27 的区别。

malloc_par

在 ptmalloc 中使用malloc_par结构体来记录堆管理器的相关参数,该结构体定义于malloc.c中,如下:

  • 2.23
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
struct malloc_par
{
/* Tunable parameters */
unsigned long trim_threshold;
INTERNAL_SIZE_T top_pad;
INTERNAL_SIZE_T mmap_threshold;
INTERNAL_SIZE_T arena_test;
INTERNAL_SIZE_T arena_max;

/* Memory map support */
int n_mmaps;
int n_mmaps_max;
int max_n_mmaps;
/* the mmap_threshold is dynamic, until the user sets
it manually, at which point we need to disable any
dynamic behavior. */
int no_dyn_threshold;

/* Statistics */
INTERNAL_SIZE_T mmapped_mem;
/*INTERNAL_SIZE_T sbrked_mem;*/
/*INTERNAL_SIZE_T max_sbrked_mem;*/
INTERNAL_SIZE_T max_mmapped_mem;
INTERNAL_SIZE_T max_total_mem; /* only kept for NO_THREADS */

/* First address handed out by MORECORE/sbrk. */
char *sbrk_base;
};
  • 2.27
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
struct malloc_par  
{
/* Tunable parameters */
unsigned long trim_threshold;
INTERNAL_SIZE_T top_pad;
INTERNAL_SIZE_T mmap_threshold;
INTERNAL_SIZE_T arena_test;
INTERNAL_SIZE_T arena_max;

/* Memory map support */
int n_mmaps;
int n_mmaps_max;
int max_n_mmaps;
/* the mmap_threshold is dynamic, until the user sets
it manually, at which point we need to disable any
dynamic behavior. */
int no_dyn_threshold;

/* Statistics */
INTERNAL_SIZE_T mmapped_mem;
INTERNAL_SIZE_T max_mmapped_mem;

/* First address handed out by MORECORE/sbrk. */
char *sbrk_base;

#if USE_TCACHE
/* Maximum number of buckets to use. */
size_t tcache_bins;
size_t tcache_max_bytes;
/* Maximum number of chunks in each bucket. */
size_t tcache_count;
/* Maximum number of chunks to remove from the unsorted list, which
aren't used to prefill the cache. */
size_t tcache_unsorted_limit;
#endif
};

malloc_par结构体定义了和mmaparena相关的一些参数,以及sbrk的基址,其中重要的参数解释如下:

  • top_pad:初始化或扩展堆的时候需要多申请的内存大小。
  • mmap_threshold:决定sysmalloc是通过mmapsbrk分配内存的界限,即如果申请的内存大小不小于该值则采用mmap分配,否则采用sbrk扩展heap区域分配。并且这个值是动态调整的,如果释放的内存是通过mmap得到的则mmap_threshold与该内存大小取max。并且mmap_threshold最大不能超过DEFAULT_MMAP_THRESHOLD_MAX,即 0x2000000。
  • trim_threshold:用于main_arena中保留内存量的控制。当释放的chunkmmap获得的,同时大小大于mmap_threshold,则除了更新mmap_threshold外还会将trim_threshold乘2。当释放的chunk大小不在fast bin范围合并完size大于FASTBIN_CONSOLIDATION_THRESHOLD即0x10000,会根据该字段缩小top chunk
  • n_mmapsmmap的内存数量,即 ptmalloc 每次成功mmapn_mmpas加 1,ptmalloc 每次成功munmapn_mmaps减1。
  • n_mmaps_maxn_mmaps的上限,即最多能mmap的内存数量。
  • max_n_mmapsn_mmaps达到过的最大值。
  • mmapped_mem:当前mmap的内存大小总和。
  • max_mmapped_memmmap的内存大小总和达到过的最大值。
  • sbrk_base:表示通过brk系统调用申请的heap区域的起始地址。
  • no_dyn_threshold:表示是否禁用heap动态调整保留内存的大小,默认为0。

该结构体类型的实例mp_用以记录 ptmalloc 相关参数,同样定义于malloc.h中,如下:

  • 2.23
1
2
3
4
5
6
7
8
9
10
11
/* There is only one instance of the malloc parameters.  */

static struct malloc_par mp_ =
{
.top_pad = DEFAULT_TOP_PAD,
.n_mmaps_max = DEFAULT_MMAP_MAX,
.mmap_threshold = DEFAULT_MMAP_THRESHOLD,
.trim_threshold = DEFAULT_TRIM_THRESHOLD,
#define NARENAS_FROM_NCORES(n) ((n) * (sizeof (long) == 4 ? 2 : 8))
.arena_test = NARENAS_FROM_NCORES (1)
};
  • 2.27
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static struct malloc_par mp_ =  
{
.top_pad = DEFAULT_TOP_PAD,
.n_mmaps_max = DEFAULT_MMAP_MAX,
.mmap_threshold = DEFAULT_MMAP_THRESHOLD,
.trim_threshold = DEFAULT_TRIM_THRESHOLD,
#define NARENAS_FROM_NCORES(n) ((n) * (sizeof (long) == 4 ? 2 : 8))
.arena_test = NARENAS_FROM_NCORES (1)
#if USE_TCACHE
,
.tcache_count = TCACHE_FILL_COUNT,
.tcache_bins = TCACHE_MAX_BINS,
.tcache_max_bytes = tidx2usize (TCACHE_MAX_BINS-1),
.tcache_unsorted_limit = 0 /* No limit. */
#endif
};

在 gdb 调试时我们可以通过p mp_命令查看该实例值。

或者可以通过display mp_命令来动态查看所有malloc_par字段的值

heap_info

heap_info位于一个heap块的开头,用以记录通过mmap系统调用从 Memory Mapping Segment 处申请到的内存块的信息。定义于arena.c中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* A heap is a single contiguous memory region holding (coalesceable)
malloc_chunks. It is allocated with mmap() and always starts at an
address aligned to HEAP_MAX_SIZE. */

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;

heap_info结构体的成员如下:

  • ar_ptr:指向管理该堆块的arena
  • prev:该heap_info所链接的上一个heap_info
  • size:记录该堆块的大小
  • mprotect_size:记录该堆块中被保护(mprotected)的大小。
  • pad:即padding,用以在SIZE_SZ不正常的情况下进行填充以让内存对齐,正常情况下pad所占用空间应为0字节。

arena

大部分情况下对于每个线程而言其都会单独有着一个arena实例用以管理属于该线程的堆内存区域。

ptmalloc内部的内存池是由malloc_state结构体进行定义的,即arena本身便为malloc_state的一个实例对象。

malloc_state结构体定义于malloc.c中,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
struct malloc_state
{
/* Serialize access. */
mutex_t mutex;

/* Flags (formerly in max_fast). */
int flags;

/* Fastbins */
//NFASTBINS宏用于计算fastbin数组的大小
//#define NFASTBINS (fastbin_index(request2size(MAX_FAST_SIZE)) + 1)

//MAX_FAST_SIZE是glibc中定义的最大fastbin大小,通常为一个常量
//#define MAX_FAST_SIZE (80 * SIZE_SZ / 4)
//64位下为160,32位为80

//首先通过request2size将申请的内存大小转化为合适的chunk大小
/*#define request2size(req) \
(((req) + SIZE_SZ + MALLOC_ALIGN_MASK < MINSIZE) ? \
MINSIZE : \
((req) + SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK)
*/

//64位结果为172,32位结果为84
//然后通过fastbin_index计算chunk size对应的fastbin索引
//32位结果为9,64位结果也为9

//实际上fastbinY数组的大小是10,这是因为额外保留了一个位置用于其它特殊目的
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 */
//NBINS是一个宏定义,值为128
//bins[254]
mchunkptr bins[NBINS * 2 - 2];

/* Bitmap of bins */
//BINMAPSIZE (NBINS / BITSPERMAP)
//结果为4
unsigned int binmap[BINMAPSIZE];

/* Linked list */
//指向下一个arena
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. */
//在多线程内存管理中,记录当前有多少个线程与特定的arena相关联
INTERNAL_SIZE_T attached_threads;

/* Memory allocated from the system in this arena. */
INTERNAL_SIZE_T system_mem;
INTERNAL_SIZE_T max_system_mem;
};
  • 2.27
1
2
3
4
5
6
7
8
__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;

malloc_state结构体的成员如下:

  • mutexmutex变量即为多线程互斥锁,用以保证线程安全。
  • flags:标志位,用以表示arena的一些状态,如:是否有fastbin、内存是否连续等。
  • fastbinY:存放 fastbin chunk的数组,一共有10个fastbin chunk。
  • top:指向Top chunk的指针。
  • last_remainderchunk切割中的剩余部分。malloc在分配chunk时若是没找到size合适的chunk而是找到了一个size更大的chunk,则会从大chunk中切割掉一块返回给用户,剩下的那一块便是last_remainder,其随后会被放入unsorted bin中。
  • bins:存放闲置chunk的数组。bins包括large binsmall binunsorted bin
  • binmap:记录bin是否为空的bitset。需要注意的是chunk被取出后若一个bin空了并不会立即被置0,而会在下一次遍历到时重新置位。
  • system_mem:记录当前arena在堆区中所分配到的内存的总大小。
  • max_system_mem:当操作系统予进程以内存时,system_mem会随之增大,当内存被返还给操作系统时,system_mem会随之减小,max_system_mem变量便是用来记录在这个过程中system_mem的峰值。

在gdb调试中我们可以通过p &main_arena来查看main arena

main_arena为一个定义于malloc.c中的静态的malloc_state结构体。

1
2
3
4
5
6
7
8
9
10
11
12
/* There are several instances of this struct ("arenas") in this
malloc. If you are adapting this malloc in a way that does NOT use
a static or mmapped malloc_state, you MUST explicitly zero-fill it
before using. This malloc relies on the property that malloc_state
is initialized to all zeroes (as is true of C statics). */

static struct malloc_state main_arena =
{
.mutex = _LIBC_LOCK_INITIALIZER,
.next = &main_arena,
.attached_threads = 1
};

由于其为 libc 中的静态变量,该arena会随着 libc 文件一同加载到 Memory Mapping Segment。因此在堆题中通常通过泄露arena的地址以获得 libc 在内存中的基地址。

chunk

在程序执行的过程中,我们称由malloc申请的内存为chunk。这块内存在ptmalloc内部用malloc_chunk结构体来表示。当程序申请的chunkfree后,会被加入到相应的空闲管理链表中。

我们在malloc一块内存的时候返回给我们的指针是指向user data的指针

malloc_chunk定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
struct malloc_chunk {

INTERNAL_SIZE_T prev_size; /* Size of previous chunk (if free). */
INTERNAL_SIZE_T 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;
};

每个字段的具体的解释如下:

  • prev_size:如果物理相邻的前一地址chunk是空闲的话,那该字段记录的是前一个chunk的大小(包括chunk头)。否则,该字段可以用来存储物理相邻的前一个chunk的数据。
  • size:该chunk的大小,大小必须是2 * SIZE_SZ的整数倍。该字段的低三个比特位对chunk的大小没有影响,它们从高到低分别表示为:
    • NON_MAIN_ARENA,记录当前chunk是否不属于主线程,1 表示不属于,0 表示属于。
    • IS_MAPPED,记录当前chunk是否是由mmap分配的。
    • PREV_INUSE,记录前一个chunk块是否被分配。一般来说,堆中第一个被分配的内存块的size字段的P位都会被设置为 1,以便于防止访问前面的非法内存。当一个chunksizeP位为0时,我们能通过prev_size字段来获取上一个chunk的大小以及地址。这也方便进行空闲chunk之间的合并。
  • fdbkchunk处于分配状态时,从fd字段开始是用户的数据。chunk空闲时,会被添加到对应的空闲管理链表中,其字段的含义如下
    • fd指向下一个(非物理相邻)空闲的chunk
    • bk指向上一个(非物理相邻)空闲的chunk

通过fdbk可以将空闲的chunk块加入到空闲的chunk块链表进行统一管理。

  • fd_nextsizebk_nextsize,也是只有chunk空闲的时候才使用,不过其用于较大的chunk(large chunk)。
    • fd_nextsize:指向前一个与当前chunk大小不同的第一个空闲块,不包含bin的头指针。
    • bk_nextsize:指向后一个与当前chunk大小不同的第一个空闲块,不包含bin的头指针。
    • 一般空闲的large chunkfd的遍历顺序中,按照由大到小的顺序排列。这样做可以避免在寻找合适chunk时挨个遍历。

chunk的结构

bins

用户释放掉的chunk不会马上归还给系统,ptmalloc 会统一管理heapmmap映射区域中的空闲的chunk。当用户再一次请求分配内存时,ptmalloc 分配器会试图在空闲的chunk中挑选一块合适的给用户。这也可以避免频繁的系统调用,降低内存分配的开销。

在具体的实现中,ptmalloc 采用分箱式方法对空闲的chunk进行管理。首先,它会根据空闲的chunk的大小以及使用状态将chunk初步分为4类:fast binssmall binslarge binsunsorted bin。对于 libc2.27 及以上版本还有tcache

概述

对于small binslarge binsunsorted bin来说,ptmalloc 将它们维护在一个bins数组中。这些bin对应的数据结构在malloc_state中,如下:

1
2
3
#define NBINS 128
/* Normal bins packed as described above */
mchunkptr bins[NBINS * 2 - 2];

bins数组实际上可以看做是以chunk为单位,只不过采用空间复用策略,因为实际用到的只有fdbk

1
2
3
4
/* 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指针维护一个bin(即fdbk),其结构如下图所示(64位)。其中 small binschunk大小已给出。large bins的每个bin中的chunk大小在一个范围内。


lareg binchunk范围如下:

大于0x400chunk就会放到large bin中。

对于fast bin,在malloc_state又单独定义了一个fastbinsY的结构维护。

1
2
3
4
5
6
typedef struct malloc_chunk *mfastbinptr;

/*
/* Fastbins */
mfastbinptr fastbinY[ NFASBINS ];
*/

由于fast bin为单链表结构,因此数组中第一个指针就可以维护一个bin。结构如图所示:

Fast Bin

为了避免大部分时间花在了合并、分割以及中间检查的过程中影响效率,因此 ptmalloc 中专门设计了fast bin

fast bin采用单链表形式,结构如下图所示:

fast bin有如下性质:

  • 由于采用单链表结构,fast bin采取 LIFO(后进先出)策略。
  • 每个fast bin中维护的chunk大小确定,并且fast bin维护的最大的chunk为 128 字节(64位),因此不超过 0x80(chunk大小)的内存释放会进入fast bin
  • fast bin范围的chunk下一个相邻chunkPREV_INUSE始终被置为1。因此它们不会和其它被释放的chunk合并。除非调用malloc_consolidate函数。

安全检查:

  • size:在malloc()函数分配fastbin size范围的chunk时,若是对应的fastbin中有空闲chunk,在取出前会检查其size域与对应下标是否一致,不会检查标志位,若否便会触发abort
  • double free: 在free()函数中会对fast bin链表的头结点进行检查,若将要被放入fast bin中的chunk与对应下标的链表的头结点为同一chunk,则会触发abort
  • Safe linking 机制(only glibc2.32 and up):自 glibc 2.32 起引入了 safe-linking 机制,其核心思想是在链表上的chunk中并不直接存放其所连接的下一个chunk的地址,而是存放下一个chunk的地址与【fd指针自身地址右移 12 位】所异或得的值,使得攻击者在得知chunk的地址之前无法直接利用其构造任意地址写。
1
2
3
#define PROTECT_PTR(pos, ptr) \
  ((__typeof (ptr)) ((((size_t) pos) >> 12) ^ ((size_t) ptr)))
#define REVEAL_PTR(ptr)  PROTECT_PTR (&ptr, ptr)

需要注意的是fastbin的入口节点存放的仍是未经异或的chunk地址。

另外第一个加入fast binchunkfd字段可以泄露堆地址(右移 12 位)。

Small Bin

small bin采用双向链表,结构如下图所示。

small bin有如下性质:

  • small bins中每个bin对应的链表采用 FIFO 的规则。
  • 每个small bin维护的chunk大小确定,并且small bin维护的最大的chunk为1008字节(64位),即 0x3f0 的chunk大小。

Large Bin

large bins中一共包括64个bin,每个bin中的chunk的大小不一致,而是处于一定区间范围内。large bin的结构如下:

关于fd_nextsizebk_nextsize的机制,这里以fd_nextsize为例:

  • fd_nextsizebk_nextsizebins数组没有连接关系(这就解释了为什么bins上没有体现fd_nextsizebk_nextsize结构)。
  • large bin里的chunkfd指针指向的方向上按照chunk大小降序排序。
  • large bin里有一个chunk时,fd_nextsizebk_nextsize指向自己(如上面large bin的结构图所示)。
  • large bin里同一个大小的chunk有多个时,只有相同大小chunk中的第一个的fd_nextsizebk_nextsize指针有效,其余的chunkfd_nextsizebk_nextsize设为NULL

  • large bin中有多个不同大小的chunkfd_nextsize连接比它小的第一个chunkbk_nextsize就是把fd_nextsize反过来连到对应结构上。

  • large bin最小的一组 chunk 中的第一个 chunk 的 fd_nextsize 连接的是最大的 chunk,最大的 chunk 的 bk_nextsize 相反。

Unsorted Bin

unsorted bin可以视为空闲chunk回归其所属bin之前的缓冲区。像small bin一样采用双向链表维护。chunk大小乱序。

Top chunk

程序第一次进行malloc的时候,heap会被分为两块,一块给用户,剩下的那块就是top chunk。其实,所谓的top chunk就是处于当前堆的物理地址最高的chunk。这个chunk不属于任何一个bin,它的作用在于当所有的bin都无法满足用户请求的大小时,如果其大小不小于指定的大小,就进行分配,并将剩下的部分作为新的top chunk。否则,就对heap进行扩展后再进行分配。在main_arena中通过sbrk扩展heap,而在threadarena中通过mmap分配新的heap。

需要注意的是,top chunkprev_inuse比特位始终为1,否则其前面的chunk就会被合并到top chunk中。

last remainder

在用户使用malloc请求分配内存时,ptmalloc2 找到的chunk可能并不和申请的内存大小一致,这时候就将剩余部分称之为last remainder chunkunsortbin也会存这一块。top chunk分割剩下的部分不会作为last_remainder

threads arena

  • 每个进程只有一个主分配区(main_arena),可能存在多个非主分配区(non_main_arena):

 - x86: 2*number of cores + 1

 - x64: 8*number of cores + 1

  • main_arena使用brkmmap申请虚拟内存

    • mmap分配的内存一律通过munmap返还
    • 只有一个heap
  • non_main_arena只能用mmap

    • 每次用mmap向系统批发HEAP_MAX_SIZE(32位1MB,64位64MB),对象请求时再零售
    • 当前heap不够用,mmap再次申请heap,所以可以包含多个heaps

分配策略

假设有如下情境:一台只含有一个处理器核心的PC机安装有32位操作系统,其上运行了一个多线程应用程序,共含有4个线程——主线程和三个用户线程。显然线程个数大于系统能维护的最大arena个数(2* 核心数 + 1= 3),那么此时glibc malloc就需要确保这4个线程能够正确地共享这3个arena,那么它是如何实现的呢?

当主线程首次调用malloc的时候,glibc malloc会直接为它分配一个main arena,而不需要任何附加条件。

当用户线程1和用户线程2首次调用malloc的时候,glibc malloc会分别为每个用户线程创建一个新的thread arena。此时,各个线程与arena是一一对应的。但是,当用户线程3调用malloc的时候,就出现问题了。因为此时glibc malloc能维护的arena个数已经达到上限,无法再为线程3分配新的arena了,那么就需要重复使用已经分配好的3个arena中的一个(main arena, arena 1或者arena 2)。那么该选择哪个arena进行重复利用呢?

1)首先,glibc malloc循环遍历所有可用的arenas,在遍历的过程中,它会尝试lock该arena。如果成功lock(该arena当前对应的线程并未使用堆内存则表示可lock),比如将main arena成功lock住,那么就将main arena返回给用户,即表示该arena被线程3共享使用。

2)而如果没能找到可用的arena,那么就将线程3的malloc操作阻塞,直到有可用的arena为止。

3)现在,如果线程3再次调用malloc的话,glibc malloc就会先尝试使用最近访问的arena(此时为main arena)。如果此时main arena可用的话,就直接使用,否则就将线程3阻塞,直到main arena再次可用为止。

这样线程3与主线程就共享main arena了。至于其他更复杂的情况,以此类推。

tcache机制

tcache 是 glibc 2.26 (ubuntu 17.10) 之后引入的一种技术,目的是提升堆管理的性能,与fast bin类似。tcache 引入了两个新的结构体,tcache_entry 和 tcache_perthread_struct 。

tcache_entry 定义如下:

1
2
3
4
typedef struct tcache_entry
{
  struct tcache_entry *next;
} tcache_entry;

tcache_entry 用于链接空闲的chunk结构体,其中的next指针指向下一个大小相同的chunk。需要注意的是这里的next指向chunkuser data,而fast binfd指向chunk开头的地址。而且,tcache_entry 会复用空闲 chunk 的user data部分。

tcache_perthread_struct 定义如下:

1
2
3
4
5
6
7
8
9
typedef struct tcache_perthread_struct
{
  char counts[TCACHE_MAX_BINS];
  tcache_entry *entries[TCACHE_MAX_BINS];
} tcache_perthread_struct;

# define TCACHE_MAX_BINS                64

static __thread tcache_perthread_struct *tcache = NULL;

对应结构如下


每个thread都会维护一个 tcache_perthread_struct ,它是整个 tcache 的管理结构,一共有 TCACHE_MAX_BINS 个计数器和 TCACHE_MAX_BINS 项 tcache_entry。这个结构在 tcache_init 函数中被初始化在堆上,大小为 0x250(高版本为 0x290)。其中数据部分前 0x40 为 counts ,剩下的为 entries 结构。如果能控制这个堆块就可以控制整个 tcache 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
static void
tcache_init(void)
{
  mstate ar_ptr;
  void *victim = 0;
  const size_t bytes = sizeof (tcache_perthread_struct);
  
  if (tcache_shutting_down)
    return;

  arena_get (ar_ptr, bytes);
  victim = _int_malloc (ar_ptr, bytes);
  if (!victim && ar_ptr != NULL)
    {
      ar_ptr = arena_get_retry (ar_ptr, bytes);
      victim = _int_malloc (ar_ptr, bytes);
    }
    
  if (ar_ptr != NULL)
    __libc_lock_unlock (ar_ptr->mutex);

  /* In a low memory situation, we may not be able to allocate memory
     - in which case, we just keep trying later.  However, we
     typically do this very early, so either there is sufficient
     memory, or there isn't enough memory to do non-trivial
     allocations anyway.  */

  if (victim)
    {
      tcache = (tcache_perthread_struct *) victim;
      memset (tcache, 0sizeof (tcache_perthread_struct));
    }
}

tcache_perthread_struct 中的 tcache_entry 用单向链表的方式链接了相同大小的处于空闲状态(free 后)的 chunk,这一点上和 fast bin 很像。

另外与fast bin相同的是释放进入 tcache 的 chunk 的下一个相邻 chunk 的 PREV_INUSE 位不清零。

counts 记录了 tcache_entry 链上空闲 chunk 的数目,每条链上最多可以有 7 个 chunk 。注意指针指向的位置是 fd 指针,这一点与fast bin不同。
结构如下:

stash 机制:

当申请的大小在 tcache 范围的 chunk 在 tcache 中没有,此时 ptmalloc 会在其他 bin 里面找,如果找到了会将该 chunk 放到 tcache 中,直到 tcache 填满,最后直接返回找到的 chunk 或是从 tcache 中取出并返回。

安全检查:

  • tcache key(only libc2.29 and up):自 glibc2.29 版本起 tcache 新增了一个 key 字段,该字段位于 chunk 的 bk 字段,值为 tcache 结构体的地址,若 free() 检测到 chunk->bk == tcache 则会遍历 tcache 查找对应链表中是否有该 chunk
    最新版本的一些老 glibc (如新版2.27等)也引入了该防护机制
  • Safe linking 机制(only glibc2.32 and up):与fast bin类似。
    绕过方法:
    • 在 tcache 的一个 entry 中放入第一个 chunk 时,其同样会对该 entry 中的 “chunk” (NULL)进行异或运算后写入到将放入 tcache 中的 chunk 的 fd 字段,若是我们能够打印该 free chunk 的 fd 字段,便能够直接获得未经异或运算的堆上相关地址(右移 12 位)
    • 在 tcache->entry 中存放的仍是未经加密过的地址,若是我们能够控制 tcache 管理器则仍可以在不知道堆相关地址时进行任意地址写。

分配过程

  • 我们调用malloc函数分配一块内存给一个指针时,实际上我们是调用了_libc_malloc函数。
  • 首先会在_libc_malloc函数中判断__malloc_hook函数指针是否为空,如果不为空则调用__malloc_hook函数指针(glibc2.34已删除)。
  • 如果glibc存在tcache且有相应大小的chunk则将其从tcache中取出并返回结果。
  • 如果没有tcache或其中不存在相应的chunk,则调用_int_malloc函数申请内存。
    • 首先把申请的内存的字节数转化为chunk的大小。
    • 如果arena未初始化,则调用sysmalloc向系统申请内存,然后将获取的chunk返回。
    • 如果申请的chunk大小不超过fast bin的最大值,则尝试从对应的fast bin的头部获取chunk。在获取到chunk后,如果对应的fast bin还有chunk并且大小在tcache范围就将它们依次从头结点取出放到tcache中,直到把tcache填满。最后将申请到的chunk返回。
    • 如果申请的chunklarge bin大小范围则调用malloc_consolidate函数将fast bin中的chunk合并后放入 unsorted bin
    • 循环进行如下操作:
      • 循环取unsorted bin最后一个chunk
        • 如果用户的请求为small bin chunk,那么我们首先考虑last remainder,如果当前chunklast remainder,且last remainderunsorted bin中的唯一一个chunk,并且last remainder的大小分割后还可以作为一个chunk,则从last remainder中切下一块内存返回。
        • 如果chunk的大小恰好等于申请的chunk大小,则如果该内存大小在 tcache 范围且 tcache 没有满,则先将其放入tcache,之后会考虑从tcache中找chunk。否则直接将找到的chunk返回。
        • 根据chunk的大小将其放入small binlarge bin中。对于small bin直接从链表头部加入;对于 large bin,首先特判加入链表尾部的情况,如果不在链表尾部则从头部遍历找位置,如果large bin中有与加入的chunk大小相同的chunk,则加入到第一个相等chunk后面,否则加到合适位置后还需要更新 nextsize 指针。
        • 尝试从tcachechunk
        • 如果循环超过 10000 次就跳出循环。
      • 尝试从tcachechunk
      • 如果申请chunk大小不在small bin范围,则从后往前遍历对应large bin,找到第一个不小于申请 chunk 大小的 chunk。为了 unlink 时避免修改 nextsize 的操作,如果存在多个合适的 chunk 则选择第二个 chunk。如果选取的 chunk 比申请的 chunk 大不少于 MINSIZE,则需要将多出来的部分切出来作为 remainder,并将其加入unsorted bin头部。然后将获取的chunk返回。
      • 找一个chunk范围比申请chunk大的非空bin里面找最后一个chunk,这个过程用 binmap 优化,同时也可以更新 binmap 的状态。这个chunk上切下所需的chunk,剩余部分放入unsorted bin头部。然后将获取的chunk返回。
      • 如果top chunk切下所需chunk后剩余部分还是不小于 MINSIZE 则从top chunk上切下所需 chunk 返回。
      • 如果fast bins还有chunk则调用malloc_consolidate合并fast bin中的chunk并放入unsorted bin中,然后继续循环。
      • 如果fast bins还有chunk则调用malloc_consolidate合并fast bin中的chunk并放入 unsorted bin中,然后继续循环。
      • 最后sysmalloc系统调用向操作系统申请内存分配chunk
        • 如果arena没有初始化或者申请的内存大于mp_.mmap_threshold,并且mmap的次数小于最大值,则使用mmap申请内存。然后检查一下是否16字节对齐然后更新mmap次数和mmap申请过的最大内存大小后就将chunk返回。
        • 如果arena没有初始化就返回0。
        • 对之前的top chunk进行检查,如果是 dummy top的话,因为是用unsorted bin表示的,因此 top chunk 的大小需要是0。否则堆的大小应该不小于 MINSIZE ,并且前一个堆块一个处于使用中,并且堆的结束地址应该是页对齐的,由于页对齐的大小默认是 0x1000,所以低 12 个比特需要为0.初次之外,top chunk大小必须比申请chunk大小加上 MINSIZE 要小。
        • 如果arena不是main_arena
          • 尝试将top chunk所在的heap扩展大小,如果成功则更新arena记录的内存总大小 system_memtop chunk大小。
          • 尝试申请一个新的heap。设置新的heap以及arena的参数并且将原来的top chunk先从尾部切下 2 个0x10大小的chunk,剩余部分如果不小于 MINSIZE 则将其释放掉。
          • 否则,如果前面没有执行到mmap申请chunk的分支就尝试执行。
        • 如果arenamain arena
          • 计算需要获取的内存大小。需要获取的内存大小等于申请的chunk大小加上0x20000和MINSIZE。如果堆空间连续,则可以再减去原来内存的大小。然后将需要获取的内存大小与页对齐。
          • sbrk扩展内存如果成功则会尝试调用一个 hook 函数,否则mmap申请内存,然后brk移到申请的内存处并设置堆不连续参数。
          • 如果成功获取到内存,则更新arena记录的内存总大小system_memsbrk_base。之后对一系列的情况进行处理,在这期间,之前的top chunk会被从尾部切下两个 0x10 大小的chunk,剩余部分如果不小于MINSIZE则将其释放掉。
        • 最后从新获取的top chunk切下所需的chunk并返回。

malloc源码分析

如果前一个 chunk 处于使用状态,那么不需要去通过链表串起来,所以当前 chunk 也就不需要 prev_size,当申请的内存大小对 2 * size_t 取余之后比 size_t 小于等于的话就可以用它的下一个 chunk 的 prev_size

还是 64 位下,如果大小是 0x49 的话取余之后还差 0x5,那就没法用了,只能多申请一块,最后加上 chunk header 用了 0x60

内存对齐

机器类型 64位 32位
对齐位数 16 8
size_t 8 4

__libc_malloc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
void *
__libc_malloc (size_t bytes)
{
//定义了一个指向arena的指针,用于内存分配
mstate ar_ptr;

//用于存储最终返回的分配内存地址
void *victim;

//检查自定义malloc钩子
void *(*hook) (size_t, const void *)
= atomic_forced_read (__malloc_hook);
if (__builtin_expect (hook != NULL, 0))
return (*hook)(bytes, RETURN_ADDRESS (0));

//获取本线程对应的arena,即malloc_state结构体
arena_get (ar_ptr, bytes);

//调用_int_malloc申请内存
//参数ar_ptr指向arena,参数bytes为申请的字节数
victim = _int_malloc (ar_ptr, bytes);

//若在当前arena中分配失败,且ar_ptr非空,会调用arena_get_retry获取其他arena进行重试。
if (!victim && ar_ptr != NULL)
{
LIBC_PROBE (memory_malloc_retry, 1, bytes);
ar_ptr = arena_get_retry (ar_ptr, bytes);
victim = _int_malloc (ar_ptr, bytes);
}

//在完成分配或失败后,释放该arena的互斥锁,确保其它线程可以访问此arena
if (ar_ptr != NULL)
(void) mutex_unlock (&ar_ptr->mutex);

//通过条件验证分配,不满足则直接崩溃
//1.是否没有申请成功内存返回是空
//2.该chunk是否是通过mmap分配的内存块
//3.判断arean指针和mainarena是否匹配
assert (!victim || chunk_is_mmapped (mem2chunk (victim)) ||
ar_ptr == arena_for_chunk (mem2chunk (victim)));
//返回申请到的内存
return victim;
}

_int_malloc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
static void *
_int_malloc (mstate av, size_t bytes)
{
INTERNAL_SIZE_T nb; /* 请求的chunk_size */
unsigned int idx; /* 对应bin数组中的index */
mbinptr bin; /* 指向对应bin的指针 */

mchunkptr victim; /* 指向分配的chunk */
INTERNAL_SIZE_T size; /* 分配的chunk的size */
int victim_index; /* 分配的chunk的bin的index */

mchunkptr remainder; /* 指向分割后剩下的那块chunk */
unsigned long remainder_size; /* 分割后剩下的那块chunk的size */

unsigned int block; /* bit map traverse */
unsigned int bit; /* bit map traverser */
unsigned int map; /* 一个block值 */

mchunkptr fwd; /* 用于链表操作 */
mchunkptr bck; /* 用于链表操作 */

const char *errstr = NULL; //报错字符串指针

checked_request2size (bytes, nb); //将申请内存的字节数转换为合适的chunk size存入nb

//检查是否有可用的arena
if (__glibc_unlikely (av == NULL))
{
//无可用的arena,使用sysmalloc获取内存
void *p = sysmalloc (nb, av);
if (p != NULL)
//内存分配成功则对内存进行扰动处理,即填充内存特定字节
alloc_perturb (p, bytes);
return p;
}

//要分配的chunk size小于等于global_max_fast则先从fastbin中寻找
if ((unsigned long) (nb) <= (unsigned long) (get_max_fast ()))//get_max_fast用于获取global_max_fast
{
//通过size计算其在fastbin中对应的索引然后存入idx
idx = fastbin_index (nb);
//通过idx获取arena的fastbin中对应的bin
mfastbinptr *fb = &fastbin (av, idx);
//获取bin的首个chunk
mchunkptr pp = *fb;

//这段代码在并发环境下,通过循环和原子操作安全地从自由链表中取出一个空闲块。它使用 catomic_compare_and_exchange_val_acq 来确保无锁访问的原子性,使得多个线程能够同时操作同一个自由链表而不会出现数据竞争。
do
{//将当前链表的头节点赋值给victim
victim = pp;
//如果链表为空跳出循环
if (victim == NULL)
break;
}
//使用CAS操作确保线程安全,如果fastbin的头指针未被其它线程修改,将下一个块的地址设置为链表新的头节点
while ((pp = catomic_compare_and_exchange_val_acq (fb, victim->fd, victim))
!= victim);


//此时victim是该fb原来的首个chunk,或者为0
if (victim != 0)
{
//如果victim的大小不符合当前fastbin的要求,则进行错误处理
if (__builtin_expect (fastbin_index (chunksize (victim)) != idx, 0))
{
//错误处理
errstr = "malloc(): memory corruption (fast)";
errout:
malloc_printerr (check_action, errstr, chunk2mem (victim), av);
return NULL;
}
//检查victim块是否被重复分配或被修改,确保内存安全性。
check_remalloced_chunk (av, victim, nb);
//将chunk块指针转换为user data的指针。
//#define chunk2mem(p) ((void*)((char*)(p) + 2*SIZE_SZ))
void *p = chunk2mem (victim);
//用于初始化内存块p,将其中的数据用固定字节填充。
alloc_perturb (p, bytes);
//将分配出来的内存指针返回
return p;
}
}

//如果前面在fastbin中没有找到就会从smallbin中查找
//如果size小于largebin中最小的size那么就从smallbin中查找
/*#define in_smallbin_range(sz) \
((unsigned long) (sz) < (unsigned long) MIN_LARGE_SIZE)*/

//#define MIN_LARGE_SIZE ((NSMALLBINS - SMALLBIN_CORRECTION) * SMALLBIN_WIDTH)
//#define NSMALLBINS 64
//#define SMALLBIN_WIDTH MALLOC_ALIGNMENT
//#define SMALLBIN_CORRECTION (MALLOC_ALIGNMENT > 2 * SIZE_SZ)
//MALLOC_ALIGNMENT (2 *SIZE_SZ),32位为8字节,64位为16字节
//min_large_size为0x400

//该宏返回一个布尔值,如果sz小于min_large_size,则返回true
if (in_smallbin_range (nb))
{

/*smallbin_index(sz)
((SMALLBIN_WIDTH == 16 ? (((unsigned) (sz)) >> 4) : (((unsigned) (sz)) >> 3))
+ SMALLBIN_CORRECTION)*/

//根据size找到相应在smallbin中的下标
idx = smallbin_index (nb);
/*
bin_at(m, i)
(mbinptr) (((char *) &((m)->bins[((i) - 1) * 2]))
- offsetof (struct malloc_chunk, fd))
*/
//m->bins,获取bins数组
//((i)-1)*2,索引i表示bin的编号
//&((m)->bins[((i)-1)*2],计算出目标bin在bins数组中的地址
//#define offsetof(TYPE, MEMBER) __builtin_offsetof (TYPE, MEMBER)
//offsetof宏计算fd成员在malloc_chunk中的偏移量
//从bins的地址减去fd的偏移量,得到一个完整的chunk结构的基地址

//根据下标将这个bin取出来
bin = bin_at (av, idx);

//last(b) ((b)->bk
//last获取当前bk指向的值,如果不等于bin则smallbin不为空
//一般向bin中取出块用bk,添加块用fd
if ((victim = last (bin)) != bin)
{

if (victim == 0)

//victim为0,表示smallbin还没有初始化为双向循环链表,调用malloc_consolidate函数,此时由于global_max_fast也未初始化,所以会调用malloc_init_state初始化
malloc_consolidate (av);
else
{
bck = victim->bk;
//双向链表检测,last(bin)->bk->fd == last(bin)
//检查下一个chunk的bk指向的上一个chunk是否指向bin
if (__glibc_unlikely (bck->fd != victim))
{

errstr = "malloc(): smallbin double linked list corrupted";
goto errout;
}
//设置inuse标志
set_inuse_bit_at_offset (victim, nb);
//将victim从smallbin的双向循环链表中取出
bin->bk = bck;
bck->fd = bin;

//判断是否指向main_arena
if (av != &main_arena)
//如果不指向,则将non_main_arena置为1
victim->size |= NON_MAIN_ARENA;
//检查victim所指向的已分配内存块的完整性和合法性,确保分配的内存块符合预期的大小和对齐要求
check_malloced_chunk (av, victim, nb);
//将指向chunk头部的指针指向user data
void *p = chunk2mem (victim);
//用于将分配的内存区域填充特定模式的数据,以便在调试和诊断中帮助检测未初始化内存使用等问题
alloc_perturb (p, bytes);
return p;
}
}
}

else
{
//如果smallbin没有就从largebin取
idx = largebin_index (nb);
//先查看是否存在fastbin
if (have_fastchunks (av))
malloc_consolidate (av);
}


for (;; )
{
int iters = 0;
while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
{
bck = victim->bk;
if (__builtin_expect (victim->size <= 2 * SIZE_SZ, 0)
|| __builtin_expect (victim->size > av->system_mem, 0))
malloc_printerr (check_action, "malloc(): memory corruption",
chunk2mem (victim), av);
size = chunksize (victim);


if (in_smallbin_range (nb) &&
bck == unsorted_chunks (av) &&
victim == av->last_remainder &&
(unsigned long) (size) > (unsigned long) (nb + MINSIZE))
{
/* split and reattach remainder */
remainder_size = size - nb;
remainder = chunk_at_offset (victim, nb);
unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder;
av->last_remainder = remainder;
remainder->bk = remainder->fd = unsorted_chunks (av);
if (!in_smallbin_range (remainder_size))
{
remainder->fd_nextsize = NULL;
remainder->bk_nextsize = NULL;
}

set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);
set_foot (remainder, remainder_size);

check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}

/* remove from unsorted list */
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);

/* Take now instead of binning if exact fit */

if (size == nb)
{
set_inuse_bit_at_offset (victim, size);
if (av != &main_arena)
victim->size |= NON_MAIN_ARENA;
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}

/* place chunk in bin */

if (in_smallbin_range (size))
{
victim_index = smallbin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;
}
else
{
victim_index = largebin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;

/* maintain large bins in sorted order */
if (fwd != bck)
{
/* Or with inuse bit to speed comparisons */
size |= PREV_INUSE;
/* if smaller than smallest, bypass loop below */
assert ((bck->bk->size & NON_MAIN_ARENA) == 0);
if ((unsigned long) (size) < (unsigned long) (bck->bk->size))
{
fwd = bck;
bck = bck->bk;

victim->fd_nextsize = fwd->fd;
victim->bk_nextsize = fwd->fd->bk_nextsize;
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
}
else
{
assert ((fwd->size & NON_MAIN_ARENA) == 0);
while ((unsigned long) size < fwd->size)
{
fwd = fwd->fd_nextsize;
assert ((fwd->size & NON_MAIN_ARENA) == 0);
}

if ((unsigned long) size == (unsigned long) fwd->size)
/* Always insert in the second position. */
fwd = fwd->fd;
else
{
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
}
bck = fwd->bk;
}
}
else
victim->fd_nextsize = victim->bk_nextsize = victim;
}

mark_bin (av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;

#define MAX_ITERS 10000
if (++iters >= MAX_ITERS)
break;
}

/*
If a large request, scan through the chunks of current bin in
sorted order to find smallest that fits. Use the skip list for this.
*/

if (!in_smallbin_range (nb))
{
bin = bin_at (av, idx);

/* skip scan if empty or largest chunk is too small */
if ((victim = first (bin)) != bin &&
(unsigned long) (victim->size) >= (unsigned long) (nb))
{
victim = victim->bk_nextsize;
while (((unsigned long) (size = chunksize (victim)) <
(unsigned long) (nb)))
victim = victim->bk_nextsize;

/* Avoid removing the first entry for a size so that the skip
list does not have to be rerouted. */
if (victim != last (bin) && victim->size == victim->fd->size)
victim = victim->fd;

remainder_size = size - nb;
unlink (av, victim, bck, fwd);

/* Exhaust */
if (remainder_size < MINSIZE)
{
set_inuse_bit_at_offset (victim, size);
if (av != &main_arena)
victim->size |= NON_MAIN_ARENA;
}
/* Split */
else
{
remainder = chunk_at_offset (victim, nb);
/* We cannot assume the unsorted list is empty and therefore
have to perform a complete insert here. */
bck = unsorted_chunks (av);
fwd = bck->fd;
if (__glibc_unlikely (fwd->bk != bck))
{
errstr = "malloc(): corrupted unsorted chunks";
goto errout;
}
remainder->bk = bck;
remainder->fd = fwd;
bck->fd = remainder;
fwd->bk = remainder;
if (!in_smallbin_range (remainder_size))
{
remainder->fd_nextsize = NULL;
remainder->bk_nextsize = NULL;
}
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);
set_foot (remainder, remainder_size);
}
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}

malloc_consolidate

  1. 首先判断当前 malloc_state 结构体中的 fastbin 是否为空,如果为空就说明整个 malloc_state 都没有完成初始化,需要对malloc_state 进行初始化。
  2. malloc_state 的初始化操作由函数 malloc_init_state(av) 完成,该函数先初始化除 fastbins 之外的所有的bins,再初始化 fastbins,清空 fastbins
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
static void malloc_consolidate(mstate av)
{
mfastbinptr* fb; /* current fastbin being consolidated */
mfastbinptr* maxfb; /* last fastbin (for loop control) */
mchunkptr p; /* current chunk being consolidated */
mchunkptr nextp; /* next chunk to consolidate */
mchunkptr unsorted_bin; /* bin header */
mchunkptr first_unsorted; /* chunk to link to */

/* These have same use as in free() */
mchunkptr nextchunk;
INTERNAL_SIZE_T size;
INTERNAL_SIZE_T nextsize;
INTERNAL_SIZE_T prevsize;
int nextinuse;
mchunkptr bck;
mchunkptr fwd;

if (get_max_fast () != 0) {
//global_max_fast不为0,表示ptmalloc已经初始化,清除分配区flag中fastbin的标志位

/*#define clear_fastchunks(M) \
catomic_or (&(M)->flags, FASTCHUNKS_BIT)*/
//#define FASTCHUNKS_BIT (1U)
//catomic_or是一个原子操作,用于对地址上的值进行按位或运算
//结果flags被置为1
clear_fastchunks(av);

//unsorted_chunks(M) (bin_at (M, 1))

/*#define bin_at(m, i) \
(mbinptr) (((char *) &((m)->bins[((i) - 1) * 2])) \
- offsetof (struct malloc_chunk, fd))*/
//每个bin占用两个指针,所以乘以2
//获取arena对应的unsortedbin的第一个第一个bin

unsorted_bin = unsorted_chunks(av);

//#define fastbin(ar_ptr, idx) ((ar_ptr)->fastbinsY[idx])

//maxfb是指向fastbin中最后一个bin的指针
maxfb = &fastbin (av, NFASTBINS - 1);
//获取第一个bin的地址
fb = &fastbin (av, 0);
do {
//读取fb的当前值
//将fb设置为0
//返回之前的链表头地址
p = atomic_exchange_acq (fb, 0);


if (p != 0) {
do {
//如果prev_inuse为0则报错
check_inuse_chunk(av, p);
//将nextp赋值为下一个bin
nextp = p->fd;
//PREV_INUSE为0x1,NON_MAIN_ARENA为0x4
//通过按位与去除PREV_INUSE位和NON_MAIN_ARENA位
size = p->size & ~(PREV_INUSE|NON_MAIN_ARENA);

//#define chunk_at_offset(p, s) ((mchunkptr) (((char *) (p)) + (s)))
//通过当前chunk的地址和大小计算出下一个chunk的地址
nextchunk = chunk_at_offset(p, size);
//#define chunksize(p) ((p)->size & ~(SIZE_BITS))
//#define SIZE_BITS (PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
//获取nextchunk的实际内存大小
nextsize = chunksize(nextchunk);

//prev_inuse(p) ((p)->size & PREV_INUSE),确认内存块是否空闲

//如果p指向的chunk空闲,即prev_inuse为0
if (!prev_inuse(p)) {

prevsize = p->prev_size;//获取前一个chunk的大小
size += prevsize; //更新当前chunk的大小,增加前一个chunk的大小
p = chunk_at_offset(p, -((long) prevsize));//获取上一个chunk的地址,然后赋值给p

/*
#define unlink(AV, P, BK, FD) { \
FD = P->fd; \
BK = P->bk; \
//FD为要移除块的fd指针,BK为要移除块的bk指针

__builtin_expect是GCC提供的一个优化提示,告诉编译器在常见情况下链表是正确的,而不常见的情况下链表会损害
FD的bk指针如果不等于当前块或BK的fd指针不等于当前块则链表损害
通过malloc_printerr输出错误信息

if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \
malloc_printerr (check_action, "corrupted double-linked list", P, AV); \

如果链表没有损害,继续执行移除操作
else { \
将FD的前向指针指向BK,然后将BK的后向指向指向FD
FD->bk = BK; \
BK->fd = FD; \

//判断当前块p的大小是否在小块堆的范围内。
if (!in_smallbin_range (P->size) \
&& __builtin_expect (P->fd_nextsize != NULL, 0)) { \
//优化指令,告诉编译器这个条件很少发生
//检查检查当前块在双向链表中是否完整
if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0) \
|| __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0)) \
malloc_printerr (check_action, \
"corrupted double-linked list (not small)", \
P, AV); \

判断FD的fd_nextsize是否为空,表示FD是否是当前链表的尾部
if (FD->fd_nextsize == NULL) { \
如果是尾部,而且P的fd_nextsize判定为P,则将FD作为链表的唯一元素
if (P->fd_nextsize == P) \
FD->fd_nextsize = FD->bk_nextsize = FD; \
else { \
确保双向链接的完整性
FD->fd_nextsize = P->fd_nextsize; \
FD->bk_nextsize = P->bk_nextsize; \
P->fd_nextsize->bk_nextsize = FD; \
P->bk_nextsize->fd_nextsize = FD; \
} \
如果FD->nextsize不为空,意味着FD已经位于链表中。
在这种情况下,需要更新P的前后链接
} else { \
P->fd_nextsize->bk_nextsize = P->bk_nextsize; \
P->bk_nextsize->fd_nextsize = P->fd_nextsize; \
} \
} \
} \
}
*/

//av即当前的arena
//p为要移除的块
unlink(av, p, bck, fwd);//将前一个chunk从链表中移除
}

//如果nextchunk和top chunk不相同,则说明当前的内存块不是堆中的顶端块
if (nextchunk != av->top) {
/*#define inuse_bit_at_offset(p, s) \
(((mchunkptr) (((char *) (p)) + (s)))->size & PREV_INUSE)*/
//检查nextchunk是否为已分配,如果已分配则nextinuse为1
nextinuse = inuse_bit_at_offset(nextchunk, nextsize);

//如果nextinuse为未分配则执行
if (!nextinuse) {
//将nextchunk的nextsize追加到当前块p的大小size上,更新合并后块的大小
size += nextsize;
//将nextchunk移除
unlink(av, nextchunk, bck, fwd);
} else
//如果nextchunk已分配,则清除nextchunk的prev_inuse位
clear_inuse_bit_at_offset(nextchunk, 0);

//将unsortedbin的第一个指针存入first_unsorted中
first_unsorted = unsorted_bin->fd;
//将当前块p插入到unsortedbin的开头
unsorted_bin->fd = p;
//将原先的第一个未排序块的后向指针指向当前块p,完成双向链表的更新
first_unsorted->bk = p;

if (!in_smallbin_range (size)) {
p->fd_nextsize = NULL;
p->bk_nextsize = NULL;
}

set_head(p, size | PREV_INUSE);
p->bk = unsorted_bin;
p->fd = first_unsorted;
set_foot(p, size);
}
else {
size += nextsize;
set_head(p, size | PREV_INUSE);
av->top = p;
}
} while ( (p = nextp) != 0);
//直到遍历完当前fastbin中的所有空闲chunk
}
} while (fb++ != maxfb);
//直到遍历完所有的fastbin
}
else {
//如果ptmalloc没有初始化,初始化ptmalloc
malloc_init_state(av);
check_malloc_state(av);
}
}

__libc_free (void *mem)
{
mstate ar_ptr;
mchunkptr p; /* chunk corresponding to mem */

void (*hook) (void *, const void *)
= atomic_forced_read (__free_hook);
if (__builtin_expect (hook != NULL, 0))
{
(*hook)(mem, RETURN_ADDRESS (0));
return;
}

if (mem == 0) /* free(0) has no effect */
return;

p = mem2chunk (mem);

if (chunk_is_mmapped (p)) /* release mmapped memory. */
{
//如果当前的free是chunk通过mmap分配的,调用munmap_chunk函数
if (!mp_.no_dyn_threshold
&& p->size > mp_.mmap_threshold
&& p->size <= DEFAULT_MMAP_THRESHOLD_MAX)
{
mp_.mmap_threshold = chunksize (p);
mp_.trim_threshold = 2 * mp_.mmap_threshold;
LIBC_PROBE (memory_mallopt_free_dyn_thresholds, 2,
mp_.mmap_threshold, mp_.trim_threshold);
}
munmap_chunk (p);
return;
}

ar_ptr = arena_for_chunk (p);
_int_free (ar_ptr, p, 0);
//不需要对分配区加锁,调用_int_free函数执行实际的释放工作
}

sysmalloc 源码分析

sysmalloc负责向操作系统申请内存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
static void *  
sysmalloc (INTERNAL_SIZE_T nb, mstate av)
{
mchunkptr old_top; //av->top的原始值
INTERNAL_SIZE_T old_size; //它的大小
char *old_end; //它的结束地址

long size; //参数给MORECORE或mmap调用
char *brk; //MORECORE的返回值

long correction; //参数给第二个MORECORE调用
char *snd_brk; //第二个返回值

INTERNAL_SIZE_T front_misalign; /* 表示分配内存时,在起始地址与对齐要求之间多出的不可用字节 */
INTERNAL_SIZE_T end_misalign; /* 表示分配后,在页面结尾部分由于对齐问题而留下的不可用字节 */
char *aligned_brk; /* 执行brk内经过对齐调整后的地址 */

mchunkptr p; /* 用于指向分配器内部的内存块结构 */
mchunkptr remainder; /* 分配内存后剩余的内存块 */
unsigned long remainder_size; /* 表示remainder块的大小 */

//GLRO是一个宏,用于读取全局只读变量。
//dl_pagesize是一个变量,表示系统页面的大小。
size_t pagesize = GLRO (dl_pagesize);
//tried_mmap 表示程序是否使用 mmap 系统调用分配内存
bool tried_mmap = false;

/*
如果使用mmap,并且请求的大小达到mmap阈值,并且系统支持mmap,
并且当前分配的mmap区域数量较少,尝试直接映射这个请求,
而不是扩展top
*/
//当arena为空,或请求内存nb超过阈值(mmap_threshold)且mmap次数未达到限制时,则进入mmap分配
if (av == NULL
|| ((unsigned long) (nb) >= (unsigned long) (mp_.mmap_threshold)
&& (mp_.n_mmaps < mp_.n_mmaps_max)))
{
char *mm; //mmap调用的返回值

try_mmap: //定义一个标签,提供一个跳转位置

/*
将大小上调到最近的页大小。对于mmapped区块,开销比普通区块多一个SIZE_SZ单位,
因为没有后续区块的prev_size字段可用。
*/

//MALLOC_ALIGNMENT是内存分配的对齐单位,通常为16字节
if (MALLOC_ALIGNMENT == 2 * SIZE_SZ)
//对齐到页大小时,只需将用户请求大小nb加上元数据开销SIZE_SZ,并调整为页大小的整数倍
//ALIGN_UP是对齐函数,确保(nb+SIZE_SZ)的大小是pagesize的整数倍
size = ALIGN_UP (nb + SIZE_SZ, pagesize);
else
//如果对齐规则更复杂,需要考虑额外的对齐掩码,MALLOC_ALIGN_MASK
//MALLOC_ALIGN_MASK 的作用是用于处理非双字对齐的需求
size = ALIGN_UP (nb + SIZE_SZ + MALLOC_ALIGN_MASK, pagesize);
//将tried_mmap置为true,表示当前尝试通过mmap分配内存
tried_mmap = true;

//防止内存大小溢出
//将size转化为无符号长整型进行比较,确保size不会溢出为负值
//如果size小于请求大小nb,意味着计算时可能发生了溢出,此时不会继续尝试mmap
if ((unsigned long) (size) > (unsigned long) (nb))
{
//调用mmap分配内存
//0为系统选择映射地址,size调整页大小的分配内存大小
//PROT_READ|PROT_WRITE映射的内存可读可写
//0,不设置特殊标志,默认匿名映射
//如果分配成功返回一个指针,否则返回MAP_FAILED
mm = (char *) (MMAP (0, size, PROT_READ | PROT_WRITE, 0));

//如果内存分配成功则执行
if (mm != MAP_FAILED)
{
/*
                 mmapped区域的开始偏移存储在区块的prev_size字段中。这允许我们在这里
                 和memalign()中调整返回的开始地址以满足对齐要求,并且仍然能够在
                 free()和realloc()中计算出正确的munmap参数地址。
              */
if (MALLOC_ALIGNMENT == 2 * SIZE_SZ)
{
/* 对于glibc,chunk2mem增加地址2*SIZE_SZ,并且MALLOC_ALIGN_MASK是2*SIZE_SZ-1。
                     每个mmap区域都是页面对齐的,因此一定是MALLOC_ALIGN_MASK对齐的。*/ assert (((INTERNAL_SIZE_T) chunk2mem (mm) & MALLOC_ALIGN_MASK) == 0);
front_misalign = 0;
}
else
front_misalign = (INTERNAL_SIZE_T) chunk2mem (mm) & MALLOC_ALIGN_MASK;
if (front_misalign > 0)
{
correction = MALLOC_ALIGNMENT - front_misalign;
p = (mchunkptr) (mm + correction);
p->prev_size = correction;
set_head (p, (size - correction) | IS_MMAPPED);
}
else
{
p = (mchunkptr) mm;
set_head (p, size | IS_MMAPPED);
}

/* 更新统计数据 */

int new = atomic_exchange_and_add (&mp_.n_mmaps, 1) + 1;
atomic_max (&mp_.max_n_mmaps, new);

unsigned long sum;
sum = atomic_exchange_and_add (&mp_.mmapped_mem, size) + size;
atomic_max (&mp_.max_mmapped_mem, sum);

check_chunk (av, p);

//返回chunk
return chunk2mem (p);
}
}
}

/* 如果没有可用的arena,并且mmap也失败了则返回0 */
if (av == NULL)
return 0;

/* 记录进入时top的配置 */
//记录top chunk
old_top = av->top;

old_size = chunksize (old_top);
old_end = (char *) (chunk_at_offset (old_top, old_size));

brk = snd_brk = (char *) (MORECORE_FAILURE);

  /*如果不是第一次通过,我们需要old_size至少是MINSIZE,并且设置了prev_inuse位。*/
assert ((old_top == initial_top (av) && old_size == 0) ||
((unsigned long) (old_size) >= MINSIZE &&
prev_inuse (old_top) &&
((unsigned long) old_end & (pagesize - 1)) == 0));

 /* 前提条件: 当前空间不足以满足nb请求 */
assert ((unsigned long) (old_size) < (unsigned long) (nb + MINSIZE));

// 如果av不是主arena,则尝试扩展当前堆或创建新堆。
if (av != &main_arena)
{
heap_info *old_heap, *heap;
size_t old_heap_size;

  /* 首先尝试扩展当前堆。 */
old_heap = heap_for_ptr (old_top);
old_heap_size = old_heap->size;
if ((long) (MINSIZE + nb - old_size) > 0
&& grow_heap (old_heap, MINSIZE + nb - old_size) == 0)
{
av->system_mem += old_heap->size - old_heap_size;
arena_mem += old_heap->size - old_heap_size;
set_head (old_top, (((char *) old_heap + old_heap->size) - (char *) old_top)
| PREV_INUSE);
}
else if ((heap = new_heap (nb + (MINSIZE + sizeof (*heap)), mp_.top_pad)))
{
/* Use a newly allocated heap. */
heap->ar_ptr = av;
heap->prev = old_heap;
av->system_mem += heap->size;
arena_mem += heap->size;
/* Set up the new top. */
top (av) = chunk_at_offset (heap, sizeof (*heap));
set_head (top (av), (heap->size - sizeof (*heap)) | PREV_INUSE);

/* Setup fencepost and free the old top chunk with a multiple of
MALLOC_ALIGNMENT in size. */ /* The fencepost takes at least MINSIZE bytes, because it might become the top chunk again later. Note that a footer is set up, too, although the chunk is marked in use. */ old_size = (old_size - MINSIZE) & ~MALLOC_ALIGN_MASK;
set_head (chunk_at_offset (old_top, old_size + 2 * SIZE_SZ), 0 | PREV_INUSE);
if (old_size >= MINSIZE)
{
set_head (chunk_at_offset (old_top, old_size), (2 * SIZE_SZ) | PREV_INUSE);
set_foot (chunk_at_offset (old_top, old_size), (2 * SIZE_SZ));
set_head (old_top, old_size | PREV_INUSE | NON_MAIN_ARENA);
_int_free (av, old_top, 1);
}
else
{
set_head (old_top, (old_size + 2 * SIZE_SZ) | PREV_INUSE);
set_foot (old_top, (old_size + 2 * SIZE_SZ));
}
}
else if (!tried_mmap)
/* We can at least try to use to mmap memory. */
goto try_mmap;
}
else /* av == main_arena */


{ /* Request enough space for nb + pad + overhead */
size = nb + mp_.top_pad + MINSIZE;

 /*
         如果是连续的,我们可以减去希望与新空间合并的现有空间。
         我们稍后只在我们实际没有获得连续空间时再加回来。
       */
if (contiguous (av))
size -= old_size;

 /*
         将大小调整为页的倍数。
         如果MORECORE不是连续的,这确保我们只用整页参数调用它。
         并且如果MORECORE是连续的,并且这不是第一次通过,
         这会保持先前调用的页对齐。
       */
size = ALIGN_UP (size, pagesize);

/*
Don't try to call MORECORE if argument is so big as to appear negative. Note that since mmap takes size_t arg, it may succeed below even if we cannot call MORECORE. */
if (size > 0)
{
brk = (char *) (MORECORE (size));
LIBC_PROBE (memory_sbrk_more, 2, brk, size);
}

if (brk != (char *) (MORECORE_FAILURE))
{
/* Call the `morecore' hook if necessary. */
void (*hook) (void) = atomic_forced_read (__after_morecore_hook);
if (__builtin_expect (hook != NULL, 0))
(*hook)();
}
else
{
/*
If have mmap, try using it as a backup when MORECORE fails or cannot be used. This is worth doing on systems that have "holes" in address space, so sbrk cannot extend to give contiguous space, but space is available elsewhere. Note that we ignore mmap max count and threshold limits, since the space will not be used as a segregated mmap region. */
/* Cannot merge with old top, so add its size back in */ if (contiguous (av))
size = ALIGN_UP (size + old_size, pagesize);

/* If we are relying on mmap as backup, then use larger units */
if ((unsigned long) (size) < (unsigned long) (MMAP_AS_MORECORE_SIZE))
size = MMAP_AS_MORECORE_SIZE;

/* Don't try if size wraps around 0 */
if ((unsigned long) (size) > (unsigned long) (nb))
{
char *mbrk = (char *) (MMAP (0, size, PROT_READ | PROT_WRITE, 0));

if (mbrk != MAP_FAILED)
{
/* We do not need, and cannot use, another sbrk call to find end */
brk = mbrk;
snd_brk = brk + size;

/*
Record that we no longer have a contiguous sbrk region. After the first time mmap is used as backup, we do not ever rely on contiguous space since this could incorrectly bridge regions. */ set_noncontiguous (av);
}
}
}

if (brk != (char *) (MORECORE_FAILURE))
{
if (mp_.sbrk_base == 0)
mp_.sbrk_base = brk;
av->system_mem += size;

/*
If MORECORE extends previous space, we can likewise extend top size. */
if (brk == old_end && snd_brk == (char *) (MORECORE_FAILURE))
set_head (old_top, (size + old_size) | PREV_INUSE);

else if (contiguous (av) && old_size && brk < old_end)
{
/* Oops! Someone else killed our space.. Can't touch anything. */
malloc_printerr (3, "break adjusted to free malloc space", brk,
av);
}

/*
Otherwise, make adjustments:
* If the first time through or noncontiguous, we need to call sbrk just to find out where the end of memory lies.
* We need to ensure that all returned chunks from malloc will meet MALLOC_ALIGNMENT
* If there was an intervening foreign sbrk, we need to adjust sbrk request size to account for fact that we will not be able to combine new space with existing space in old_top.
* Almost all systems internally allocate whole pages at a time, in which case we might as well use the whole last page of request. So we allocate enough more memory to hit a page boundary now, which in turn causes future contiguous calls to page-align. */
else
{
front_misalign = 0;
end_misalign = 0;
correction = 0;
aligned_brk = brk;

/* handle contiguous cases */
if (contiguous (av))
{
/* Count foreign sbrk as system_mem. */
if (old_size)
av->system_mem += brk - old_end;

/* Guarantee alignment of first new chunk made from this space */

front_misalign = (INTERNAL_SIZE_T) chunk2mem (brk) & MALLOC_ALIGN_MASK;
if (front_misalign > 0)
{
/*
Skip over some bytes to arrive at an aligned position. We don't need to specially mark these wasted front bytes. They will never be accessed anyway because prev_inuse of av->top (and any chunk created from its start) is always true after initialization. */
correction = MALLOC_ALIGNMENT - front_misalign;
aligned_brk += correction;
}

/*
If this isn't adjacent to existing space, then we will not be able to merge with old_top space, so must add to 2nd request. */
correction += old_size;

/* Extend the end address to hit a page boundary */
end_misalign = (INTERNAL_SIZE_T) (brk + size + correction);
correction += (ALIGN_UP (end_misalign, pagesize)) - end_misalign;

assert (correction >= 0);
snd_brk = (char *) (MORECORE (correction));

/*
If can't allocate correction, try to at least find out current brk. It might be enough to proceed without failing.
Note that if second sbrk did NOT fail, we assume that space is contiguous with first sbrk. This is a safe assumption unless program is multithreaded but doesn't use locks and a foreign sbrk occurred between our first and second calls. */
if (snd_brk == (char *) (MORECORE_FAILURE))
{
correction = 0;
snd_brk = (char *) (MORECORE (0));
}
else
{
/* Call the `morecore' hook if necessary. */
void (*hook) (void) = atomic_forced_read (__after_morecore_hook);
if (__builtin_expect (hook != NULL, 0))
(*hook)();
}
}

/* handle non-contiguous cases */
else
{
if (MALLOC_ALIGNMENT == 2 * SIZE_SZ)
/* MORECORE/mmap must correctly align */
assert (((unsigned long) chunk2mem (brk) & MALLOC_ALIGN_MASK) == 0);
else
{
front_misalign = (INTERNAL_SIZE_T) chunk2mem (brk) & MALLOC_ALIGN_MASK;
if (front_misalign > 0)
{
/*
Skip over some bytes to arrive at an aligned position. We don't need to specially mark these wasted front bytes. They will never be accessed anyway because prev_inuse of av->top (and any chunk created from its start) is always true after initialization. */
aligned_brk += MALLOC_ALIGNMENT - front_misalign;
}
}

/* Find out current end of memory */
if (snd_brk == (char *) (MORECORE_FAILURE))
{
snd_brk = (char *) (MORECORE (0));
}
}

/* Adjust top based on results of second sbrk */
if (snd_brk != (char *) (MORECORE_FAILURE))
{
av->top = (mchunkptr) aligned_brk;
set_head (av->top, (snd_brk - aligned_brk + correction) | PREV_INUSE);
av->system_mem += correction;

/*
If not the first time through, we either have a gap due to foreign sbrk or a non-contiguous region. Insert a double fencepost at old_top to prevent consolidation with space we don't own. These fenceposts are artificial chunks that are marked as inuse and are in any case too small to use. We need two to make sizes and alignments work out. */
if (old_size != 0)
{
/*
Shrink old_top to insert fenceposts, keeping size a multiple of MALLOC_ALIGNMENT. We know there is at least enough space in old_top to do this. */ old_size = (old_size - 4 * SIZE_SZ) & ~MALLOC_ALIGN_MASK;
set_head (old_top, old_size | PREV_INUSE);

/*
Note that the following assignments completely overwrite old_top when old_size was previously MINSIZE. This is intentional. We need the fencepost, even if old_top otherwise gets lost. */ chunk_at_offset (old_top, old_size)->size =
(2 * SIZE_SZ) | PREV_INUSE;

chunk_at_offset (old_top, old_size + 2 * SIZE_SZ)->size =
(2 * SIZE_SZ) | PREV_INUSE;

/* If possible, release the rest. */
if (old_size >= MINSIZE)
{
_int_free (av, old_top, 1);
}
}
}
}
}
} /* if (av != &main_arena) */

if ((unsigned long) av->system_mem > (unsigned long) (av->max_system_mem))
av->max_system_mem = av->system_mem;
check_malloc_state (av);

/* finally, do the allocation */
p = av->top;
size = chunksize (p);

/* check that one of the above allocation paths succeeded */
if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE))
{
remainder_size = size - nb;
remainder = chunk_at_offset (p, nb);
av->top = remainder;
set_head (p, nb | PREV_INUSE | (av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);
check_malloced_chunk (av, p, nb);
return chunk2mem (p);
}

/* catch all failure paths */
__set_errno (ENOMEM);
return 0;
}

释放过程

free源码分析

__libc_free

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
void
__libc_free (void *mem)
{
mstate ar_ptr;
mchunkptr p; /* chunk corresponding to mem */
// 调用 __free_hook ,参数是是否的内存的地址。
void (*hook) (void *, const void *)
= atomic_forced_read (__free_hook);
if (__builtin_expect (hook != NULL, 0))
{
(*hook)(mem, RETURN_ADDRESS (0));
return;
}

if (mem == 0) /* free(0) has no effect */
return;

p = mem2chunk (mem);
// 如果是 mmapp 得到的内存单独处理
if (chunk_is_mmapped (p)) /* release mmapped memory. */
{
/* see if the dynamic brk/mmap threshold needs adjusting */
// 释放的内存大小如果大于 mmap_threshold 并且小于 DEFAULT_MMAP_THRESHOLD_MAX(0x20000)
// 则更新 mmap_threshold 为释放内存的大小,trim_threshold 为两倍释放内存的大小。
// 其中 mmap_threshold 是 sysmalloc 中 brk 和 mmap 两种系统调用获取内存的选择的边界值
// trim_threshold 为是否 systrim 减少 ptmalloc 保留内存的参考值
if (!mp_.no_dyn_threshold
&& p->size > mp_.mmap_threshold
&& p->size <= DEFAULT_MMAP_THRESHOLD_MAX)
{
mp_.mmap_threshold = chunksize (p);
mp_.trim_threshold = 2 * mp_.mmap_threshold;
LIBC_PROBE (memory_mallopt_free_dyn_thresholds, 2,
mp_.mmap_threshold, mp_.trim_threshold);
}
// 调用 nummap 释放内存
munmap_chunk (p);
return;
}

// 调用 _int_free 释放内存
ar_ptr = arena_for_chunk (p);
_int_free (ar_ptr, p, 0);
}

_int_free

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
static void _int_free (mstate av, mchunkptr p, int have_lock) {
INTERNAL_SIZE_T size; /* 释放的chunk的size */
mfastbinptr *fb; /* 对应的fastbin */
mchunkptr nextchunk; /* 内存空间中下一个chunk */
INTERNAL_SIZE_T nextsize; /* 下一个chunk的大小 */
int nextinuse; /* 下一个chunk是否在使用 */
INTERNAL_SIZE_T prevsize; /* 内存空间中上一个chunk */
mchunkptr bck; /* 用于储存bin链表指针 */
mchunkptr fwd; /* 用于储存bin链表指针 */

const char *errstr = NULL;
int locked = 0;
size = chunksize (p);
if (__builtin_expect ((uintptr_t) p > (uintptr_t) -size, 0) || __builtin_expect (misaligned_chunk (p), 0)) {
//chunk的指针地址不能溢出
errstr = "free(): invalid pointer";
errout:
if (!have_lock && locked)
(void) mutex_unlock (&av->mutex);
malloc_printerr (check_action, errstr, chunk2mem (p), av);
return;
}
if (__glibc_unlikely (size < MINSIZE || !aligned_OK (size))) {
//chunk的大小必须大于等于MINSIZE且对齐
errstr = "free(): invalid size";
goto errout;
}
check_inuse_chunk(av, p);
if ((unsigned long)(size) <= (unsigned long)(get_max_fast ())
#if TRIM_FASTBINS
&& (chunk_at_offset(p, size) != av->top)
#endif
) {
//当前free的chunk属于fastbin
if (__builtin_expect (chunk_at_offset (p, size)->size <= 2 * SIZE_SZ, 0) || __builtin_expect (chunksize (chunk_at_offset (p, size)) >= av->system_mem, 0)) {
//查看下一个相邻的chunk的大小是否小于等于2 * SIZE_SZ,或是否大于分配区所分配的内存总量
if (have_lock || ({ assert (locked == 0); mutex_lock(&av->mutex); locked = 1; chunk_at_offset (p, size)->size <= 2 * SIZE_SZ || chunksize (chunk_at_offset (p, size)) >= av->system_mem; })) {
errstr = "free(): invalid next size (fast)";
goto errout;
}
if (! have_lock) {
(void)mutex_unlock(&av->mutex);
locked = 0;
}
//读取分配区所分配的内存总量需要对分配区加锁,检查完以后,释放分配区的锁
}
free_perturb (chunk2mem(p), size - 2 * SIZE_SZ);

set_fastchunks(av);
unsigned int idx = fastbin_index(size);
fb = &fastbin (av, idx);
//设置当前分配区的fastbin的flag,表示当前分配区的fastbin中已有空闲chunk.然后根据当前free的chunk大小获取所属的fastbin
mchunkptr old = *fb, old2;
unsigned int old_idx = ~0u;
do {
if (__builtin_expect (old == p, 0)) {
//fastbin double free检测
errstr = "double free or corruption (fasttop)";
goto errout;
}
if (have_lock && old != NULL)
old_idx = fastbin_index(chunksize(old));
p->fd = old2 = old;
} while ((old = catomic_compare_and_exchange_val_rel (fb, p, old2)) != old2);
//使用lock-free技术实现fastbin的单向链表插入操作
if (have_lock && old != NULL && __builtin_expect (old_idx != idx, 0)) {
errstr = "invalid fastbin entry (free)";
goto errout;
}
} else if (!chunk_is_mmapped(p)) {
//当前free的chunk不是通过mmap分配的,并且当前还没有获得分配区的锁,获取分配区的锁
if (! have_lock) {
(void)mutex_lock(&av->mutex);
locked = 1;
}
nextchunk = chunk_at_offset(p, size);

if (__glibc_unlikely (p == av->top)) {
//free的是top chunk
errstr = "double free or corruption (top)";
goto errout;
}
if (__builtin_expect (contiguous (av) && (char *) nextchunk >= ((char *) av->top + chunksize(av->top)), 0)) {
//内存中下一个chunk的地址大于top chunk的末尾
errstr = "double free or corruption (out)";
goto errout;
}
if (__glibc_unlikely (!prev_inuse(nextchunk))) {
//该chunk已经是free状态
errstr = "double free or corruption (!prev)";
goto errout;
}

nextsize = chunksize(nextchunk);
if (__builtin_expect (nextchunk->size <= 2 * SIZE_SZ, 0) || __builtin_expect (nextsize >= av->system_mem, 0)) {
//查看下一个相邻的chunk的大小是否小于等于2 * SIZE_SZ,或是否大于分配区所分配的内存总量
errstr = "free(): invalid next size (normal)";
goto errout;
}
free_perturb (chunk2mem(p), size - 2 * SIZE_SZ);

if (!prev_inuse(p)) {
//如果当前free的chunk的前一个相邻chunk为空闲状态,与前一个空闲chunk合并
prevsize = p->prev_size;
size += prevsize;
p = chunk_at_offset(p, -((long) prevsize));
unlink(av, p, bck, fwd);
}
if (nextchunk != av->top) {
//与当前free的chunk相邻的下一个chunk不是分配区的top chunk
nextinuse = inuse_bit_at_offset(nextchunk, nextsize);
if (!nextinuse) {
//如果当前free的chunk的下一个相邻chunk为空闲状态,与下一个空闲chunk合并
unlink(av, nextchunk, bck, fwd);
size += nextsize;
} else
//与当前free的chunk相邻的下一个chunk处于inuse状态,清除当前chunk的inuse状态
clear_inuse_bit_at_offset(nextchunk, 0);

bck = unsorted_chunks(av);
fwd = bck->fd;
if (__glibc_unlikely (fwd->bk != bck)) {
//unsorted_bin第一个chunk的fd的bk不是第一个chunk
errstr = "free(): corrupted unsorted chunks";
goto errout;
}
p->fd = fwd;
p->bk = bck;
if (!in_smallbin_range(size)) {
p->fd_nextsize = NULL;
p->bk_nextsize = NULL;
}
bck->fd = p;
fwd->bk = p;
//将合并后的chunk加入unsorted_bin的双向循环链表中
set_head(p, size | PREV_INUSE);
set_foot(p, size);
check_free_chunk(av, p);
} else {
//当前free的chunk下一个相邻的chunk为top chunk,则将当前chunk合并入top chunk
size += nextsize;
set_head(p, size | PREV_INUSE);
av->top = p;
check_chunk(av, p);
}

if ((unsigned long)(size) >= FASTBIN_CONSOLIDATION_THRESHOLD) {
//如果合并后的chunk大小大于64KB
if (have_fastchunks(av))
malloc_consolidate(av);
if (av == &main_arena) {
#ifndef MORECORE_CANNOT_TRIM
if ((unsigned long)(chunksize(av->top)) >= (unsigned long)(mp_.trim_threshold))
//如果当前分配区为主分配区且top chunk的大小大于heap的收缩阈值,调用systrim函数收缩heap
systrim(mp_.top_pad, av);
#endif
} else {
//为非主分配区,调用heap_trim函数收缩非主分配区的sub_heap
heap_info *heap = heap_for_ptr(top(av));
assert(heap->ar_ptr == av);
heap_trim(heap, mp_.top_pad);
}
}
if (! have_lock) {
//有锁则对分配区解锁
assert (locked);
(void)mutex_unlock(&av->mutex);
}
} else {
//当前free的chunk是通过mmap分配则调用munma_chunk释放
munmap_chunk (p);
}
}

systrim

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
static int systrim (size_t pad, mstate av) {
long top_size;
long extra;
long released;
char *current_brk;
char *new_brk;
size_t pagesize;
long top_area;

pagesize = GLRO (dl_pagesize);
top_size = chunksize (av->top);
top_area = top_size - MINSIZE - 1;
if (top_area <= pad)
return 0;
extra = ALIGN_DOWN(top_area - pad, pagesize);
//计算top chunk中最大可释放的整数页大小,top chunk中至少需要MINSIZE的内存保存fencepost
if (extra == 0)
return 0;

current_brk = (char *) (MORECORE (0));
if (current_brk == (char *) (av->top) + top_size) {
//如果当前top chunk的结束地址与当前的brk值相等,执行heap收缩
MORECORE (-extra);
//调用sbrk释放指定大小的内存
void (*hook) (void) = atomic_forced_read (__after_morecore_hook);
if (__builtin_expect (hook != NULL, 0))
(*hook)();
new_brk = (char *) (MORECORE (0));
LIBC_PROBE (memory_sbrk_less, 2, new_brk, extra);
if (new_brk != (char *) MORECORE_FAILURE) {
//计算释放的内存大小,更新当前分配区所分配的内存总量,更新top chunk的大小
released = (long) (current_brk - new_brk);
if (released != 0) {
av->system_mem -= released;
set_head (av->top, (top_size - released) | PREV_INUSE);
check_malloc_state (av);
return 1;
}
}
}
return 0;
}

calloc源码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
void *
__libc_calloc (size_t n, size_t elem_size)
{
mstate av;
mchunkptr oldtop, p;
INTERNAL_SIZE_T bytes, sz, csz, oldtopsize;
void *mem;
unsigned long clearsize;
unsigned long nclears;
INTERNAL_SIZE_T *d;

/* size_t is unsigned so the behavior on overflow is defined. */
// 将需要申请的内存大小转换为以字节为单位
bytes = n * elem_size;
#define HALF_INTERNAL_SIZE_T \
(((INTERNAL_SIZE_T) 1) << (8 * sizeof (INTERNAL_SIZE_T) / 2))

// 如果 n 和 elem_size 中的任何一个不小于 HALF_INTERNAL_SIZE_T
// 以 64 位为例,HALF_INTERNAL_SIZE_T = 2^32
if (__builtin_expect ((n | elem_size) >= HALF_INTERNAL_SIZE_T, 0))
{
// 判断 bytes 是否溢出
if (elem_size != 0 && bytes / elem_size != n)
{
__set_errno (ENOMEM);
return 0;
}
}

// 获取 __malloc_hook
void *(*hook) (size_t, const void *) =
atomic_forced_read (__malloc_hook);

// 如果 __malloc_hook 不为 NULL 则调用 __malloc_hook,参数为申请内存的大小。
if (__builtin_expect (hook != NULL, 0))
{
sz = bytes;
mem = (*hook)(sz, RETURN_ADDRESS (0));
if (mem == 0)
return 0;

return memset (mem, 0, sz);
}

sz = bytes;

arena_get (av, sz);
if (av)
{
/* Check if we hand out the top chunk, in which case there may be no
need to clear. */
// 获取 top chunk 和 top chunk 的大小,这里的 top chunk 的大小是指 top chunk 头之后可以“控制”的的内存大小,具体看后面的解释。
// 获取这些的原因是无论是 main_arena 控制的 heap 区域通过 sbrk 扩展还是非 main_arena 区域通过对 heap_info 向后扩展受保护的内存区域,
// 新扩展的内存初始值为 0,即这些内存不需要清空,因此后面会将需要清零的内存大小减去和这部分内存重合的区域,提升程序效率。
#if MORECORE_CLEARS
oldtop = top (av);
oldtopsize = chunksize (top (av));
# if MORECORE_CLEARS < 2
/* Only newly allocated memory is guaranteed to be cleared. */
if (av == &main_arena &&
oldtopsize < mp_.sbrk_base + av->max_system_mem - (char *) oldtop)
// 对于 main_arena 管理的内存,top chunk 后需要清空的内存大小为 top chunk 到原先 heap 区域末尾位置
oldtopsize = (mp_.sbrk_base + av->max_system_mem - (char *) oldtop);
# endif
if (av != &main_arena)
{
// 对于非 main_arena 管理的内存,top chunk 后需要清空的内存大小为 top chunk 到原先 heap_info 受保护区域末尾位置
heap_info *heap = heap_for_ptr (oldtop);
if (oldtopsize < (char *) heap + heap->mprotect_size - (char *) oldtop)
oldtopsize = (char *) heap + heap->mprotect_size - (char *) oldtop;
}
#endif
}
else
{
/* No usable arenas. */
// av 为 NULL ,那么之后 _int_malloc 会直接 mmap 获取内存,而 mmap 获取的内存初始值为 0,因此不需要清零。
oldtop = 0;
oldtopsize = 0;
}
// 调用 _int_malloc 获取内存
mem = _int_malloc (av, sz);

// 同 __libc_malloc 的 3 种情况
assert (!mem || chunk_is_mmapped (mem2chunk (mem)) ||
av == arena_for_chunk (mem2chunk (mem)));

if (mem == 0 && av != NULL)
{
LIBC_PROBE (memory_calloc_retry, 1, sz);
av = arena_get_retry (av, sz);
mem = _int_malloc (av, sz);
}

if (av != NULL)
(void) mutex_unlock (&av->mutex);

/* Allocation failed even after a retry. */
if (mem == 0)
return 0;

p = mem2chunk (mem);

/* Two optional cases in which clearing not necessary */
// 如果是 mmap 获取的不需要清零,因此只要 chunk 的 size 字段中的 IS_MMAPPED 位置 1 就不会清零。
if (chunk_is_mmapped (p))
{
if (__builtin_expect (perturb_byte, 0))
return memset (mem, 0, sz);

return mem;
}

csz = chunksize (p);

#if MORECORE_CLEARS
// 如果是从 top chunk 上切下来的则只需要清零 top chunk 范围的内存。
if (perturb_byte == 0 && (p == oldtop && csz > oldtopsize))
{
/* clear only the bytes from non-freshly-sbrked memory */
csz = oldtopsize;
}
#endif

/* Unroll clear of <= 36 bytes (72 if 8byte sizes). We know that
contents have an odd number of INTERNAL_SIZE_T-sized words;
minimally 3. */
// 清空内存,包括下一个 chunk 的 prev_size 。
d = (INTERNAL_SIZE_T *) mem;
clearsize = csz - SIZE_SZ;
nclears = clearsize / sizeof (INTERNAL_SIZE_T);
assert (nclears >= 3);

if (nclears > 9)
return memset (d, 0, clearsize);

else
{
*(d + 0) = 0;
*(d + 1) = 0;
*(d + 2) = 0;
if (nclears > 4)
{
*(d + 3) = 0;
*(d + 4) = 0;
if (nclears > 6)
{
*(d + 5) = 0;
*(d + 6) = 0;
if (nclears > 8)
{
*(d + 7) = 0;
*(d + 8) = 0;
}
}
}
}

return mem;
}

relloc源码分析

_libc_realloc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
void *
__libc_realloc (void *oldmem, size_t bytes)
{
mstate ar_ptr;
INTERNAL_SIZE_T nb; /* padded request size */

void *newp; /* chunk to return */
// 调用 __realloc_hook
void *(*hook) (void *, size_t, const void *) =
atomic_forced_read (__realloc_hook);
if (__builtin_expect (hook != NULL, 0))
return (*hook)(oldmem, bytes, RETURN_ADDRESS (0));
// 如果 bytes 为 0 则相当于 free(oldmem)
#if REALLOC_ZERO_BYTES_FREES
if (bytes == 0 && oldmem != NULL)
{
__libc_free (oldmem); return 0;
}
#endif
// 如果 oldmem 为 NULL 相当于 malloc(bytes)
/* realloc of null is supposed to be same as malloc */
if (oldmem == 0)
return __libc_malloc (bytes);

// 获取 oldmem 对应的 chunk 的指针和大小
/* chunk corresponding to oldmem */
const mchunkptr oldp = mem2chunk (oldmem);
/* its size */
const INTERNAL_SIZE_T oldsize = chunksize (oldp);
// 寻找 oldp 对应的 arena
if (chunk_is_mmapped (oldp))
ar_ptr = NULL;
else
ar_ptr = arena_for_chunk (oldp);

/* Little security check which won't hurt performance: the
allocator never wrapps around at the end of the address space.
Therefore we can exclude some size values which might appear
here by accident or by "design" from some intruder. */
// 检查 oldp + oldsize 是否超过地址上限
if (__builtin_expect ((uintptr_t) oldp > (uintptr_t) -oldsize, 0)
|| __builtin_expect (misaligned_chunk (oldp), 0))
{
malloc_printerr (check_action, "realloc(): invalid pointer", oldmem,
ar_ptr);
return NULL;
}
// 检查如果申请最小的 chunk 是否会超过地址上限
checked_request2size (bytes, nb);

// 如果是 mmap 得到的内存会单独处理
if (chunk_is_mmapped (oldp))
{
void *newmem;

#if HAVE_MREMAP
// 如果是 mmap 得到的内存则利用 mremap 系统调用实现 realloc。
// mremap 会重新分配一块内存并将之前的数据复制到新的内存上。
newp = mremap_chunk (oldp, nb);
if (newp)
return chunk2mem (newp);
#endif
/* Note the extra SIZE_SZ overhead. */
if (oldsize - SIZE_SZ >= nb)
return oldmem; /* do nothing */

/* Must alloc, copy, free. */
// 如果 mremap 获取不到所需的内存则通过 malloc 获取内存,并将原先内存的数据复制过来然后 munmap 将原先的内存释放掉
newmem = __libc_malloc (bytes);
if (newmem == 0)
return 0; /* propagate failure */

memcpy (newmem, oldmem, oldsize - 2 * SIZE_SZ);
munmap_chunk (oldp);
return newmem;
}

(void) mutex_lock (&ar_ptr->mutex);

// 调用 _int_realloc 调整内存
newp = _int_realloc (ar_ptr, oldp, oldsize, nb);

(void) mutex_unlock (&ar_ptr->mutex);
// 检查内存分配后的 3 种情况
assert (!newp || chunk_is_mmapped (mem2chunk (newp)) ||
ar_ptr == arena_for_chunk (mem2chunk (newp)));

// 如果 _int_realloc 没有成功则尝试调用 _int_malloc 重新分配内存
if (newp == NULL)
{
/* Try harder to allocate memory in other arenas. */
LIBC_PROBE (memory_realloc_retry, 2, bytes, oldmem);
newp = __libc_malloc (bytes);
// 如果 malloc 成功则将数据拷贝后释放原先的内存
if (newp != NULL)
{
memcpy (newp, oldmem, oldsize - SIZE_SZ);
_int_free (ar_ptr, oldp, 0);
}
}

return newp;
}

int_realloc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
// _int_realloc函数用于重新分配内存块。它尝试更改内存块的大小并可能移动它以满足新的大小要求。
// 参数:
// av - 指向内存状态的指针。
// oldp - 指向当前内存块的指针。
// oldsize - 当前内存块的大小。
// nb - 请求的新大小。
void* _int_realloc(mstate av, mchunkptr oldp, INTERNAL_SIZE_T oldsize, INTERNAL_SIZE_T nb) {
// 定义一系列局部变量来存储分配的状态和中间结果。
mchunkptr newp; // 新分配的内存块指针。
INTERNAL_SIZE_T newsize; // 新内存块的大小。
void* newmem; // 对应用户内存的指针。

mchunkptr next; // 指向oldp后面的连续内存块。

mchunkptr remainder; // 新分配内存后剩余的内存块。
unsigned long remainder_size; // 剩余内存块的大小。

mchunkptr bck; // 用于链接的临时变量。
mchunkptr fwd; // 用于链接的临时变量。

unsigned long copysize; // 需要复制的字节数。
unsigned int ncopies; // 需要复制的INTERNAL_SIZE_T字数。
INTERNAL_SIZE_T* s; // 复制源的指针。
INTERNAL_SIZE_T* d; // 复制目标的指针。

const char *errstr = NULL; // 用于错误处理的字符串。

// 检查oldp的大小是否合法。
if (__builtin_expect(oldp->size <= 2 * SIZE_SZ, 0) || __builtin_expect(oldsize >= av->system_mem, 0)) {
errstr = "realloc(): invalid old size";
goto errout;
}

check_inuse_chunk(av, oldp); // 检查oldp是否正在使用中。

assert(!chunk_is_mmapped(oldp)); // 确保oldp不是映射内存。

next = chunk_at_offset(oldp, oldsize); // 计算下一个内存块的位置。
INTERNAL_SIZE_T nextsize = chunksize(next); // 获取下一个内存块的大小。
// 检查下一个内存块的大小是否合法。
if (__builtin_expect(next->size <= 2 * SIZE_SZ, 0) || __builtin_expect(nextsize >= av->system_mem, 0)) {
errstr = "realloc(): invalid next size";
goto errout;
}

// 如果当前内存块已经足够大,则直接返回当前内存块。
if ((unsigned long)(oldsize) >= (unsigned long)(nb)) {
newp = oldp;
newsize = oldsize;
} else {
// 如果下一个内存块是 top chunk ,并且可以合并以满足请求的大小,则进行合并。
if (next == av->top && (unsigned long)(newsize = oldsize + nextsize) >= (unsigned long)(nb + MINSIZE)) {
set_head_size(oldp, nb | (av != &main_arena ? NON_MAIN_ARENA : 0));
av->top = chunk_at_offset(oldp, nb);
set_head(av->top, (newsize - nb) | PREV_INUSE);
check_inuse_chunk(av, oldp);
return chunk2mem(oldp);
}
// 如果下一个内存块不在使用中,并且可以合并以满足请求的大小,则进行合并。
else if (next != av->top && !inuse(next) && (unsigned long)(newsize = oldsize + nextsize) >= (unsigned long)(nb)) {
newp = oldp;
unlink(av, next, bck, fwd);
}
// 否则,分配新内存,复制数据,然后释放旧内存。
else {
newmem = _int_malloc(av, nb - MALLOC_ALIGN_MASK);
if (newmem == 0)
return 0; // 如果分配失败,则返回0。

newp = mem2chunk(newmem);
newsize = chunksize(newp);

// 如果新分配的内存块紧跟在旧内存块后面,则合并这两个内存块。
if (newp == next) {
newsize += oldsize;
newp = oldp;
} else {
// 否则,复制旧内存块的内容到新内存块。
copysize = oldsize - SIZE_SZ;
s = (INTERNAL_SIZE_T*)(chunk2mem(oldp));
d = (INTERNAL_SIZE_T*)(newmem);
ncopies = copysize / sizeof(INTERNAL_SIZE_T);
assert(ncopies >= 3);

if (ncopies > 9)
memcpy(d, s, copysize);
else {
// 对于小内存块,使用手动复制以提高效率。
*(d + 0) = *(s + 0);
*(d + 1) = *(s + 1);
*(d + 2) = *(s + 2);
if (ncopies > 4) {
*(d + 3) = *(s + 3);
*(d + 4) = *(s + 4);
if (ncopies > 6) {
*(d + 5) = *(s + 5);
*(d + 6) = *(s + 6);
if (ncopies > 8) {
*(d + 7) = *(s + 7);
*(d + 8) = *(s + 8);
}
}
}
}

_int_free(av, oldp, 1); // 释放旧内存块。
check_inuse_chunk(av, newp); // 检查新内存块是否正在使用中。
return chunk2mem(newp); // 返回新内存块的用户可用部分。
}
}
}

// 尝试释放新内存块中的多余空间。
assert((unsigned long)(newsize) >= (unsigned long)(nb));
remainder_size = newsize - nb;
// 如果剩余空间太小,无法分割为独立的内存块,则保留它。
if (remainder_size < MINSIZE) {
set_head_size(newp, newsize | (av != &main_arena ? NON_MAIN_ARENA : 0));
set_inuse_bit_at_offset(newp, newsize);
} else {
// 否则,分割剩余空间为独立的内存块。
remainder = chunk_at_offset(newp, nb);
set_head_size(newp, nb | (av != &main_arena ? NON_MAIN_ARENA : 0));
set_head(remainder, remainder_size | PREV_INUSE | (av != &main_arena ? NON_MAIN_ARENA : 0));
set_inuse_bit_at_offset(remainder, remainder_size); // 标记剩余部分为正在使用中,以便_free()不会报错。
_int_free(av, remainder, 1); // 释放剩余部分。
}

check_inuse_chunk(av, newp); // 检查新内存块是否正在使用中。
return chunk2mem(newp); // 返回新内存块的用户可用部分。
}

参考

参考:ptmalloc内存管理分析
参考:看雪pwn探索篇
参考:Understanding glibc malloc – sploitF-U-N