郑重声明:文中所涉及的技术、思路和工具仅供以安全为目的的学习交流使用,如果您不同意请关闭该页面!任何人不得将其用于非法用途以及盈利等目的,否则后果自行承担!

前言

HVV结束,终于能闲下来学习了,开新坑啦~好耶~

4E2FEA9B95DD08A1A4A30E8A3E4B9C63

记个小知识点

  • libc是Linux下的ANSI C函数库。

  • glibc是Linux下的GUN C函数库。

Linux下原来的标准c库Linux libc逐渐不再被维护。Linux下面的标准c库不仅有这一个,如uclibc、klibc,以及上面被提到的Linux libc,但是glibc无疑是用得最多的。glibc在/lib目录下的.so文件为libc.so.6。

堆内存管理机制介绍

不同平台的堆内存管理机制不相同,下面是几个常见平台的堆内存管理机制:

平台 堆内存分配机制
General purpose allocator dlmalloc
glibc ptmalloc2
free BSD and Firefox jemalloc
Google tcmalloc
Solaris libumem

在 Linux 的 glibc 使用的 ptmalloc2 实现原理。本来 Linux 默认的是 dlmalloc,但是由于其不支持多线程堆管理,所以后来被支持多线程的 prmalloc2 代替了。当然在 Linux 平台上的 malloc 函数本质上都是通过系统调用 brk 或者 mmap 实现的。

img

进程的虚拟内存分布示意图(32位系统的进程虚拟内存分布):

  • 在Linux 2.6.7之前的布局
  • 堆只有1G虚拟空间

img

任何应用都可以调用 Linux 的 mmap 系统调用,或者 Windows 的 CreateFileMapping/MapViewOfFile,向操作系统申请内存映射。内存映射是一个很方便、高效的做文件 IO 的方式, 所以一般用来加载动态链接库(dynamic libraries),也可以创建一块匿名的映射内存,不对应任何文件,在程序中使用。

进程的虚拟内存分布示意图(64位系统的进程虚拟内存分布):

2

堆内存分配实验

//test_malloc.cpp
//clang++ -std=c++11 test_malloc.cpp -lpthread
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>

void* ThreadFunc(void* arg) {
printf("Before malloc in thread 1\n");
getchar();
char* addr = (char*) malloc(1000);
printf("After malloc and before free in thread 1\n");
getchar();
free(addr);
printf("After free in thread 1\n");
getchar();
return 0;
}

int main() {
pthread_t t1;
void* s;
int ret;
char* addr;

printf("Welcome to per thread arena example::%d\n",getpid());
printf("Before malloc in main thread\n");
getchar();
addr = (char*) malloc(1000);
printf("After malloc and before free in main thread\n");
getchar();
free(addr);
printf("After free in main thread\n");
getchar();
ret = pthread_create(&t1, NULL, ThreadFunc, NULL);
if(ret)
{
printf("Thread creation error\n");
return -1;
}
ret = pthread_join(t1, &s);
if(ret)
{
printf("Thread join error\n");
return -1;
}
return 0;
}

主线程 malloc 之前

image-20210505225640027

可以看到,在本机上(Ubuntu16.04,x64),在主线程调用 malloc 之前,就已经给主线程分配了一块堆内存,这块默认大小的内存是 200 KB

堆区内存位置是紧接着数据段的,并且offset的值为00000000,说明这个系统是通过 brk 进行内存分配的。

主线程 malloc 之后

image-20210505231259039

在主线程中调用 malloc 之后,发现 堆的大小仍然是 200K,因此 malloc 并没有引起堆区总容量的自增长。

作者原文中有一种解释(32位):还可以看出虽然我们只申请了1000bytes的数据,但是系统却分配了132KB大小的堆,这是为什么呢?原来这132KB的堆空间叫做arena,此时因为是主线程分配的,所以叫做main arena(每个arena中含有多个chunk,这些chunk以链表的形式加以组织)。由于132KB比1000bytes大很多,所以主线程后续再申请堆空间的话,就会先从这132KB的剩余部分中申请,直到用完或不够用的时候,再通过增加program break location的方式来增加main arena的大小。同理,当main arena中有过多空闲内存的时候,也会通过减小program break location的方式来缩小main arena的大小。

