glibc堆内存管理

2023-11-07

glibc堆内存管理

背景

应用出现SIGABRT crash,报错信息为:malloc(): invalid size (unsorted),即是在应用调用malloc分配内存时出现异常导致的crash。

管理结构

进程虚拟地址空间被划分为代码段、数据段、堆和栈。其中,堆内存从低地址到高地址增长,并通过brk系统调用从系统中分配实际的物理内存空间,同时建立物理内存到虚拟内存的映射。堆内存在应用程序运行过程中存在反复的分配和释放过程,为提高内存的分配效率和减少内存碎片,需要利用相关的策略对堆内存进行管理。

Arena(分配区)

当一个进程中包含多个线程时,线程之间共享同一个堆内存空间(即共享资源)。由于linux中共享资源的申请需要获取对应的资源锁,同一时刻仅有一个线程能过获取到锁并操作共享资源,其他线程只能够等待锁的释放,这样就造成了堆内存分配和释放效率的低下。为解决这个问题,可为每个线程单独划分一片堆内存区域,用于各个线程的堆内存分配。基于这种思想,glibc中引入了Arena的概念,用于多线程的堆内存管理。其中,对于进程中的主线程来说,堆内存空间通过brk系统调用进行扩展,而对于其他线程的堆内存(即Arean)则通过mmap系统调用来扩展。如下图:
在这里插入图片描述

heap(堆)

brk和mmap每次以固定大小的size从系统中获取内存,对于主线程来说,每次都是通过brk系统调用申请内存,内存空间地址连续,但是对于其他线程对应的堆内存来说,每次通过mmap获取的内存空间不一定连续,所以为每一次通过mmap获得的内存增加一个heap_info的数据结构,并且通过prev指针将同属于一个Arena的各个heap_info连接起来,所有同属于一个Arena的heap_info的ar_ptr指向同一个Arena数据管理结构malloc_state。如下图:
在这里插入图片描述
malloc_struce的结构如下所示:

struct malloc_state
{
  /* Serialize access.  */
  __libc_lock_define (, mutex);

  /* Flags (formerly in max_fast).  */
  int flags;

  /* Set if the fastbin chunks contain recently inserted free blocks.  */
  /* Note this is a bool but not all targets support atomics on booleans.  */
  int have_fastchunks;

  /* Fastbins */
  mfastbinptr fastbinsY[NFASTBINS];

  /* Base of the topmost chunk -- not otherwise kept in a bin */
  mchunkptr top;

  /* The remainder from the most recent split of a small request */
  mchunkptr last_remainder;

  /* Normal bins packed as described above */
  mchunkptr bins[NBINS * 2 - 2];

  /* Bitmap of bins */
  unsigned int binmap[BINMAPSIZE];

  /* Linked list */
  struct malloc_state *next;

  /* Linked list for free arenas.  Access to this field is serialized
     by free_list_lock in arena.c.  */
  struct malloc_state *next_free;

  /* Number of threads attached to this arena.  0 if the arena is on
     the free list.  Access to this field is serialized by
     free_list_lock in arena.c.  */
  INTERNAL_SIZE_T attached_threads;

  /* Memory allocated from the system in this arena.  */
  INTERNAL_SIZE_T system_mem;
  INTERNAL_SIZE_T max_system_mem;
};

chunk(块)

对于同属于一个Arena的所有heap,可以将其抽象的视为一块地址连续的内存片段。当在该内存片段中申请释放内存时,通过chunk(块)对其进行管理。如下图:
在这里插入图片描述
对于已经分配的chunk称之为allocated chunk,分配之后释放过的称之为free chunk,未被分配的称之为top chunk。初始状态时,整个heap都是top chunk,经过分配之后产生allocated chunk,allocated chunk被释放之后产生free chunk。当再次进行堆内存分配时,为减少内存碎片,首先尝试从free chunk中进行分配,当free chunk中没有满足条件的内存片时,才从top chunk中进行分配。而对于free chunk来说,由于其size各不相同,在free chunk中查找满足条件的内存片段时,需要对free chunk进行遍历。为提高遍历的效率,glibc中对free chunk做了进一步的划分,用于管理不同大小的free chunk,管理的单元为bins。

bins(箱)

按照free chunk的大小,将各个free chunk交由如下的四种bin进行管理:

  • fast bin
    大小介于16 - 80 bytes的chunk被称之为fast chunk,每一种大小的fast chunk通过链表组成一个fast bin,所有的fast bin组成一个数组。并且,fast bin在内存分配时具有最高的优先级。其结构如下所示:
    在这里插入图片描述
    其中,fast bin通过malloc_chunk结构体中的fd指针链接成为一个单向链表,并且每一种fast bin内的chunk 大小相差8 bytes。

  • unsorted bin
    当small chunk和large chunk被释放时,并不是加入到small bin或者large bin,而是首先加入到unsort bin中。用于快速找到最近被释放的chunk以加速内存分配的效率。unsorted bin只有一个,并且unsorted bin内的chunk大小可以各不相同。unsorted bin和small bins以及large bins共用一个数组,如下图:
    在这里插入图片描述
    三种bin都是双向链表,其中数组中的元素只是记录了fd和bk两个指针,并不是一个malloc_chunk的结构体指针。如fd = 0x804b000指向前一个malloc_chunk,相对的前一个malloc_chunk的bk指针则应该指向本身,此时看前一个bk指针为0xb7fd8430,并非数组首地址0xb7fd8438,这里前一个malloc_chunk是将包含了fd的这个节点当做了malloc_chunk结构体看待,以保证双向循环链表操作函数的统一。

  • small bin
    大小小于512bytes的chunk为small chunk,由small chunk组成的双向链表为一个small bin。small bin共62个,占据数组的第2 - 124项,每一个bin中的small chunk的大小是相同的,并且相邻的bin包含的small chunk的大小相差8bytes。

  • large bin
    大小超过512bytes的chunk为large chunk,由large chunk组成的双向链表为一个large bin。large bin共63个,bin内包含的chunk大小可以不同,而是在一个范围内并且按大小的顺序排列在双向链表中。其中前面32个bin包含的large chunk的大小范围以64bytes递增,比如第一个large bin中的large chunk大小为512 到512 + 64 bytes,那么第二个bin的大小范围则是介于512 + 64 到512 + 128bytes之间,以此类推。后面的16个bin内的large chunk大小范围以512bytes递增,之后的8个bin内的large chunk大小范围以4096bytes递增,之后的4个bin内的large chunk大小范围以32768bytes递增,在此之后的2个bin内的large chunk大小范围则是以262144bytes递增,最后的1个bin其中的large chunk大小为保留项。

实际上,并不是每一个线程都对应一个Arena,当线程过多时会导致数据结构过度的资源占用。对于Arena的数量,一般有如下规定:

For 32 bit systems:
     Number of arena = 2 * number of cores.
For 64 bit systems:
     Number of arena = 8 * number of cores.

当线程的数量超过Arena的数量时,新的线程将遍历各个Arena,并且尝试获取该Arena的锁,如果获取到了就直接从中分配,如果没有便等待。当下一次再次尝试分配堆内存时,该线程将直接从此前分配成功的Arena中分配(如果没有获取到锁,便等待),并不会重新再次遍历所有的Arena。

内存合并

内存合并是指当与free chunk相邻的allocated chunk被释放时,将allocated chunk和相邻的free chunk合并为一个新的free chunk,并根据free chunk的大小和类型进行转移(如转移到unsorted bin/small bin)。其中,fastbin中的free chunk不会进行合并,以保证fast bin中的chunk足够碎片化,能够尽快满足小内存的申请。当allocated chunk释放的时候怎么去判断相邻的前后是否是free chunk呢?这一点通过chunk的数据结构来实现:

struct malloc_chunk {

  INTERNAL_SIZE_T      mchunk_prev_size;  /* Size of previous chunk (if free).  */
  INTERNAL_SIZE_T      mchunk_size;       /* Size in bytes, including overhead. */

  struct malloc_chunk* fd;         /* double links -- used only if free. */
  struct malloc_chunk* bk;

