Linux
内存空间简介
Linux
提供了如下几个系统调用,用于内存分配:
brk()/sbrk() // 通过移动Heap堆顶指针brk,达到增加内存目的
mmap()/munmap() // 通过文件影射的方式,把文件映射到mmap区
这两种方式分配的都是虚拟内存,没有分配物理内存。在第一次访问已分配的虚拟地址空间的时候,发生缺页中断,操作系统负责分配物理内存,然后建立虚拟内存和物理内存之间的映射关系。
那么,既然brk
、mmap
提供了内存分配的功能,直接使用brk
、mmap
进行内存管理不是更简单吗,为什么需要glibc
呢?
我们知道,系统调用本身会产生软中断,导致程序从用户态陷入内核态,比较消耗资源。
试想,如果频繁分配回收小块内存区,那么将有很大的性能耗费在系统调用中。
因此,为了减少系统调用带来的性能损耗,glibc
采用了内存池的设计,增加了一个代理层,每次内存分配,都优先从内存池中寻找,如果内存池中无法提供,再向操作系统申请。
一切计算机的问题都可以通过加层的方式解决。
glibc
的内存分配回收策略
glibc
中malloc
内存分配逻辑如下是:
malloc
- 分配内存 <
DEFAULT_MMAP_THRESHOLD
,走__brk
,从内存池获取,失败的话走brk
系统调用 - 分配内存 >
DEFAULT_MMAP_THRESHOLD
,走__mmap
,直接调用mmap
系统调用
其中,DEFAULT_MMAP_THRESHOLD
默认为128k
,可通过mallopt
进行设置。
重点看下小块内存(size < DEFAULT_MMAP_THRESHOLD
)的分配,glibc
使用的内存池如下图示:
内存池
内存池保存在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)