Glibc之malloc实现原理

前言导入

内存管理之虚拟内存空间 详细了解这部分知识,再看下面的内容会很舒服

进程地址空间

以Linux内核2.6.7以前的进程内存布局为例,如下图所示(之后的内核,内存共享区是向上增长的)。

  • 在32位Linux系统中,进程地址空间是这样分布的。其中内核空间独占1G,不允许用户操作,其余3G由用户操作。
  • malloc的操作对象:堆是向上增长的,与之对应的共享区则是向下增长的。
    在这里插入图片描述

进程控制块mm_struct

图中标出的是堆区的起始地址,结束地址以及栈区的起始地址。
在usr/src/linux-headers-5.15.0-76/include/linux/mm_types.h下
在这里插入图片描述

几个系统调用

操作系统提供了几个相关的系统调用来完成内存分配工作。

  1. 对于堆(heap),OS提供了int brk(void *addr);函数,C库提供了void *sbrk(int* increment);)函数。其中,malloc使用的就是sbrk
  2. 对于映射区域(mmap)的操作,操作系统提供了void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);``int munmap(void *addr, size t length)两个函数
    在这里插入图片描述
    在这里插入图片描述

对于堆

由man手册中查到的信息:

  • 堆是处于数据段中的动态分配内存的区域,brk,sbrk是用来设置未初始数据段的末尾位置的下一个位置。
  • 其中 brk 通过形参指定堆的起始位置,如果该位置合理的话(每个进程有一个rlimit参数用于表示该进程可用资源的上限)。
  • sbrk 则通过传入的参数,向上移动指定字节来扩展为未初始数据段的范围。
  • malloc通常是通过sbrk(0)来获取当前program break的位置,然后sbrk(size)来扩展堆的大小。sbrk是brk的一个包装函数。
    所以brk直接设置的是进程整个数据段的结尾,而不仅仅是堆区。堆区只是data segment中的一部分,是malloc等函数用于动态内存管理的空间。

对于内存映射区

mmap函数实际有两个功能:

  1. 我们常用的动态库就被加载到内存映射区,所有程序共享。这是从磁盘到内存,属于文件映射,用动态库文件填充物理内存。
  2. 用户使用malloc申请大内存时调用到了mmap,这种mmap的使用方式叫做匿名映射。
    • 这种方式不属于映射磁盘文件到内存中,而是进程向内存申请一块空间,访问到了这块空间,触发缺页中断再分配实际物理内存给用户,不需要的时候就释放了,与heap的使用方式相同。
    • 与动态库的映射不同,这种方式使用的mmap申请到的空间,进程间不共享,当对这块空间修改时,会触发写时拷贝。
    • munmap用来释放之前使用mmap申请的映射区域

malloc原理:Linux下的内存管理机制 ptmalloc

内存池

  • malloc使用 ‘块’ (chunk)来进行管理动态分配的内存
  • 由于以上几个接口都是系统调用,用户使用malloc申请内存,那么就意味着频繁的用户内核态的切换,这就会导致性能上的极大降低,因此,ptmalloc 采用了类似于STL的空间配置器使用的内存池的方式进行管理。

chunk结构

glibc中malloc目录下的malloc.h中:
在这里插入图片描述

  • 首先,空闲chunk和非空闲chunk在结构上是不一样的,非空闲字段会把某些用不上的字段当做程序能够使用的空间。
  • 空闲chunk
    在这里插入图片描述
    使用中的chunk
    在这里插入图片描述
  • mchunk_prev_size: 如果前一个chunk是空闲的,则该字段表示前一个chunk的大小,如果前一个chunk不空闲,该字段无意义。一段连续的内存被分成多个chunk,prev_size记录的就是相邻的前一个chunk的size,知道当前chunk的地址,减去prev_size便是前一个chunk的地址。prev_size主要用于相邻空闲chunk的合并。
  • malloc_chunk_size :当前 chunk 的大小,并且记录了当前 chunk 和前一个 chunk 的一些属性,包括前一个 chunk 是否在使用中,当前 chunk 是否是通过 mmap 获得的内存,当前 chunk 是否属于非主分配区。
  • malloc_chunk_fd 和 malloc_chunk_bk :指针 fd 和 bk 只有当该 chunk 块空闲时才存在,其作用是用于将对应的空闲 chunk 块加入到空闲chunk 块链表中统一管理,如果该 chunk 块被分配给应用程序使用,那么这两个指针也就没有用,被当作应用程序的使用空间,而不至于浪费。
  • malloc_chunk__fd_nextsize 和 malloc_chunk_bk_nextsize: 当前的 chunk 存在于 large bins 中时, large bins 中的空闲 chunk 是按照大小排序的,但同一个大小的 chunk 可能有多个,增加了这两个字段可以加快遍历空闲 chunk ,并查找满足需要的空闲 chunk , fd_nextsize 指向下一个比当前 chunk 大小大的第一个空闲 chunk , bk_nextszie 指向前一个比当前 chunk 大小小的第一个空闲 chunk 。(同一大小的chunk可能有多块,在总体大小有序的情况下,要想找到下一个比自己大或小的chunk,需要遍历所有相同的chunk,所以才有fd_nextsize和bk_nextsize这种设计) 如果该 chunk 块被分配给应用程序使用,那么这两个指针也就没有用(该chunk 块已经从 size 链中拆出)了,所以也当作应用程序的使用空间,而不至于浪费。
  • 其中 AMP字段的意义:
    • chunk指针指向chunk开始的地址
    • mem指针指向用户内存块开始的地址。
    • p=0时,表示前一个chunk为空闲,prev_size才有效
    • p=1时,表示前一个chunk正在使用,prev_size无效 p主要用于内存块的合并操作;ptmalloc 分配的第一个块总是将p设为1, 以防止程序引用到不存在的区域
    • M=1 为mmap映射区域分配;M=0为heap区域分配
    • A=0 为主分配区分配;A=1 为非主分配区分配。

