动态内存分配器维护着一个进程的虚拟内存区域,称为堆(heap),紧接在未初始化的数据区域后开始,向上生长。对于每个进程,内核维护着一个变量brk,指向堆的顶部。
分配器将堆视为一组大小不同的block的集合来维护,每个block就是一个连续的虚拟内存片,要么是已分配状态,要么是空闲状态。c/c++中采用的是显式分配器,及需要用户显式释放已分配的块。
malloc函数是由c标准库提供的,从堆中分配块的函数。malloc返回一个指针,指向大小至少为size字节的内存块,这个块满足最严格的内存对齐(32位系统中满足8字节对齐,64位系统中满足16字节对齐)。
关于碎片
造成堆利用率很低的主要原因是一种称为碎片的现象,有两种形式的碎片:内部碎片和外部碎片。内部碎片的产生:分配块比有效载核大。外部碎片的产生:当前空闲内存合计起来足够满足一个分配请求,但是没有一个单独的空闲块足够大能够处理这个请求。

以上基础内容取自《深入理解计算机系统》

每个unix进程都有一组特殊的线性区,这个线形区就是所谓的堆。
关于线性区的概念见:《深入理解linux内核》进程地址空间那一章
内存描述符的两个字段分别限定了这个区的开始地址和结束地址。

brk系统调用和mmap系统调用是系统层面的扩展程序线性区的两个系统调用,brk通过调整内存描述符中的堆结束地址字段,向内核申请物理内存或者swap区域的磁盘空间,建立新扩展的线性区和申请物理内存或者swap磁盘空间的映射关系。
mmap类似于brk,但是更为灵活,它不仅可以映射到物理内存和swap区域的磁盘空间,还能映射到磁盘上的文件。

malloc函数就是建立在这两个基本的系统调用上,以此我们可以想象,malloc主要负责维护空闲的堆内存,必要时通过brk或者mmap扩展线性区获得新的内存空间,当free被调用时,就将对应内存放入空闲内存集合中,等下次malloc时,如果有符合要求的空闲内存就分配出去,否则先进行空闲内存合并,如果合并后还是没有办法满足要求,那么就要通过brk或者mmap申请新的堆内存。
最简单的我们使用一个链表,将所有空闲内存(我们将其称为一个chunk,每个chunk代表了线性区的一段地址)串起来,但是这样做的效率实在太低,glibc采用如下结构进行内存管理:
把将一组chunk串起来的链表称为bin,malloc中有四种bin:
1.fast bin
2.unsorted bin
3.small bin
4.large bin
fast bin的存在就是为了快速的小内存的分配和释放。unsorted bin是重新利用最近释放的chunk机制,small bin和large bin按照存储chunk大小依次排列。

具体的分配策略见:https://blog.csdn.net/T146lLa...
https://blog.csdn.net/dongyu_...

本人并不是内核玩家,上述内容都是从其他博客中摘抄出来,仅作为记录和温习使用,因此也就不费力气多打字了。


p__n
491 声望10 粉丝

科学告诉你什么是不可能的;工程则告诉你,付出一些代价,可以把它变成可行,这就是科学和工程不同的魅力。