文章目录
- 一、内存布局
- 二、brk(sbrk)和mmap函数
-
- [1. brk(sbrk)](#1. brk(sbrk))
- [2. mmap](#2. mmap)
- [3. ptmalloc](#3. ptmalloc)
-
- [3.1 chunk(内存块基本结构)](#3.1 chunk(内存块基本结构))
- [3.1 malloc分配大体流程](#3.1 malloc分配大体流程)
- [3.3 释放流程](#3.3 释放流程)
一、内存布局

由上图可知,栈至顶向下扩展,堆至底向上扩展。
二、brk(sbrk)和mmap函数
1. brk(sbrk)
在 Linux 系统中,malloc底层主要通过brk、sbrk和mmap这几个系统调用来实现内存的分配和管理。
cpp
#include <unistd.h>
int brk( const void *addr )
void* sbrk ( intptr_t incr );
两者的作用是扩展heap的上界brk
Brk()的参数设置为新的brk上界地址,成功返回1,失败返回0;如果addr大于当前的程序中断点,就会扩大数据段,分配新的内存;如果addr小于当前的程序中断点,就会缩小数据段,释放内存。
Sbrk()的参数为申请内存的大小,返回heap新的上界brk的地址;当increment为正数时,堆顶指针会向高地址移动,分配内存;当increment为负数时,堆顶指针会向低地址移动,释放内存 。
2. mmap
cpp
#include <sys/mman.h>
void *mmap(void *addr, size\_t length, int prot, int flags, int fd, off\_t offset);
int munmap(void *addr, size_t length);
mmap()进行内存分配(malloc)时一般使用后者,前者主要是进行文件映射。
mmap分配内存比较直接,相对的开销也较大,释放也比较简单,,通过munmap函数可以立即将内存归还给操作系统。
一般来说,当malloc申请的内存较小时,会使用brk或sbrk来扩展堆内存;而当申请的内存较大时(通常阈值为 128KB),会直接使用mmap在内存映射区申请内存 。
但是,如果每次申请内存都调用这些接口的话,势必会影响系统的性能,并且也极容易产生内存碎片。所以malloc采用ptmalloc(内存池管理机制)对内存的分配与回收进行管理。
3. ptmalloc
ptmalloc会预先向操作系统申请一块内存供用户使用,当我们申请和释放内存的时候,ptmalloc会将这些内存管理起来,并通过一些策略来判断是否将其回收给操作系统。
3.1 chunk(内存块基本结构)
在ptmalloc中,内存是由一个个chunk组成,每个chunk由结构体malloc_chunk进行描述:
cpp
struct malloc_chunk {
INTERNAL_SIZE_T prev_size; // 前一个chunk的大小(如果前一个chunk是空闲的)
INTERNAL_SIZE_T size; // 当前chunk的大小,包括头部开销
struct malloc_chunk *fd; // 双向链表指针,指向下一个空闲chunk(仅当chunk空闲时使用)
struct malloc_chunk *bk; // 双向链表指针,指向上一个空闲chunk(仅当chunk空闲时使用)
// 其他字段,如用于管理大内存块的指针等
/*当前的 chunk 存在于 large bins 中时使用 */
struct malloc_chunk* fd_nextsize; /*仅当chunk空闲时使用 */
struct malloc_chunk* bk_nextsize; /*仅当chunk空闲时使用 */
};
prev_size: 表示前一个空闲的chunk大小,如果前一个 chunk 不空闲,该字段无意义,prev_size主要用于相邻空闲chunk的合并。
size :当前 chunk 的大小和一些其他信息,其中低三位(A,M,P)中记录包括前一个 chunk 是否在使用中,当前 chunk 是否是通过 mmap 获得的内存,当前 chunk 是否属于非主分配区。
fd 和 bk : 这两兄弟只有当该 chunk 块空闲时才存在,其作用是用于将对应的空闲 chunk 块加入到空闲chunk 块链表中进行管理。例如,当一个chunk被释放时,它会根据size字段中的信息找到前一个和后一个chunk,判断它们是否空闲,如果空闲则进行合并,然后将合并后的chunk通过这两个指针插入到相应的空闲链表中 。如果该 chunk 块被分配给了应用程序使用,那么这两个指针被当作应用程序的使用空间,不会浪费。
fd_nextsize 和 bk_nextsize: 和fd、bk类似。
当chunk为空时才有fd、bk、fd_nextsize、bd_nextsize四个指针,当chunk不为空,这四个指针的空间是直接交给用户使用的。
3.1 malloc分配大体流程
- 搜索空闲链表:首先,ptmalloc 会在快速链表(fast bins)(小于4 字节)、小链表(small bins)和大链表(large bins)中查找是否有合适大小的空闲chunk。快速链表用于管理小且常用大小的chunk,这些chunk在释放时不会与相邻的chunk合并,分配速度很快;小链表中的chunk大小固定且不超过 512 字节,按大小顺序排列;大链表中的chunk大小大于 512 字节 。如果在这些链表中找到了合适的chunk,就直接返回该chunk给用户 。
- 扩展堆或使用 mmap:如果在空闲链表中没有找到合适的chunk,ptmalloc 会尝试从堆顶的top chunk中分配内存。(top chunk相当于分配区的顶部空闲内存)如果top chunk的大小足够,就从top chunk中分割出一块满足需求的内存返回给用户,剩余部分成为新的top chunk;如果top chunk的大小不足,且申请的内存小于一定阈值(如 128KB),ptmalloc 会调用sbrk扩展堆内存,然后从新扩展的内存中分配;如果申请的内存大于阈值,ptmalloc会使用mmap将内存放到mmaped chunk上,当释放mmaped chunk上的内存的时候会直接交还给操作系统。
- 大块拆分:当从大链表中找到一个大于请求大小的chunk时,ptmalloc 会将该chunk拆分成两部分,一部分满足请求大小返回给用户,另一部分作为剩余块(remainder chunk),根据其大小插入到合适的空闲链表中 。
3.3 释放流程
当调用free释放内存时,ptmalloc 会执行以下操作:
- 标记为空闲:首先将释放的chunk标记为空闲状态,并根据chunk的大小和标志位判断是否需要与相邻的空闲chunk进行合并 。
- 合并相邻空闲块:如果当前chunk的前一个和后一个chunk都是空闲的,ptmalloc 会将它们合并成一个大的chunk,减少内存碎片 。合并后的chunk会被插入到合适的空闲链表中 。
- 缩小堆:如果释放的chunk与top chunk相邻,且合并后的top chunk足够大(超过一定阈值),ptmalloc 会调用sbrk缩小堆内存,将多余的内存归还给操作系统 。
