0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

glibc内存管理存在的共性问题及解决方法

Linux阅码场 来源:Linux阅码场 作者:刘冬云 2021-06-18 14:50 次阅读

引言

对于嵌入式设备来说,用户态内存管理是一项基础功能,目前主流的用户态内存管理库有glibc、uclibc、tcmalloc、jemalloc等。

本文基于glibc2.17版本进行分析,围绕glibc内存分配原理、内存站岗问题形成原因展开讨论,并对glibc缓存大量内存(高达几十个 G甚至上百 G)且不释放的问题给出一种解决方案。

笔者遇到的问题是基于glibc进行内存管理的64 位Linux系统。具体现象如下:设备32G物理内存,在大规格打流情况下,某用户进程占用的物理内存暴涨至20G左右。

在停止打流后,观察到业务模块已经释放了绝大部分内存,但是进程占用的物理内存依然达到16G左右,此后内存状况一直维持该状态,导致系统内存紧张,若叠加上其他业务则出现了OOM的现象,已排除该进程内存泄露的可能性。

1

Glibc内存分配基本原理

Glibc使用了ptmalloc的内存管理方式,本文在描述时均使用glibc来称呼。Glibc申请内存时是从分配区申请的,分为主分配区和非主分配区,分配区都有锁,在分配内存前需要先获取锁,然后再去申请内存。

一般进程都是多线程的,当多个线程同时需要申请内存时,如果只有一个分配区,那么效率太低。

glibc为了支持多线程的内存申请释放,会在多个线程同时需要申请内存时根据cpu核数分配一定数量的分配区,将分配区分配给线程。如果线程数量较多,则会出现多个线程争用一个分配区的的情况,这里不展开。

内存申请基本原理:当用户调用malloc申请内存时,glibc会查看是否已经缓存了内存,如果有缓存则会优先使用缓存内存,返回一块符合用户请求大小的内存块。

如果没有缓存或者缓存不足则会去向操作系统申请内存(可通过brk、mmap申请内存),然后切一块内存给用户。

内存释放基本原理:当业务模块使用完毕后调用free释放内存时,glibc会检查该内存块虚拟地址上下内存块的使用状态(fast bin除外)。若其上一块内存空闲,则与上一块内存进行合并。若下一块内存空闲,则与下一块内存进行合并。如图2所示。

若下一块内存时top chunk(top chunk一直是空闲的),则看top chunk的大小是否超过一个阈值,如果超过一个阈值则将其释放给OS。

2

Glibc内存站岗及其原因

内存站岗概念:

内存站岗指的是glibc从OS申请到内存后分配给业务模块,业务模块使用完毕后释放了内存,但是glibc没有将这些空闲内存释放给OS,也就是缓存了很多空闲内存无法归还给系统的现象。

内存站岗原因:

glibc设计时就确定其内存是用于短生命周期的,因此在设计上内存释放给OS的时机是当top chunk的大小超过一个阈值时会释放top chunk的一部分内存给OS。当top chunk不超过阈值就不会释放内存给OS。

那么问题来了,若与top chunk相邻的内存块一直在使用中,那么top chunk就永远也不会超过阈值,即便业务模块释放了大量内存,达到几十个G 或者上百个G,glibc也是无法将内存还给OS的。

对于glibc来说,其有主分配和非主分配区的概念。主分配通过sbrk来增加分配区的内存大小,而非主分配区则是通过一个或多个mmap出来的内存块用链表链接起来模拟主分配区的。为了更清晰的解释内存站岗,下面举个例子来说明主分配区的内存站岗。

如上有(a) (c) (e) (g)内存块正在使用,故而导致了空闲内存(b) (d) (f)无法和top chunk连成一块更大的空闲内存块,glibc的阈值(64位系统默认是128K),尽管目前空闲内存有将近130M,也无法还给OS。

接下来看非主分配区的内存站岗,实际的非主分配区可能有很多个heap,这里假设只有4个heap。

在定位过程中,笔者与同事讨论过多次如何解决站岗。在一次讨论过程中由邓竑杰提出降低heap的size(类似于tcmalloc的做法),虽然实测后发现完全没有效果,但是为后续解决问题起到了启示作用。

后面笔者在走读代码时发现这是glibc原生机制,同时笔者在查看内存布局时观察到非主分配区大量heap均为free状态。原有机制是先释放heap3,如果heap3有内存在使用,尽管heap0、heap1、heap2的内存都释放了,那也是无法释放给系统。

glibc有多个分配区,每个分配区都几百 M 空闲内存的话,则整个进程占用达到几十个G也就不奇怪了。

3

Glibc内存站岗解决方法及patch

在内存释放时,对于主分配区和非主分配其走的流程是不一样的,我们64位系统的进程内存模型为经典模式,栈是从高地址向低地址生长的。

对于主分配区的内存站岗我还没有遇到过,若主分配区内存站岗,一种方法是可以尝试madvise将主分配区的pagesize对齐的空闲内存进行释放,但是这样效果可能不太明显。

