malloc作为一个库函数,用于根据开发人员的需求在堆上动态分配内存。根据需要分配的内存大小,实现方式分以下两类:
申请:初始时,进程会有一个初始大小的堆空间。brk指针(_enddata)指向堆空间的堆顶,通常通过空闲链表和位图管理这些空闲内存。当需要分配的空间小于128k时,将在堆上分配对应内存空间。malloc函数首先遍历已管理的堆空间(brk指针指向地址以下),若存在空闲的内存能满足所需大小,则分配该部分内存,完成内存分配。注意此时作为库函数malloc并未调用系统调用,仅仅在用户态空间即完成了内存分配。
当遍历所管理的所有空闲内存空间后发生没有能满足需要的,则调用系统调用brk函数增加brk指针(_enddata),即扩大堆空间以满足需要。
释放:当调用free释放上面所申请的内存时,malloc会将该部分内存回收(仍然用空闲链表或者位图管理)。注意,此时该部分内存并未真正意义上回收,内核端认为该内存处于使用状态,对应的物理页仍然对应该部分虚拟内存映射(通常所说的内存碎片)。若此时产生了新的内存分配需求,而该部分内存能满足需要,则分配该部分内存。当释放该部分内存后,堆顶指针brk附近的连续空闲内存大于128K时,将进行真正意义上的内存回收操作。
调用malloc时,只是分配了对应的虚拟地址空间。只有当访问该部分内存时才会真正分配物理内存并将物理内存和虚拟内存建立映射关系,并且根据实际使用多少内存分配多少物理内存。通过这种策略大大提高了内存使用率。
从上面可以看出,当分配的内存小于128k时,malloc函数扮演了一个类似于代理商的角色,它从工厂(内核)获取大批量内存,然后根据每次实际使用需求进行零售。使用brk分配内存有如下优势:
当分配的内存大于128k时,将调用调系统调用mmap函数分配。此时分配的内存位置也和上面不一样,不再扩展brk指针(_enddata),而是直接在堆和栈之间区域(文件映射区域)分配一块虚拟内存。当调用free接口时,即调用munmap系统调用接口直接释放该部分内存。通过mmap单独解决大块内存分配需求有如下优势:
根据malloc/free调用实例进行说明。如下图所示: