前言:从malloc的内存方式、内存池管理、brk系统调用过程这三个点,讲述malloc系统调用的底层原理和流程。
一、malloc的内存申请方式
malloc申请内存有两种,申请小于128KB的内存是用brk系统调用,申请大于128KB就是用mmap的匿名映射。(brk和mmap申请的都是懒分配内存,只有真正访问时才分配物理内存)
- 为什么要有brk方式
mmap是系统调用,频繁的系统调用会让CPU性能下降。所以就通过brk系统调用,每次malloc申请内存时,brk会给malloc分配比所需内存更大的内存池,然后malloc后面就可以自己操作内存池来申请或释放内存,而无需再调用系统调用。所以对于brk方式的malloc,使用free并不会直接将内存归还系统,而是留在内存池等进程结束才归还。
二、malloc的内存池管理
注1:只有brk方式申请的内存会有内存池机制,mmap并没有这个机制。
注2:下面只是简单说明malloc内存管理最主要的机制,省略了一些其他的机制或细节
1、分级内存池
malloc使用分级线程池,将不同大小的内存块分为不同等级,每个等级有对应的一个空闲链表来管理。malloc会根据每次请求的大小来选择合适的级别,从链表中分配内存。链表的遍历可以是从头到尾找到第一个满足请求的大小进行分配,也可以是找到大小最接近的块来分配。
2、内存块信息
在每块内存的开头处会有对应的内存块信息,包括内存块的大小、空闲链表的下一块内存的指针、以及内存是否被分配。free调用就是根据这里来得知释放内存的大小。
3、内存块合并
当内存块被释放时,如果相邻块也是空闲的,malloc会进行块合并来减少内存碎片。在高频率的内存分配和释放场景中,这个操作会影响性能
4、内存块分割
当 malloc在空闲链表中找到一个比请求大小大的空闲块时,它可能会将这个块分割成两部分,一部分分配给请求的内存,另一部分仍然保持空闲状态。但这个频繁的分割操作可能会导致小的内存块散布在内存中,增加内存碎片化
三、brk的内存分配过程
对于malloc,brk每次以页为单位来给malloc分配内存池,之后让malloc自己管理和操作内存池的内存,直到内存池不够了再调用brk来扩充内存池。brk分配内存的大致过程就是将堆区内存向上扩展,具体流程这里以xv6操作系统为例(一个类Unix的简单系统)。注:看懂这里需要有一定的页表基础
1、空闲链表
和malloc的内存池管理方式类似,xv6系统在每个空闲页面的内存开头处存放一个run结构体,该结构体存储着一个指向下个空闲页起始地址的指针next。由于run的地址和页面地址相同,所以xv6可以通过遍历run链表来找到所有空闲页。这个run链表叫做allocator分配器。
2、sbrk
sbrk是xv6系统中的系统调用,用于给进程增加或减少堆区内存。分配内存主要就是做两件事:1、从空闲链表中删除一张空闲内存页并返回给进程;2、把内存页的虚拟地址和物理地址的映射关系写到页表PTE上并设置标志位。减小内存同理,主要就是:1、把对应页表项清空(设置PTE_V);2、根据该页面的物理地址创建run结构体并插入空闲链表。
四、malloc的缺陷
1、不支持多线程环境;2、由于内存块分割操作,频繁malloc会导致内存碎片化;3、由于内存块合并操作,频繁free会一直触发影响性能。
解决方法:自己实现一个内存池来替代malloc