另外一种是通过创建线程,然后将主线程的业务移到新线程即可,这样主分配区就不会造成站岗了,而将站岗转移到了非主配区,而非主分配区则是我们接下来要进行优化的主战场。

针对非主分配区进行两处优化:a) heap0,heap1,heap2是空闲的,那么我们就可以将heap1,heap2释放掉;b) heap默认是64M,降低每个heap的size(笔者测试时设置为512K)。

这里需要特别解释一下为什么不释放heap0和最后一个heap3,heap0的组成如图7所示。图左边是第一个heap即heap0,图右边是最后一个heap即heap3。

从图中可以清晰的看到如若释放掉heap0那么会将struct malloc_state结构体释放,会造成进程崩溃。右边这个由于有在用的内存,也不能释放掉。当然如果heap3的内存全部被释放了,则由glibc原生代码进行了处理,patch不再处理。

经过修改glibc源码,优化其释放机制,实际打流测试。

在打流到峰值后,进程使用了20G的内存,在停止打流后数秒内便恢复到了打流前的内存水平,进程所占用的内存基本还给系统了。至此,glibc内存站岗问题得到解决。

以上我们介绍了如何解决内存站岗的原理,纸上得来终觉浅,现在我们看patch源码实现。

目前笔者已经将该优化的patch提交到开源社区审核,提交到社区的patch未对heap的size进行修改,这是因为想要谨慎一些,毕竟开源的代码使用场景较多,如有需要可自行决定heap的size。

Patch基于glibc2.17代码