分配区

  • 在ptmalloc中,分配区分为主分配区和非主分配区。
  • 主分配区和非主分配区的区别是:主分配区可以使用sbrk和mmap向OS申请内存,而非分配区只能通过mmap向OS申请内存。

主分配区

在这里插入图片描述

  • 因为有内存池的存在,用户调用free函数释放内存的时候,ptmalloc并不会立即将其归还操作系统。在主分配区中包含一个bin数组(即空闲链表),该bin数组管理者内存池中申请的内存。
  • bin数组每个元素是一个固定大小的chunk,每个块又是一个双向链表的结构,挂接着同样大小的块的地址。
  • bin数组中存着不一样大小的块,类似于下图(small bins-画的是单向链表,实际是双向的)
    small bins
  • malloc在实现bin时,把数组分为了不同的bin种类。
  • 数组中第一个元素为unsorted bin,类似于缓冲区,运用了局部性原理。当一块空间被释放了,这块空间返还给内存池,但是由于局部性原理,这个大小的空间可能再一次被申请,那么malloc就直接先到这里面找,如果找不到再去找其他大小的chunk元素。
  • 数组编号前2到前64的bin为 small bins,small bins 是 62 个双向循环链表,并且是 FIFO 的。
  • 64-128 为 large bins。是 63 个双向循环链表,large bin的每个bin相差64字节,且同一个bin中的chunk大小也不一样,所以插入和删除可以发生在任意位置,内存分配器会找到最符合申请的大小的chunk进行分配。。large bins中的每一个bin分别包含了一个给定范围内的chunk,其中的chunk按大小序排列。
  • fast bin。用于提高小内存分配效率。这个bin并不在bins数组中,不被bins数组维护。程序在运行时会经常需要申请和释放一些较小的内存空间。当分配器合并了相邻的几个小的 chunk 之后,也许马上就会有另一个小块内存的请求,这样分配器又需要从大的空闲内存中切分出一块,这样无疑是比较低效且易导致内存碎片的,故而,malloc 中在分配过程中引入了 fast bins,在申请非常小的内存时从这里面找,省去了分割chunk的开销。
    同时,这里面的chunk由于P字段一直被置为1,即指示该chunk的前一个位置的chunk的使用状态一直是非空闲状态,导致前一个位置的chunk永远不会被合并,保证了没有分割chunk的开销。

以上是bins数组维护的常规块

但实际上还有一些非常规块,用来满足上面几种bin不能满足分配的情况
在这里插入图片描述

  • top chunk
    top chunk是堆最上面的一段空间,不被bin维护,当所有的bin都无法满足分配要求时,就要从这块区域里来分配,将top chunk 切割成两部分,一部分用来满足用户的内存申请,另一部分变成last remainder chunk。last remainder chunk成为新的top chunk。如果top chunk的空间也不满足用户的请求。就要使用brk扩充数据段的空间或者使用mmap映射一段空间。
    在free chunk的时候,如果chunk size不属于fastbin的范围,就要考虑是不是和top chunk挨着,如果挨着,就要合并到top chunk中。

  • mmaped chunk
    当分配的内存非常大(大于分配阀值,默认128K)的时候,需要被mmap映射,则会放到mmaped chunk上,当释放mmaped chunk上的内存的时候会直接交还给操作系统。(chunk中的M标志位置1)

  • last remainder chunk
    位于堆的顶部,用于指示堆区域的结束。当有小块内存请求且在small bin中找不到符合要求的chunk时,last remained chunk会被分割以服务,一部分返回给用户,剩下的成为新的last remainder chunk。它的存在使得连续的小空间内存申请,分配到的内存都是相邻的,从而达到了更好的局部性。
    而当需要扩展堆时,last remained chunk会被去除last remained标记,然后进行brk系统调用移动堆末尾指针来扩展堆。
    同时,它把堆的最高地址作为边界,防止堆上分配的chunk溢出到其他数据段。
    由于last remained chunk在末尾,当相邻的chunk释放时,可以通过它们融合成一个更大的空闲快。