  /* Only used for large blocks: pointer to next larger size.  */
  struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
  struct malloc_chunk* bk_nextsize;
};

malloc_chunk结构体内的元素含义根据allocated chunk和free chunk的不同而有所差异:对于allocated chunk来说,它只需要记录用于和free chunk合并的信息,而对于free chunk来说,则需要利用malloc_chunk将自身链接入不同的bin中。allocated chunk和free chunk对应的malloc_chunk结构体示意图分别如下:
在这里插入图片描述
判断一个chunk是否为free chunk则根据mchunk_size后的标志位来判断:P(PREV_INUSE),表示当前chunk的前一个chunk被分配了,即为allocated chunk;M(IS_MAPPED),表示该chunk被mapped;N(NON_MAIN_ARENA),表示该chunk属于thread arena而不是mian arena(即主线程的arena)。

当allocated chunk之前为一个free chunk,则此时allocated chunk的头部malloc_chunk的P标志位被置1,其中的mchunk_prev_size则记录了之前free chunk的size,根据当前allocated chunk的地址和mchunk_prev_size,则可以计算出free chunk的起始地址了。而判断当前allocated chunk之后的一个chunk是否为free chunk,则可以直接通过当前allocated chunk的size和地址,计算出下一个chunk的地址,然后根据该处的malloc_chunk中的标志位进行判断。对于allocated chunk来说,malloc_chunk的其他元素都没有作用,用户可以直接使用该部分空间。

对于free chunk来说,因为要将free chunk分类到不同的bin中,所以其中有两个链表节点,用于和其他chunk组成链表,用于堆内存分配时的chunk索引。