主线程 free 之后

主线程调用 free 之后,堆内存分布未发生改变

原文也给出了解释(32位):在主线程调用free之后,从内存布局可以看出程序的堆空间并没有被释放掉,因为调用free函数释放已经分配了的空间并非直接“返还”给系统,而是由glibcmalloc库函数加以管理。它会将释放的chunk(称为free chunk)添加到 main arenabin(这是一种用于存储同类型free chunk 的双链表数据结构,后面会加以详细介绍)中。在这里,记录空闲空间的 free list 数据结构称之为 bins。之后当用户再次调用 malloc 申请堆空间的时候,glibcmalloc 会先尝试从 bin 中找到一个满足要求的 chunk ,如果没有才会向操作系统申请新的堆空间。

子线程 malloc 之前

image-20210506110331027

红色框这一部分地址可以看出,在子线程 malloc 之前,已经创建了子线程的栈,或者说子线程创建时就在内存映射区上创建了该线程的栈空间,其默认大小是 8MB

Linux 子线程是由 mmap 创建的,所以其栈是位于内存映射区区域

子线程 malloc 之后

image-20210506135215497

malloc 之后,为子线程分配了堆区,这个大小是 132K,并且同样是位于内存映射区,这部分区域就是 thread1 的堆空间,即 thread1 的 arena。同时还要注意,子线程分配的空间是内存映射区向下增长的,也就是向堆区增长。

所以可以确定的是,子线程的堆栈都是分配在内存映射区和堆区之间的区域(也可以理解为就是分配在内存映射区,因为内存映射区和堆区都是动态增长的,内存映射区向下增长,堆区向上增长)。从虚拟内存的分布图中我们可以看到,在 3GB 的用户进程空间中,地址最高处的栈所占用的比较少(主线程的栈一般是 16MB 或者 8MB),然后内存映射区也不大,而初始化的堆区也很小。所以内存映射区和堆之间的区域是非常大的。

子线程 free 之后

同主线程类似,并没有立即回收。

brk和mmap的区别

从操作系统角度来看,进程分配内存有两种方式,分别由两个系统调用完成:brk和mmap(不考虑共享内存)。

  • brk是将数据段(.data)的最高地址指针_edata往高地址推;

  • mmap是在进程的虚拟地址空间中(堆和栈中间,称为文件映射区域的地方)找一块空闲的虚拟内存。

这两种方式分配的都是虚拟内存,没有分配物理内存。在第一次访问已分配的虚拟地址空间的时候,发生缺页中断,操作系统负责分配物理内存,然后建立虚拟内存和物理内存之间的映射关系。

在标准C库中,提供了malloc/free函数分配释放内存,这两个函数底层是由brk,mmap,munmap这些系统调用实现的。

arena

介绍

arena原本的翻译是竞技场,这个词非常巧妙的表现了堆中内存的管理的思路。前文提到,每个线程都有一个自己的arena用于堆内存的分配,这个区域是调用malloc的时候从操作系统获得的,一般情况下比实际malloc要大一些,当下次再次调用malloc,可以直接从arena中进行堆内存分配。

arena分为thread arena(可以包含多个 heap)和main arena(只包含一个可以自增长的 heap),每个heap 被分为多个 chunk

arena 数量

对于不同系统,arena 数量的约束如下

systems number of arena
32bits 2 x number of cpu cores + 1
64bits 8 x number of cpu cores + 1

