8/28 深入理解计算机系统笔记 动态内存分配
9.9 动态内存分配
动态内存分配器维护一个进程的虚拟内存区域,称为堆。
对于每个进程,内核维护一个变量brk,它指向堆的顶部。
分配器将堆视做一组不同大小的块的集合来维护。每个块就是一个连续的虚拟内存片,要么是已分配的,要么是空闲的。块被释放要么是应用程序显式的执行的,要么是内存分配器隐式的执行的。
显式分配器
隐式分配器:分配器检查一个以分配块何时不在被程序所使用,那么就释放这个块,隐式分配器也叫垃圾收集器。
malloc函数和free函数
在32位模式中,malloc返回的块的地址总是8的倍数,在64位模式中,该地址总是16的倍数。
calloc 初始化为0的分配
recalloc 改变分配大小
动态内存分配器可以使用mmap和munmap函数,显示的分配和释放堆内存,也可以使用sbrk函数。
void *sbrk(intptr_t incr)
sbrk通过内核的brk指针来增加incr来扩展或者收缩堆,incr可以是负数。
free()函数没有返回值,不会报错。
分配器的要求和目标:
要求:见书P590
目标:
最大化吞吐率
最大化内存利用率(参考峰值利用率)
碎片:
内部碎片:已分配块比有效载荷大的时候发生,是已分配的块大小和它们的有效载荷大小之差。
发生的情形大都是分配器为了满足当前请求模式的约束条件。
外部碎片:是空间空闲内存合起来可以满足一个分配需求但是没有一个单独的空闲块可以处理这个请求。
外部碎片难以量化,且不可预测,所以分配器通常采用启发式策略来试图维持少量大空闲块,而不是大量小空闲块。
分配器要把握好空闲块组织,放置新分配的块,分割空闲块的剩余部分,空闲块合并
隐式空闲链表
块需要数据结构区分 块边界,区分分配块和空闲块,包括满足对齐条件的填充区域。
头部 |
有效载荷 |
填充 |
放置以分配的块:
按照放置策略确定(首次适配,下一次适配,最佳适配等)详见书P594
获得额外的堆内存,调用sbrk函数,分配器会将额外的内存转化为一个大的空闲块,并插入空闲链表。
合并空闲块:
- 立即合并(可能会抖动)
- 推迟合并,直到某个请求分配失败,然后扫描整个堆,合并所有的块。
带边际标记的合并:
合并前面的块需要搜索整个链表,所以使用了边界标记:在块结尾处增加一个脚部,是头部的副本。
合并的时候要更新头部和脚部。
可以把以分配/空闲位的判断位放在多出来的位中,这样已分配的位就不需要脚部了,空闲位需要脚部。
9.9.12 实现一个简单的分配器见书P597-602
显式空闲链表
使用双向链表,使首次适配的分配算法的分配时间由块总数的线性时间变为了空闲块数量的线性时间。
按先进先出的顺序维护列表。
按地址的顺序维护列表。(内存利用率更高)
显式链表的空闲块要足够大来包含指针,提高了内部碎片的程度。
分离的空闲链表:
- 简单分离存储:把空闲块分割成大小相等的块,按需分配块的个数,不合并,不分割。
- 分离适配:分配器维护一个空闲链表数组,每个空闲链表和一个大小相关。每次去特定的链表查找,如果没有合适的就申请堆空间,分配后分割放置在链表里。
- 伙伴系统:把块按2的幂来分割,请求到向上舍入的最近的2的幂的块。搜索快,合并快,内部碎片大。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY