C | 浅析malloc实现

18 篇文章 0 订阅

brk(sbrk)和mmap函数

  首先,linux系统向用户提供申请的内存有brk(sbrk)和mmap函数。下面我们先来了解一下这几个函数。

brk() 和 sbrk()

两者的作用是扩展heap的上界brk
Brk()的参数设置为新的brk上界地址,成功返回1,失败返回0;
Sbrk()的参数为申请内存的大小,返回heap新的上界brk的地址

mmap()

#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、mmap都属于系统调用,若每次申请内存,都调用这三个,那么每次都会产生系统调用,影响性能;其次,这样申请的内存容易产生碎片,因为堆是从低地址到高地址,如果高地址的内存没有被释放,低地址的内存就不能被回收。
  
  所以malloc采用的是内存池的管理方式(ptmalloc),Ptmalloc 采用边界标记法将内存划分成很多块,从而对内存的分配与回收进行管理。为了内存分配函数malloc的高效性,ptmalloc会预先向操作系统申请一块内存供用户使用,当我们申请和释放内存的时候,ptmalloc会将这些内存管理起来,并通过一些策略来判断是否将其回收给操作系统。这样做的最大好处就是,使用户申请和释放内存的时候更加高效,避免产生过多的内存碎片。

chunk 内存块的基本组织单元

在 ptmalloc 的实现源码中定义结构体 malloc_chunk 来描述这些块。malloc_chunk 定义如下:

1.struct malloc_chunk {  
2.  INTERNAL_SIZE_T      prev_size;    /* Size of previous chunk (if free).  */  
3.  INTERNAL_SIZE_T      size;         /* Size in bytes, including overhead. */  
4.  
5.  struct malloc_chunk* fd;           /* double links -- used only if free. */  
6.  struct malloc_chunk* bk;  
7.  
8.  /* Only used for large blocks: pointer to next larger size.  */  
9.  struct malloc_chunk* fd_nextsize;      /* double links -- used only if free. */  
10.  struct malloc_chunk* bk_nextsize; 
11.};  

chunk 的定义相当简单明了,对各个域做一下简单介绍 :
  prev_size: 如果前一个 chunk 是空闲的,该域表示前一个 chunk 的大小,如果前一个 chunk 不空闲,该域无意义。
  size :当前 chunk 的大小,并且记录了当前 chunk 和前一个 chunk 的一些属性,包括前一个 chunk 是否在使用中,当前 chunk 是否是通过 mmap 获得的内存,当前 chunk 是否属于非主分配区。
   fd 和 bk : 指针 fd 和 bk 只有当该 chunk 块空闲时才存在,其作用是用于将对应的空闲 chunk 块加入到空闲chunk 块链表中统一管理,如果该 chunk 块被分配给应用程序使用,那么这两个指针也就没有用(该 chunk 块已经从空闲链中拆出)了,所以也当作应用程序的使用空间,而不至于浪费。
  fd_nextsize 和 bk_nextsize: 当当前的 chunk 存在于 large bins 中时, large bins 中的空闲 chunk 是按照大小排序的,但同一个大小的 chunk 可能有多个,增加了这两个字段可以加快遍历空闲 chunk ,并查找满足需要的空闲 chunk , fd_nextsize 指向下一个比当前 chunk 大小大的第一个空闲 chunk , bk_nextszie 指向前一个比当前 chunk 大小小的第一个空闲 chunk 。 如果该 chunk 块被分配给应用程序使用,那么这两个指针也就没有用(该chunk 块已经从 size 链中拆出)了,所以也当作应用程序的使用空间,而不至于浪费。

空闲链表bins

  当用户使用free函数释放掉的内存,ptmalloc并不会马上交还给操作系统,而是被ptmalloc本身的空闲链表bins管理起来了,这样当下次进程需要malloc一块内存的时候,ptmalloc就会从空闲的bins上寻找一块合适大小的内存块分配给用户使用。这样的好处可以避免频繁的系统调用,降低内存分配的开销。
  malloc将相似大小的chunk用双向链表链接起来,这样一个链表被称为一个bin。ptmalloc一共维护了128bin。每个bins都维护了大小相近的双向链表的chunk。基于chunk的大小,有下列几种可用bins:
  1、Fast bin
  2、Unsorted bin
  3、Small bin
  4、Large bin
  
  保存这些bin的数据结构为:
  fastbinsY:这个数组用以保存fast bins。
  bins:这个数组用以保存unsorted、small以及large bins,共计可容纳126个:

  当用户调用malloc的时候,能很快找到用户需要分配的内存大小是否在维护的bin上,如果在某一个bin上,就可以通过双向链表去查找合适的chunk内存块给用户使用。