arena 管理

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

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

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

  1. 首先循环遍历所有可用的 arena,在遍历的过程中,它会尝试加锁该 arena。如果成功加锁(该 arena 当前对应的线程并未使用堆内存则表示可加锁),比如将 main arena 成功锁住,那么就将 main arena 返回给用户,即表示该 arena 被子线程 3 共享使用。
  2. 如果没能找到可用的 arena,那么就将子线程 3 的 malloc 操作阻塞,直到有可用的 arena 为止。
  3. 如果子线程 3 再次调用 malloc 的话(第二次使用arena),glibcmalloc 就会先尝试使用最近访问的 arena(此时为 main arena)。如果此时 main arena 可用的话,就直接使用,否则就将子线程 3 阻塞,直到 main arena 再次可用为止(为了防止开销过大问题)。

堆的数据结构

glibcmalloc 中针对堆管理,主要涉及到以下 3 种数据结构

heap_info

我们把从操作系统申请的一块内存称为一个 heap,这个 heap 的信息就是用 heap_info 表示,也称为 heap header,因为一个 thread arena(注意:不包含主线程)可以包含多个 heap,所以为了便于管理,就给每个 heap 用一个 heap_info 表示。

此处的 heap 并非广义上的进程的虚拟内存空间中的堆,而是子线程通过系统调用 mmap 从操作系统申请的一块内存空间,后面 heap 不做声明,均是这个意思。同时我们把主线程的 main arena 的那个区域也成为 heap,这种称呼也是对的,二者功能相同,只不过位置不同,但是 main arena 中只包含一个可以自增长的 heap

那么在什么情况下一个 thread arena 会包含多个 heap 呢?在当前 heap 不够用的时候,malloc 会通过系统调用 mmap 申请新的 heap(这部分空间本来是位于内存映射区区域),新的 heap 会被添加到当前 thread arena 中,便于管理。

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. */ /*以mprotectated的字节的大小*/
/* 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;

malloc_state

malloc_state 用于表示 arena 的信息,因此也被称为 arena header,每个线程只含有一个 arena headerarena header 包含 bintop chunk 以及 last remainder chunk 等信息,这些概念会在后文详细介绍。

struct malloc_state
{
/* Serialize access. */ /*序列化访问。*/
mutex_t mutex;

/* Flags (formerly in max_fast). */ /*标志(以前在max_fast)。*/
int flags;

/* Fastbins */ /*快速的bins*/
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 */ /*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;
};

malloc_chunk

为了便于管理和更加高效的利用内存,一个 heap 被分为多个 chunk,每个 chunk 的大小不是固定,是根据用户的请求决定的,也就是说用户调用 malloc(size_t size) 传递的 size 参数就是 chunk 的大小(这种表示并不准确,但是为了方便理解就暂时这么描述了,详细说明见后文)。每个 chunk 都由一个结构体 malloc_chunk 表示,也成为 chunk header

struct malloc_chunk {
/* #define INTERNAL_SIZE_T size_t */
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. 这两个指针只在free chunk中存在*/
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;
};

关于上述的三种结构,基本都是针对子线程的,主线程和子线程有一些不同:

  1. 主线程的堆不是分配在内存映射区,而是进程的虚拟内存堆区,因此不含有多个 heap 所以也就不含有 heap_info 结构体。当需要更多堆空间的时候,直接通过增长 brk 指针来获取更多的空间,直到它碰到内存映射区域为止。
  2. 不同于 thread arena,主线程的 main arenaarena header 并不在堆区中,而是一个全局变量,因此它属于 libc.so 的 data segment 区域。
  3. heap的结构体heap_infoarena的结构体malloc_statechunk的结构体malloc_chunk

heap 与 arena 的关系

首先,通过内存分布图理清 malloc_stateheap_info 之间的组织关系。

下图是main arenathread arena(只有一个heap的时候) 的内存分布图:

single heap

关于Main Arena的几个点:

  • Main Arena中的malloc_state结构是不在Heap中的,并且是存在于libc.so的data段的
  • Main Arena是没有heap_info结构的(因为只有一个Heap)
  • malloc_state结构中的top成员是指向malloc_chunk结构和Allocated Chunk中间的
  • Top Chunk、Allocated Chunk、Free Chunk其中的任何一个和malloc_chunk结构进行组合才是一个完整的chunk块

