malloc和free的原形如下:
在c的标准中并没有定义这两个函数的具体实现,在我们最常用的gcc中,malloc使用的是ptmalloc的实现,最早由Doug Lea完成,并被Wolfram Gloger改进以支持多线程。
malloc的具体实现过于复杂,这里就不具体展开了。
总体上说,ptmalloc的内存管理是基于内存池的,而它的内存来源有两种:
1 通过brk()获得
2 通过mmap()匿名映射获得
这两种获得内存的方式分别对应于进程地址空间的不同部分。64位linux进程的默认内存布局如下:
对于其中的Heap区域,就是我们通常意义上的堆空间,这部分内存将通过brk()获得。brk()的原理非常简单,只涉及指针的上下移动,也就是只分配虚拟地址空间,而不分配物理内存,当实际访问到此内存时,将触发page fault异常由 *** 作系统完成物理内存的分配。由于 *** 作简单,所以brk的效率非常高。
而另一个Memory Mapping Region区域,简称mmap区域,将通过 *** 作系统的mmap()函数获得。mmap区域不仅可以提供内存的分配,还可以映射文件,比如通过mmap打开文件,或者加载so文件等等。
mmap函数的原形如下:
其中的flags,在使用mmap分配内存时,设置为MAP_ANONYMOUS,也就是匿名映射。当使用mmap()映射内存时, *** 作系统默认将对这块内存执行清0 *** 作,因此相比于brk,mmap的效率相对较低。
基于内存池的内存管理,本质上都是把变长的内存分配转换为定长的内存分配。因为只有定长,才可以复用。
ptmalloc的内存池结构大致如下:
可以看出,内存的分配将根据实际的大小,选择定长块。比如请求的内存是23字节,那么将分配一块24字节的内存,而请求的如果是25字节,那么将分配一块32字节的内存(对于大于512字节的数据,统一存储在large bin中)。具体的分配策略就不展开了。
每一种大小的内存,都组成了一个一个的队列,在这些队列里维护了一个双向链表,将所有的小块内存串联起来。每一个小块内存(chunk)的结构如下:
上面的这个结构是一块在使用中的内存的状态。其中的mem部分,则是返回给用户的void*指针位置。而最开始的两个结构:size of previous chunk,和 size of chunk,则用于维护全局内存的链表。
之所以说是全局内存的链表,是由于内存分配时是先向系统请求一个比较大的内存块(64位系统一般为64Mb),之后从这64Mb内存中切出用户需要的大小分配给用户。而为了维护分配出去的内存块之间的关系,通过前两个结构来使所有内存块构成一个大的链表,当回收内存时,通过这个全局链表,将所有空闲内存组合起来,还给 *** 作系统,其中有3个标记位:A,M,P,P标识前一块内存是否空闲。
下面的结构就是一块空闲内存的状态:
相比于使用中的状态,空闲部分的内存增加了4个新的结构(Forward pointer to next chunk in list 等,其中fd_nextsize和bk
_nextsize只存在于large bin中),这4个结构用于维护每个定长内存队列的双向链表结构,这个链表的存在主要是为了分配时查找内存时足够便利,可以基本上保证分配内存时的平均复杂度维持在O(1)。
在有了前面所有的介绍之后,可以总体上描述一下malloc和free的基本流程:
当用户向ptmalloc请求内存时:
1 首先查找定长内存分配池,如果查找到则返回
2 如果没有空闲内存可供使用,则向 *** 作系统申请一块64Mb的内存,从中切出用户需要的内存,返回
当用户调用free释放内存时:
1 直接将内存放入适当的定长内存池队列
2 如果触发了一定的条件,则将所有空闲内存合并,如果满足释放条件,将内存全部还给 *** 作系统
当然了,上面的描述中省略了太多的细节。比如什么时候走brk什么时候走mmap, 再比如当请求的内存大于一个阙值时,ptmalloc将会变成一个mmap的简单封装,还有触发内存归还 *** 作系统的条件等等。
不过已经足够回答题目中的问题了:因为malloc的时候记录了大小。
这里还可以得出另一个结论:由于malloc的时候记录了大量的状态,所以在频繁使用malloc分配小内存时,会造成大量的内存浪费。举例来说,当反复malloc(1)时,每一次分配的内存在32字节:包括size of previous chunk,size of chunk,bk_chunk_pointer,fd_chunk_pointer共4个指针,合计4 * 8 = 32字节....
1、内存是什么?
1) 内存又称主存,是 CPU 能直接寻址的存储空间,由半导体器件制成;
2) 内存的特点是存取速率快,断电一般不保存数据,非持久化设备;
2、内存的作用
1) 暂时存放 cpu 的运算数据
2) 硬盘等外部存储器交换的数据
3) 保障 cpu 计算机的稳定性和高性能
1、linux 内存地址空间 Linux 内存管理全貌
2、内存地址——用户态&内核态
3、内存地址——MMU 地址转换
4、内存地址——分段机制
1) 段选择符
更多Linux内核视频教程文档资料免费领取后台私信【 内核 】自行获取。
内核学习网站:
Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议栈-学习视频教程-腾讯课堂
2) 分段实现
5、内存地址——分页机制(32 位)
6、用户态地址空间
7、内核态地址空间
8、进程内存空间
内存管理算法 ——对讨厌自己管理内存的人来说是天赐的礼物
1、内存碎片
1) 基本原理
2) 如何避免内存碎片
2、伙伴系统算法——组织结构
1) 概念
2) 外部碎片
3、伙伴系统算法——申请和回收
1) 申请算法
2) 回收算法
3) 条件
4、如何分配 4M 以上内存?
1) 为何限制大块内存分配
2) 内核中获取 4M 以上大内存的方法
5、伙伴系统——反碎片机制
1) 不可移动页
2) 可回收页
6、slab 算法——基本原理
1) 基本概念
2) 内部碎片
7、slab 分配器的结构
详细参考:
经典|图解Linux内存性能优化核心思想
8、slab 高速缓存
1) 普通高速缓存
2) 专用高速缓存
9、内核态内存池
1) 基本原理
2) 内核 API
10、用户态内存池
1) C++ 实例
11、DMA 内存
1) 什么是 DMA
2) DMA 信号
out of memory 的时代过去了吗?no,内存再充足也不可任性使用。
1、内存的使用场景
2、用户态内存分配函数
a) 如果当前连续内存块足够 realloc 的话,只是将 p 所指向的空间扩大,并返回 p 的指针地址。这个时候 q 和 p 指向的地址是一样的
b) 如果当前连续内存块不够长度,再找一个足够长的地方,分配一块新的内存,q,并将 p 指向的内容 copy 到 q,返回 q。并将 p 所指向的内存空间删除
3、内核态内存分配函数
4、malloc 申请内存
5、缺页异常
6、用户进程访问内存分析
7、共享内存
1) 原理
2) shm 接口
1、C 内存泄露
2、C 野指针
3、C 资源访问冲突
4、STL 迭代器失效
错误示例:删除当前迭代器,迭代器会失效
正确示例:迭代器 erase 时,需保存下一个迭代器
5、C++ 11 智能指针
(1)原理分析:
(2)数据结构:
(3)使用方法:
6、C++ 11 更小更快更安全
六、 如何查看内存
可以通过 cat /proc/slabinfo 命令查看
可以通过 /proc/sys/vm/drop_caches来释放
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)