1. Index: arena.c2. ===================================================================3. --- arena.c (revision 2)4. +++ arena.c (working copy)5. @@ -652,7 +652,7 @@6.7. static int8. internal_function9. -heap_trim(heap_info *heap, size_t pad)10. +heap_trim(heap_info *heap, heap_info* free_heap, size_t pad)11. {12. mstate ar_ptr = heap-》ar_ptr;13. unsigned long pagesz = GLRO(dl_pagesize);14. @@ -659,7 +659,29 @@15. mchunkptr top_chunk = top(ar_ptr), p, bck, fwd;16. heap_info *prev_heap;17. long new_size, top_size, extra, prev_size, misalign;18. + heap_info *last_heap;19.20. + /*Release heap if possible*/21. + last_heap = heap_for_ptr(top_chunk);22. + if ((NULL != free_heap-》prev) && (last_heap != free_heap)){23. + p = chunk_at_offset(free_heap, sizeof(*free_heap));24. + if (!inuse(p)){25. + if (chunksize(p)+sizeof(*free_heap)+MINSIZE==free_heap-》size){26. + while (last_heap){27. + if (last_heap-》prev == free_heap){28. + last_heap-》prev == free_heap-》prev;29. + break;30. + }31. + last_heap = last_heap-》prev;32. + }33. + ar_ptr-》system_mem -= free_heap-》size;34. + arena_mem -= free_heap-》size;35. + unlink(p, bck, fwd);36. + delete_heap(free_heap);37. + return 1;38. + }39. + }40. + }41. /* Can this heap go away completely? */42. while(top_chunk == chunk_at_offset(heap, sizeof(*heap))) {43. prev_heap = heap-》prev;44. Index: malloc.c45. ===================================================================46. --- malloc.c (revision 2)47. +++ malloc.c (working copy)48. @@ -915,7 +915,7 @@49. # if __WORDSIZE == 3250. # define DEFAULT_MMAP_THRESHOLD_MAX (512 * 1024)51. # else52. -# define DEFAULT_MMAP_THRESHOLD_MAX (4 * 1024 * 1024 * sizeof(long))53. +# define DEFAULT_MMAP_THRESHOLD_MAX (256 * 1024)54. # endif55. #endif56.57. @@ -3984,7 +3984,7 @@58. heap_info *heap = heap_for_ptr(top(av));59.60. assert(heap-》ar_ptr == av);61. - heap_trim(heap, mp_.top_pad);62. + heap_trim(heap, heap_for_ptr(p), mp_.top_pad);63. }64. }

4

结束语

不同的内存管理方式均有其优势和缺陷,由于工作需要,笔者有幸研究过glibc、tcmalloc、uclibc内存管理,本文讨论了glibc内存管理存在的一个共性问题,并给出可行的解决方案。

对于内存站岗问题,一般的做法是用户自己缓存一些长时间不释放的内存。另一种是干脆将glibc替换为tcmalloc。因为 tcmalloc 的 span比较小,所以站岗发生的概率极低,即便发生也就站岗一个span的大小。若由于某些原因不能用tcmalloc代替glibc的场景,如上的解决思路可以尝试一下,该问题也困扰我们多时了,花费了较长时间和较多精力去定位。

在glibc2.28的版本中,glibc有了tcache的特性,对于业务进程使用大量小内存的场景则更加容易出现内存站岗问题。在撰写本文时查看了glibc2.33版本,开源社区还未对该问题进行修改(或许是开源社区大神认为这不是glibc的问题,而是用户不释放内存)。

编辑:jq

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • Linux
    +关注

    关注

    87

    文章

    11191

    浏览量

    208628
  • 代码
    +关注

    关注

    30

    文章

    4708

    浏览量

    68176
  • Glibc
    +关注

    关注

    0

    文章

    9

    浏览量

    7490

原文标题:Linux glibc 内存站岗问题及解决方法

文章出处:【微信号:LinuxDev,微信公众号:Linux阅码场】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    高速PCB信号和电源完整性问题的建模方法研究

    高速PCB信号和电源完整性问题的建模方法研究
    发表于 09-21 14:13 0次下载

    CAN盒指示灯不亮的解决方法

    CAN盒指示灯不亮可能有多种原因,以下是一些常见的解决方法
    的头像 发表于 09-20 14:53 337次阅读

    接地网阻值偏大的原因及解决方法

     地网接地电阻测试仪是保障电气设备安全运行的重要组成部分,但在实际使用中,我们可能会遇到接地网阻值偏大的情况。接下来,鸿蒙小小编介绍造成这种情况的原因,并提供一些解决方法。   1、接地体积不足
    发表于 06-17 09:19

    鸿蒙OpenHarmony:【常见编译问题和解决方法

    常见编译问题和解决方法
    的头像 发表于 05-11 16:09 1813次阅读

    UPS蓄电池运维管理解析,内阻抗监测数据

    石油化工行业大量使用UPS电源,对UPS蓄电池的运维管理仍然存在不少问题。本文对电池普遍存在的使用共性问题进行归类总结,结合主动电池安全运维管理
    的头像 发表于 05-11 09:07 598次阅读
    UPS蓄电池运维<b class='flag-5'>管理</b>解析,内阻抗监测数据

    分析电源电感发热解决方法

    电子发烧友网站提供《分析电源电感发热解决方法.docx》资料免费下载
    发表于 03-29 14:39 2次下载

    EMI电磁干扰:原理、影响及解决方法详解?

    EMI电磁干扰:原理、影响及解决方法详解?|深圳比创达电子
    的头像 发表于 03-21 10:02 791次阅读
    EMI电磁干扰:原理、影响及<b class='flag-5'>解决方法</b>详解?

    glibc malloc内存分配器的实现原理

    内存(Heap Memory)是一个很有意思的领域。你可能和我一样,也困惑于下述问题很久了。
    的头像 发表于 01-17 10:03 755次阅读
    <b class='flag-5'>glibc</b> malloc<b class='flag-5'>内存</b>分配器的实现原理

    PCB压合问题解决方法

    PCB压合问题解决方法
    的头像 发表于 01-05 10:32 923次阅读

    SNMP配置及网络管理系统无法接收告警(Trap)的解决方法

    本文将详细介绍如何配置SNMP,并探讨网络管理系统(NMS)无法接收告警(Trap)的原因及解决方法。SNMP(简单网络管理协议)是一种用于管理和监控网络设备的协议,而NMS则是用于集
    的头像 发表于 12-27 13:58 1550次阅读

    jvm管理内存包括哪几个运行时数据内存

    JVM(Java虚拟机)是Java程序的运行环境,它提供了内存管理机制来管理Java程序所需的运行时数据内存。这些运行时数据内存包括堆
    的头像 发表于 12-05 14:09 503次阅读

    glibc内存分配回收策略

    。malloc、free均发生在这个区域。本文将简单介绍下glibc在动态内存管理方面的机制,抛砖引玉,希望能和大家
    的头像 发表于 11-13 11:16 606次阅读
    <b class='flag-5'>glibc</b>的<b class='flag-5'>内存</b>分配回收策略

    mtrace分析内存泄露

    一、mtrace分析内存泄露 mtrace(memory trace),是 GNU Glibc 自带的内存问题检测工具,它可以用来协助定位内存泄露问题。它的实现源码在
    的头像 发表于 11-13 10:55 1213次阅读
    mtrace分析<b class='flag-5'>内存</b>泄露

    如何使用tcmalloc来替换glibc的malloc

    代码中使用tcmalloc替换malloc 我们如何使用tcmalloc来替换glibc的malloc呢? 在链接tcmalloc的时候我们可以使用以下任意一种方式: 1.启动程序之前,预先加载
    的头像 发表于 11-11 16:52 1919次阅读
    如何使用tcmalloc来替换<b class='flag-5'>glibc</b>的malloc

    Linux 内存管理总结

    一、Linux内存管理概述 Linux内存管理是指对系统内存的分配、释放、映射、管理、交换、压缩
    的头像 发表于 11-10 14:58 481次阅读
    Linux <b class='flag-5'>内存</b><b class='flag-5'>管理</b>总结