#include <unistd.h>
int brk( const void *addr )
void* sbrk ( intptr_t incr );

内存分配malloc流程

  1. 获取分配区的锁,防止多线程冲突。
  2. 计算出实际需要分配的内存的chunk实际大小。
  3. 判断chunk的大小,如果小于max_fast(64B),则尝试去fast bins上取适合的chunk,如果有则分配结束。否则,下一步;
  4. 判断chunk大小是否小于512B,如果是,则从small bins上去查找chunk,如果有合适的,则分配结束。否则下一步;
  5. ptmalloc首先会遍历fast bins中的chunk,将相邻的chunk进行合并,并链接到unsorted bin中然后遍历 unsorted bins。如果unsorted bins上只有一个chunk并且大于待分配的chunk,则进行切割,并且剩余的chunk继续扔回unsorted bins;如果unsorted bins上有大小和待分配chunk相等的,则返回,并从unsorted bins删除;如果unsorted bins中的某一chunk大小 属于small bins的范围,则放入small bins的头部;如果unsorted bins中的某一chunk大小 属于large bins的范围,则找到合适的位置放入。若未分配成功,转入下一步;
  6. 从large bins中查找找到合适的chunk之后,然后进行切割,一部分分配给用户,剩下的放入unsorted bin中。
  7. 如果搜索fast bins和bins都没有找到合适的chunk,那么就需要操作top chunk来进行分配了
    当top chunk大小比用户所请求大小还大的时候,top chunk会分为两个部分:User chunk(用户请求大小)和Remainder chunk(剩余大小)。其中Remainder chunk成为新的top chunk。
      当top chunk大小小于用户所请求的大小时,top chunk就通过sbrk(main arena)或mmap(thread arena)系统调用来扩容。
  8. 到了这一步,说明 top chunk 也不能满足分配要求,所以,于是就有了两个选择: 如 果是主分配区,调用 sbrk(),增加 top chunk 大小;如果是非主分配区,调用 mmap 来分配一个新的 sub-heap,增加 top chunk 大小;或者使用 mmap()来直接分配。在 这里,需要依靠 chunk 的大小来决定到底使用哪种方法。判断所需分配的 chunk 大小是否大于等于 mmap 分配阈值,如果是的话,则转下一步,调用 mmap 分配, 否则跳到第 10 步,增加 top chunk 的大小。
  9. 使用 mmap 系统调用为程序的内存空间映射一块 chunk_size align 4kB 大小的空间。 然后将内存指针返回给用户。
  10. 判断是否为第一次调用 malloc,若是主分配区,则需要进行一次初始化工作,分配 一块大小为(chunk_size + 128KB) align 4KB 大小的空间作为初始的 heap。若已经初 始化过了,主分配区则调用 sbrk()增加 heap 空间,分主分配区则在 top chunk 中切 割出一个 chunk,使之满足分配需求,并将内存指针返回给用户

内存回收流程

  1. 获取分配区的锁,保证线程安全。
  2. 如果free的是空指针,则返回,什么都不做。
  3. 判断当前chunk是否是mmap映射区域映射的内存,如果是,则直接munmap()释放这块内存。前面的已使用chunk的数据结构中,我们可以看到有M来标识是否是mmap映射的内存。
  4. 判断chunk是否与top chunk相邻,如果相邻,则直接和top chunk合并(和top chunk相邻相当于和分配区中的空闲内存块相邻)。转到步骤8
  5. 如果chunk的大小大于max_fast(64b),则放入unsorted bin,并且检查是否有合并,有合并情况并且和top chunk相邻,则转到步骤8;没有合并情况则free。
  6. 如果chunk的大小小于 max_fast(64b),则直接放入fast bin,fast bin并没有改变chunk的状态。没有合并情况,则free;有合并情况,转到步骤7
  7. 在fast bin,如果当前chunk的下一个chunk也是空闲的,则将这两个chunk合并,放入unsorted bin上面。合并后的大小如果大于64B,会触发进行fast bins的合并操作,fast bins中的chunk将被遍历,并与相邻的空闲chunk进行合并,合并后的chunk会被放到unsorted bin中,fast bin会变为空。合并后的chunk和topchunk相邻,则会合并到topchunk中。转到步骤8
  8. 判断top chunk的大小是否大于mmap收缩阈值(默认为128KB),如果是的话,对于主分配区,则会试图归还top chunk中的一部分给操作系统。free结束。

 转自malloc的底层实现(ptmalloc)_编程随笔与杂谈-CSDN博客_ptmalloc

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值