关于Thread Arena(单Heap)的几个点:

  • Thread Arena中的malloc_state是分配在Heap中的
  • heap_info结构中的ar_ptr成员是指向malloc_state结构中的开头,而malloc_state结构中的top成员是指向malloc_chunk和Allocated Chunk中间的(这个是和Main Arena一样的)

下图是一个 thread arena 中含有多个 heap 的情况:

multi-heap

由于两个 heap 是通过 mmap 从操作系统申请的内存,两者在内存布局上并不相邻而是分属于不同的内存区间,所以为了便于管理,glibcmalloc 将第二个 heap_info 结构体的 prev 成员指向了第一个 heap_info 结构体的起始位置(即 ar_ptr 成员),而第一个 heap_info 结构体的 ar_ptr 成员指向了 malloc_state,这样就构成了一个单链表,方便后续管理。

bin

介绍

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

在具体的实现中,ptmalloc 采用分箱式方法对空闲的 chunk 进行管理。首先,它会根据空闲的 chunk 的大小以及使用状态将 chunk 初步分为 4 类:fast binssmall binslarge binsunsorted bin。每类中仍然有更细的划分,相似大小的chunk 会用双向链表链接起来。也就是说,在每类bin 的内部仍然会有多个互不相关的链表来保存不同大小的 chunk

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

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

bin 作为一种记录 free chunk 的链表数据结构。系统针对不同大小的 free chunk ,将 bin 分为了 4 类:

  • fast bin(总共有10个)
  • unsorted bin(只有一个)
  • small bin(总共有 62 个)
  • large bin(总共有63个)

同时,在 glibc 中用于记录 bin 的数据结构有两种,分别为:

  • fastbinsY: 这是一个数组,用于记录所有的 fast bin
  • bins 数组: 这也是一个数组,用于记录除 fast bin 之外的所有 bin 。事实上这个数组共有 126 个bin,分别是:
    • bin [1] : unsorted bin
    • bin [2~63] : small bin
    • bin [64~126] : large bin

fast bin

对于size较小的chunk,释放之后单独处理,被放入fast bin中。

  • 32位系统,fast bin中的chunk大小范围在16字节到64字节;
  • 64位系统,fast bin中的chunk大小范围在32字节到128字节。

下图是32位系统分布图:

fast bin

下图为64位系统的:

img

在内存分配和释放过程中,fast bin 是所有 bin 中操作速度最快的。下面详细介绍 fast bin 的一些特性:

  1. fast bin 的个数为10个,并且为了速度,fast bin永远不会进行合并,里面chunk中的P标志位永远为1
  2. 这类bin通常申请和释放的堆块都比较小,所以使用单链表结构,LIFO(后进先出)分配策略。
  3. fastbinsY数组里按照从小到大的顺序排列。

unsorted bin

unsorted bin 可以视为空闲 chunk 回归其所属 bin 之前的缓冲区。

其在 glibc 中具体的说明如下

/*
Unsorted chunks

All remainders from chunk splits, as well as all returned chunks,
are first placed in the "unsorted" bin. They are then placed
in regular bins after malloc gives them ONE chance to be used before
binning. So, basically, the unsorted_chunks list acts as a queue,
with chunks being placed on it in free (and malloc_consolidate),
and taken off (to be either used or placed in bins) in malloc.

The NON_MAIN_ARENA flag is never set for unsorted chunks, so it
does not have to be taken into account in size comparisons.
*/

从下面的宏我们可以看出

/* The otherwise unindexable 1-bin is used to hold unsorted chunks. */
#define unsorted_chunks(M) (bin_at(M, 1))

