使用maloc()
可以分配一段连续的内存空间
- 和malloc密切相关的就是虚拟内存信息,定义为
struct mm_struct *mm
具体描述进程的地址空间。 - mm_struct结构是对整个用户空间(进程空间)的描述:
struct mm_struct {
...
unsigned long start_brk, brk, start_stack; /* 栈区 的起始地址,堆区 起始地址和结束地址 */
...
};
其中start_brk
和brk
分别是堆的起始和终止地址(我们使用malloc动态分配的内存就在这之间)
堆的大小由
start_brk
和brk
决定,但是可以使用系统调用sbrk()
或brk()
增加brk的值,达到增大堆空间的效果,但是系统调用代价太大,涉及到用户态和内核态的相互转换。
所以,实际中系统分配较大的堆空间,进程通过malloc()库函数在堆上进行空间动态分配,堆如果不够用malloc可以进行系统调用,增大brk的值。
相关系统调用
-
brk()和sbrk()
Linux通过brk和sbrk系统调用操作break指针 -
mmap()函数
malloc使用的mmap函数的第二种用法,即匿名映射,匿名映射是向映射区申请一块内存。当申请小内存的时,malloc使用sbrk分配内存;当申请大内存时,使用mmap函数申请内存
-
munmap函数
用于释放内存
malloc实现方案
-
由于
brk/sbrk/mmap
属于系统调用,如果每次申请内存,都调用这三个函数中的一个,那么每次都要产生系统调用开销(即cpu从用户态切换到内核态的上下文切换,这里要保存用户态数据,等会还要切换回用户态),这是非常影响性能的; -
其次,这样申请的内存容易产生碎片。鉴于此,malloc采用的是内存池的实现方式——先申请一大块内存,然后将内存分成不同大小的内存块,然后用户申请内存时,直接从内存池中选择一块相近的内存块即可。
-
内存池保存在bins这个长128的数组中,每个元素都是一个双向链表。
- bins[0]目前没有使用
- bins[1]的链表称为unsorted_list,用于维护free释放的chunk
- bins[2,63)的区间称为small_bins,用于维护<512字节的内存块,其中每个元素对应的链表中的chunk大小相同,均为index*8
-
bins[64,127)称为large_bins,用于维护>512字节的内存块,每个元素对应的链表中的chunk大小不同,index越大,链表中chunk的内存大小相差越大,例如: 下标为64的chunk大小介于[512, 512+64),下标为95的chunk大小介于[2k+1,2k+512)。同一条链表上的chunk,按照从小到大的顺序排列
-
malloc除了有unsorted bin,small bin,large bin三个bin之外,还有一个fast bin。
malloc内存分配流程
- 如果分配内存<512字节,则通过内存大小定位到smallbins对应的index上(floor(size/8))
- 如果分配内存>512字节,则定位到largebins对应的index上
- 遍历unsorted_list,查找合适size的chunk,如果找到则返回;否则,将这些chunk都归类放到smallbins和largebins里面