malloc分配堆空间流程

  1. 先从fast bin中判断,如果大于global_max_fast宏时,就会去unsorted bin中找,即缓冲区作用的bin中找,如果没有找到就去small bin,还没找到就去large bin。
  2. 如果在这bins里面都没有找到的话,就去topchunk超大块里面找
  3. 如果还小了就用mmap去内存映射区映射一块内存出来
  4. 具体malloc申请多大空间从哪里获取内存,这里推荐一个视频,最后详细介绍了按照多少字节走哪个bin进行分配,该up在文末画了一张流程图,很详细。

参考资料:
https://zhuanlan.zhihu.com/p/428216764【源码分析,贴合作者工作中bug万字详解malloc】
https://samwho.dev/memory-allocation/【英文文档,可视化方式介绍内存分配】
https://jacktang816.github.io/post/mallocandfree/【简单易懂】

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/263721.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Python网络爬虫入门教程2】成为“Spider Man”的第二课:观察目标网站、代码编写

Python 网络爬虫入门:Spider man的第二课 写在最前面观察目标网站代码编写 第二课总结 写在最前面 有位粉丝希望学习网络爬虫的实战技巧,想尝试搭建自己的爬虫环境,从网上抓取数据。 前面有写一篇博客分享,但是内容感觉太浅显了…

深度学习与计算机视觉技术的融合

深度学习与计算机视觉技术的融合 一、引言 随着人工智能技术的不断发展,深度学习已经成为了计算机视觉领域的重要支柱。计算机视觉技术能够从图像和视频中提取有用的信息,而深度学习则能够通过学习大量的数据来提高计算机视觉技术的性能。本文将探讨深…

熔池处理Tecplot 360 和CFD-Post做出一样的效果

熔池处理Tecplot 360 和CFD-Post做出一样的效果 效果展示详细讲述Tecplot 360实现过程分析实现过程第一步实现过程第二步界面美化注意点效果展示 详细讲述Tecplot 360实现过程 分析 这里主要是将体积分数大于0.5的区域抽取出来,然后显示温度场,所以这里主要考虑下面连个思考…

四:爬虫-Cookie与Session实战

四:Cookie与Session实战 ​ 在浏览网站的过程中,我们经常会遇到需要登录的情况,有些页面只有登录之后才可以访问。在登录之后可以连续访问很多次网站,但是有时候过一段时间就需要重新登录。还有一些网站,在打开浏览器…

logback的使用

1 logback概述 SLF4J的日志实现组件关系图如下所示。 SLF4J,即Java中的简单日志门面(Simple Logging Facade for Java),它为各种日志框架提供简单的抽象接口。 SLF4J最常用的日志实现框架是:log4j、logback。一般有s…

SpringBoot 官方脚手架不再支持Java8和Java11

Spring 官方脚手架不再支持初始化 Java8 和 Java 11 项目,目前仅支持初始化Java17 和 Java21 项目。 阿里巴巴Spring脚手架支持初始化Java8、Java11、Java17、Java19 的项目,不支持初始化Java21的项目。

【数据结构】——排序篇(下)

前言:前面我们的排序已经详细的讲解了一系列的方法,那么我们现在久之后一个归并排序了,所以我们现在就来讲解一下归并排序。 归并排序: 归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法…

MySql复习笔记03(小滴课堂) 事务,视图,触发器,存储过程

mysql 必备核心知识之事务的详细解析: 创建一个数据库表: 添加数据并开启事务。 添加数据并查询。 登录另一台服务器发现查不到这个表中的数据。 这是因为事务开启了,但是没有提交,只是把数据存到了内存中,还没有写入…

TypeScript 之 console的使用

语言: TypeScript 在线工具: PlayGround console console 对象是一个非常强大的控制台日志显示工具, 可以帮助我们在浏览器中调试代码。 注: console不属于TypeScript的语法,而是由JavaScript封装的内置对象。 简单的…

202301209将RK3399的挖掘机开发板在Android10下设置系统默认为24小时制

202301209将RK3399的挖掘机开发板在Android10下设置系统默认为24小时制 2023/12/9 22:07 应该也可以适用于RK3399的Android12系统 --- a/frameworks/base/packages/SettingsProvider/res/values/defaults.xml b/frameworks/base/packages/SettingsProvider/res/values/default…

AttributeError: ‘bool‘ object has no attribute ‘sum‘

AttributeError: ‘bool’ object has no attribute ‘sum’ AttributeError: ‘bool’ object has no attribute ‘sum’ 解决方法 将torch.max()改为torch.argmax()查看output和targets的数据类型是否都为tensor 以上就是全部内容&#…

智能优化算法应用:基于驾驶训练算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用:基于驾驶训练算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用:基于驾驶训练算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.驾驶训练算法4.实验参数设定5.算法结果6.参考…