unsorted bin 处于我们之前所说的 bin 数组下标 1 处。故而 unsorted bin 只有一个链表。unsorted bin 中的空闲 chunk 处于乱序状态,主要有两个来源

  • 当一个较大的 chunk 被分割成两半后,如果剩下的部分大于 MINSIZE,就会被放到 unsorted bin 中。
  • 释放一个不属于 fast binchunk,并且该 chunk 不和 top chunk 紧邻时,该 chunk 会被首先放到 unsorted bin 中可以加快分配速度。使用双链表结构,FIFO(先进先出)分配策略。

以64位为例,unsorted bin结构如下(非连续内存,大小无限制):

img

small bin

同一个small bin里chunk的大小相同,采用双链表结构,使用频率介于fast binlarge bin之间。每个链表都有链表头结点,这样可以方便对于链表内部结点的管理。此外,small bins 中每个 bin 对应的链表采用 FIFO(先进先出) 的规则,所以同一个链表中先被释放的 chunk 会先被分配出去。small binbins中居第2到第63位,共62个。small bins 中每个 chunk 的大小与其所在的 binindex 的关系为:chunk_size = 2 * SIZE_SZ * index,具体如下

下标 SIZE_SZ=4(32 位) SIZE_SZ=8(64 位)
2 16 32
3 24 48
4 32 64
5 40 80
x 2*4*x 2*8*x
63 504 1008

img

large bin

large bins 中一共包括 63 个 bin,每个 bin 中的 chunk 的大小不一致,而是处于一定区间范围内。此外,这 63 个 bin 被分成了 6 组,每组 bin 中的 chunk 大小之间的公差一致,具体如下:

数量 公差
1 32 64
2 16 512
3 8 4096
4 4 32768
5 2 262144
6 1 不限制

这里我们以 32 位平台的 large bin 为例,第一个 large bin 的起始 chunk 大小为 512 字节,第二个large binchunk最小为512+64字节(处于[512,512+64)之间的chunk都属于第一个large bin),以此类推,直到第一组结束。64位平台也是一样的,第一个large binchunk最小为1024字节,第二个large binchunk最小为1024+64字节(处于[1024,1024+64)之间的chunk都属于第一个large bin)

下面附上各类上述三类 bin 的逻辑:

img

chunk

介绍

glibcmalloc 中将整个堆内存空间分成了连续的、大小不一的 chunk,即对于堆内存管理而言 chunk 就是最小操作单位。chunk 总共分为4类:

  • allocated chunk
  • free chunk
  • top chunk
  • last remainder chunk

所有类型的 chunk 都是内存中一块连续的区域,只是通过该区域中特定位置的某些标识符加以区分。

allocated chunk

顾名思义就是已经被分配使用的 chunk ,区域内容表示如下:

  • prev_size:
    • 如果前一个 chunkfree chunk,则这个内容保存的是前一个 chunk 的大小。并且这个字段是归属于当前的chunk的
    • 如果前一个 chunkallocated chunk,则这个区域保存的是前一个 chunk 的用户数据(也有可能为填充值)。并且这个字段就不属于当前的chunk了
  • size: 保存的是当前这个chunk的大小。总共是 32 位,并且最后的 3 位作为标志位:
    • PREV_INUSE (P): 表示前一个 chunk 是否为 allocated chunk,而当前是不是 chunk allocated 可以通过查询下一个 chunk 的这个标志位来得知)
    • IS_MMAPPED (M): 表示当前 chunk 是否是通过 mmap 系统调用产生的,子线程是 mmap,主线程则是通过 brk
    • NON_MAIN_arena (N): 表示当前 chunk 是否属于 main arena,也就是主线程的 arena。(主线程和子线程的堆区不一样,前文已经做了详细说明)。

allocated chunk