回到开头的问题,报malloc(): invalid size (unsorted)的错误,是由于在对chunk的size进行检查的时候,发现其size异常,导致的主动abort,其代码位置:

      while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
        {
          bck = victim->bk;
          size = chunksize (victim);
          mchunkptr next = chunk_at_offset (victim, size);

          if (__glibc_unlikely (size <= 2 * SIZE_SZ)            //SIZE_SZ为malloc_chunk的size
              || __glibc_unlikely (size > av->system_mem))      //av->system_mem为arena的内存大小,chunk的size必须介于两者之间
            malloc_printerr ("malloc(): invalid size (unsorted)");

参考

  1. Understanding glibc malloc
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

glibc堆内存管理 的相关文章

随机推荐

  • [echarts]柱状图的点击事件

    先来一段简洁的写echarts图表的代码 这样获取echarts的dom节点是因为 如果将柱状图封装成了一个组件 在一个页面中多次使用 若还是按常规获取dom节点 会报一个警告 let charts echarts getInstanceB
  • Linux驱动开发(应用程序如何调用驱动)

    1 添加读写接口 1 在应用代码中 2 在驱动代码中 2 应用和驱动之间的数据交换 1 copy from user 用来将数据从用户空间复制到内核空间 2 copy to user 用来将数据从内核空间复制到用户空间 3 write和re
  • DAPM之一:概述

    DAPM Dynamic Audio Power Management 对应结构体是snd soc dapm widget和snd soc dapm route 对应的操作函数是snd soc dapm new controls snd s
  • C++对象模型和this指针

    C 对象模型和this指针 成员变量和成员函数分开存储 在C 中 类内的成员变量和成员函数分开存储 只有非静态成员变量才属于类的对象上 include
  • 逐梦C++补遗篇之一:cout与cerr的区分

    逐梦C 补遗篇之一 cout与cerr的区分 1 从定义看区别 cout 标准输出流 带缓冲 默认输出目的地为屏幕 可以被重定向 cerr 标准错误输出 不带缓冲 输出目的地为屏幕 一般不被重定向 缓冲 带缓冲 就是系统会为你分配一个缓冲区
  • 精彩观点一览

    7月20日下午 大模型的发展路径论坛于北京成功举办 大模型的发展路径论坛作为2023中国互联网大会的分论坛之一 由中国互联网协会人工智能工作委员会承办 中国信通院云计算与大数据研究所 华为云大数据与AI业务协办 并得到阿里云 北京智源研究院
  • 理解什么是 JMM

    理解什么是 JMM 本文已收录至 GitHub https github com yifanzheng java notes Java 虚拟机是一个完整的计算机的一个模型 因此这个模型自然也包含一个内存模型 Java 内存模型 也就是说 J
  • 最短路算法——Dijkstra

    Dijkstra 在大多数最短路径问题中 Dijkstra 算法是最常用 效率最高的 它是一种 单源 最短路径算法 一次计算能得到从一个起点 s 到其他所有点的最短距离长度 最短路径的途径点 一 Dijkstra的算法思想 Dijkstra
  • Upload LABS Pass-6

    第六关在后端使用了黑名单 并过滤了大小写和点 但未过滤空格 我们使用代理抓包在后缀名中添加空格 即可绕过黑名单 准备一个 6 php 文件 内容为一句话木马 上传 6 php 文件 并开启代理 此处使用 Burp Suite 拦截请求 在文
  • python旋转矩阵_python将四元数变换为旋转矩阵

    import numpy as np from autolab core import RigidTransform 写上用四元数表示的orientation和xyz表示的position orientation y 0 697127881
  • java代码的四层结构

    一 util包 放共同类的包 整个项目中 可以共用的一些代码 例如 一些常用的字符串的非空验证 身份证或者电话号码的正则验证等等 1 JDBC类功能的封装 package util import java io IOException im
  • VsCode中修改/重置gitlab远程仓库地址

    A 更换git远程仓库地址 1 查看当前remotes git remote v 2 修改remotes git remote set url origin https github com test test git B 重置git远程仓
  • Spring Boot中单元测试数据库的切换策略

    问题缘起 单元测试默认情况下使用嵌入式数据库 例如H2 如果要切换为MySQL 直接移除H2驱动 在application properties yml 配置相应的连接信息 都不起作用 那该如何切换配置呢 单元测试数据库 在SpringBo
  • 如何给Python写注释

    给程序写注释是一个很好的习惯 提高程序的可读性 写注释是不可少的步骤 Python与其他语言一样提供了两种注释方法 单行注释和多行注释 单行注释 Python中使用 进行单行注释 这里是一个单行注释 print 翔宇亭IT乐园 www bi
  • ap忘记管理ip地址怎么办_路由器刷集客固件,低成本实现AC+AP组网

    某讯K2T路由器刷集客固件 可以作为无线AP使用 多个K2T实现无缝漫游功能 通过微AC或者ESXI安装集客AC可以实现对AP进行管理 低成本的实现AC AP组网 确定版本号140版本 158以上版本的需要拆机 操作步骤 第一步 开启tel
  • 2019中国城市排名出炉——2019新一线城市有没有你的家乡!?

    新一线城市研究所在上海发布 2019城市商业魅力排行榜 新一线城市研究所收集了170个主流消费品牌的商业门店数据 18家各领域头部互联网公司的用户行为数据和数据机构的城市大数据 对337个中国地级及以上城市进行了评估 排行榜沿用了上一年的五
  • java集合框架

    这图画的真洒脱 光一个stack就有很多可探索了
  • 那些年踩过的坑——服务器中文路径

    从11年学编程至今已有十个年头 其实有时候也很后悔选择这个专业 整日和电脑相偎相依 人的思维和沟通能力也趋向机器 和别人聊天也不知道怎么开口 算法的一个评定标准就是以最少的语句实现所需的功能 但和别人聊天则不能这样 太直接简单会让人变得无趣
  • 总结了9款Mac端超好用的免费开源软件,你还有更好的推荐吗?

    与Windows相比 Mac上的软件 不仅不稀缺 并且大多数都更加精致 还没有乱七八糟烦人的弹窗骚扰 所以 本期就为大家盘点盘点Mac上有超好用的免费开源神器 1 Tincta 官网 https codingfriends github i
  • glibc堆内存管理

    glibc堆内存管理 背景 应用出现SIGABRT crash 报错信息为 malloc invalid size unsorted 即是在应用调用malloc分配内存时出现异常导致的crash 管理结构 进程虚拟地址空间被划分为代码段 数