几个注意点:

  1. 每个 chunk 的大小怎么确定?
    用户程序调用 malloc(size_t size) 就会创建一个chunk,传入的大小就是当前分配的 chunk 大小,这个是非常重要的。
  2. 我们为什么要知道前一个 chunk 的信息?
    为了方便合并不同的 chunk ,减少内存的碎片化。如果不这么做, chunk 的合并只能向下合并,必须从头遍历整个堆,然后加以合并,这就意味着每次进行 chunk 释放操作消耗的时间与堆的大小成线性关系。
  3. chunk 的链表是如何构成的?
    chunk 在堆内存上是连续的,并不是直接由指针构成的链表,而是通过 prev_sizesize 块构成了隐式的链表。在进行分配操作的时候,堆内存管理器可以通过遍历整个堆内存的 chunk ,分析每个 chunksize 字段,进而找到合适的 chunk

free chunk

  • prev_size: 为了防止碎片化,堆中不存在两个相邻的 free chunk (如果存在,则被堆管理器合并了)。因此对于一个 free chunk ,这个 prev_size 区域中一定包含的上一个 chunk 的部分有效数据或者为了地址对齐所做的填充对齐。
  • size: 同 allocated chunk ,表示当前 chunk 的大小,其标志位NMP 也同 allocated chunk 一样。
  • fd: 前向指针——指向当前 chunk 在同一个 bin(一种用于加快内存分配和释放效率的显示链表)的下一个(线性中的前一个) chunk
  • bk: 后向指针——指向当前 chunk 在同一个 bin 的上一个(线性中的后一个) chunk

free chunk

top chunk

当一个 chunk 处于一个arena的最顶部(即最高内存地址处)的时候,就称之为 top chunk。该 chunk 并不属于任何 bin ,而是在当前的 heap 所有的 free chunk (无论那种 bin)都无法满足用户请求的内存大小的时候,将此 chunk 当做一个应急消防员,分配给用户使用。如果 top chunk 的大小比用户请求的大小要大的话,就将该 top chunk 分作两部分:用户请求的 chunk 和剩余的部分(成为新的 top chunk)。否则,就需要扩展 heap 或分配新的 heap 了,在 main arena 中通过 sbrk 扩展 heap,而在thread arena 中通过 mmap 分配新的 heap。注意,至此我们已经多次强调,主线程和子线程的堆管理方式的差异。

last remainder chunk

它是如何产生的:当用户请求的是一个 small chunk,且该请求无法被 small binunsorted bin 满足的时候,就通过 binmaps 遍历 bin 查找最合适的 chunk ,如果该 chunk 有剩余部分的话,就将该剩余部分变成一个新的 chunk 加入到 unsorted bin 中,另外,再将该新的 chunk 变成新的 last remainder chunk

它的作用是:此类型的 chunk 用于提高连续 malloc(产生大量 small chunk)的效率,主要是提高内存分配的局部性。那么具体是怎么提高局部性的呢?举例说明。当用户请求一个 small chunk ,且该请求无法被 small bin 满足,那么就转而交由 unsorted bin 处理。同时,假设当前 unsorted bin 中只有一个 chunk 的话,也就是 last remainder chunk ,那么就将该 chunk 分成两部分:前者分配给用户,剩下的部分放到 unsorted bin 中,并成为新的 last remainder chunk 。这样就保证了连续 malloc 产生的各个 small chunk 在内存分布中是相邻的,即提高了内存分配的局部性。

Glibc内存管理流程图

img

参考文章

https://introspelliam.github.io/2017/09/10/Linux%E5%A0%86%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86%E6%B7%B1%E5%85%A5%E5%88%86%E6%9E%90%EF%BC%88%E4%B8%8A%EF%BC%89/
https://murphypei.github.io/blog/2019/01/linux-heap
https://sploitfun.wordpress.com/2015/02/10/understanding-glibc-malloc/comment-page-1/
http://abcdxyzk.github.io/blog/2015/08/05/kernel-mm-malloc/
https://www.cnblogs.com/unr4v31/p/14446412.html
https://ctf-wiki.org/pwn/linux/glibc-heap/heap_structure/#fast-bin
https://www.bilibili.com/video/BV1N441147K2?p=18