glibc内存管理那些事儿

news2024/11/15 17:26:51

Linux内存空间简介

32位Linux平台下进程虚拟地址空间分布如下图:

 进程虚拟地址空间分布

图中,0xC0000000开始的最高1G空间是内核地址空间,剩下3G空间是用户态空间。用户态空间从上到下依次为stack栈(向下增长)、mmap(匿名文件映射区)、Heap堆(向上增长)、bss数据段、数据段、只读代码段。

其中,Heap区是程序的动态内存区,同时也是C++内存泄漏的温床。malloc、free均发生在这个区域。本文将简单介绍下glibc在动态内存管理方面的机制,抛砖引玉,希望能和大家多多交流。

Linux提供了如下几个系统调用,用于内存分配:

brk()/sbrk() // 通过移动Heap堆顶指针brk,达到增加内存目的 
mmap()/munmap() // 通过文件影射的方式,把文件映射到mmap区

这两种方式分配的都是虚拟内存,没有分配物理内存。在第一次访问已分配的虚拟地址空间的时候,发生缺页中断,操作系统负责分配物理内存,然后建立虚拟内存和物理内存之间的映射关系。

那么,既然brk、mmap提供了内存分配的功能,直接使用brk、mmap进行内存管理不是更简单吗,为什么需要glibc呢? 我们知道,系统调用本身会产生软中断,导致程序从用户态陷入内核态,比较消耗资源。试想,如果频繁分配回收小块内存区,那么将有很大的性能耗费在系统调用中。因此,为了减少系统调用带来的性能损耗,glibc采用了内存池的设计,增加了一个代理层,每次内存分配,都优先从内存池中寻找,如果内存池中无法提供,再向操作系统申请。

一切计算机的问题都可以通过加的方式解决。

glibc的内存分配回收策略

glibc中malloc内存分配逻辑如下是:

 malloc

  • 分配内存 < DEFAULT_MMAP_THRESHOLD,走__brk,从内存池获取,失败的话走brk系统调用
  • 分配内存 > DEFAULT_MMAP_THRESHOLD,走__mmap,直接调用mmap系统调用

其中,DEFAULT_MMAP_THRESHOLD默认为128k,可通过mallopt进行设置。 重点看下小块内存(size > DEFAULT_MMAP_THRESHOLD)的分配,glibc使用的内存池如下图示:

 相关视频推荐

90分钟了解Linux内存架构,numa的优势,slab的实现,vmalloc的原理

linux内存管理问题-如何理出自己的思路出来,开发与面试双丰收

学习地址:c/c++ linux服务器开发/后台架构师

需要C/C++ Linux服务器架构师学习资料加qun812855908获取(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享

 

内存池

内存池保存在bins这个长128的数组中,每个元素都是一双向个链表。其中:

  • bins[0]目前没有使用
  • bins[1]的链表称为unsorted_list,用于维护free释放的chunk。
  • bins[2,63)的区间称为small_bins,用于维护<512字节的内存块,其中每个元素对应的链表中的chunk大小相同,均为index*8。
  • bins[64,127)称为large_bins,用于维护>512字节的内存块,每个元素对应的链表中的chunk大小不同,index越大,链表中chunk的内存大小相差越大,例如: 下标为64的chunk大小介于[512, 512+64),下标为95的chunk大小介于[2k+1,2k+512)。同一条链表上的chunk,按照从小到大的顺序排列。

chunk数据结构

 ​chunk结构

glibc在内存池中查找合适的chunk时,采用了最佳适应的伙伴算法。举例如下:

1、如果分配内存<512字节,则通过内存大小定位到smallbins对应的index上(floor(size/8))

  • 如果smallbins[index]为空,进入步骤3
  • 如果smallbins[index]非空,直接返回第一个chunk

2、如果分配内存>512字节,则定位到largebins对应的index上

  • 如果largebins[index]为空,进入步骤3
  • 如果largebins[index]非空,扫描链表,找到第一个大小最合适的chunk,如size=12.5K,则使用chunk B,剩下的0.5k放入unsorted_list中

3、遍历unsorted_list,查找合适size的chunk,如果找到则返回;否则,将这些chunk都归类放到smallbins和largebins里面

4、index++从更大的链表中查找,直到找到合适大小的chunk为止,找到后将chunk拆分,并将剩余的加入到unsorted_list中

5、如果还没有找到,那么使用top chunk

6、或者,内存<128k,使用brk;内存>128k,使用mmap获取新内存

top chunk 如下图示: top chunk是堆顶的chunk,堆顶指针brk位于top chunk的顶部。移动brk指针,即可扩充top chunk的大小。当top chunk大小超过128k(可配置)时,会触发malloc_trim操作,调用sbrk(-size)将内存归还操作系统

 chunk分布图

free释放内存时,有两种情况:

  1. chunk和top chunk相邻,则和top chunk合并
  2. chunk和top chunk不相邻,则直接插入到unsorted_list中

内存碎片

以上图chunk分布图为例,按照glibc的内存分配策略,我们考虑下如下场景(假设brk其实地址是512k):

  1. malloc 40k内存,即chunkA,brk = 512k + 40k = 552k
  2. malloc 50k内存,即chunkB,brk = 552k + 50k = 602k
  3. malloc 60k内存,即chunkC,brk = 602k + 60k = 662k
  4. free chunkA。

此时,由于brk = 662k,而释放的内存是位于[512k, 552k]之间,无法通过移动brk指针,将区域内内存交还操作系统,因此,在[512k, 552k]的区域内便形成了一个内存空洞 ---- 内存碎片。 按照glibc的策略,free后的chunkA区域由于不和top chunk相邻,因此,无法和top chunk 合并,应该挂在unsorted_list链表上。

glibc实现的一些重要结构

glibc中用于维护空闲内存的结构体是malloc_state,其主要定义如下:

struct malloc_state {
    mutex_t mutex; // 并发编程下锁的竞争
    mchunkptr        top; // top chunk
    unsigned int     binmap[BINMAPSIZE]; // bitmap,加快bins中chunk判定
    mchunkptr        bins[NBINS * 2 - 2]; // bins,上文所述
    mfastbinptr      fastbinsY[NFASTBINS]; // fastbins,类似bins,维护的chunk更小(80字节的chunk链表)
...
}
static struct malloc_state main_arena; // 主arena

多线程下的竞争抢锁

并发条件下,main_arena引发的竞争将会成为限制程序性能的瓶颈所在,因此glibc采用了多arena机制,线程A分配内存时获取main_arena锁成功,将在main_arena所管理的内存中分配;此时线程B获取main_arena失败,glibc会新建一个arena1,此次内存分配从arena1中进行。 这种策略,一定程度上解决了多线程下竞争的问题;但是随着arena的增多,内存碎片出现的可能性也变大了。例如,main_arena中有10k、20k的空闲内存,线程B要获取20k的空闲内存,但是获取main_arena锁失败,导致留下20k的碎片,降低了内存使用率。

普通arena的内部结构:

 普通arena结构

  1. 一个arena由多个Heap构成
  2. 每个Heap通过mmap获得,最大为1M,多个Heap间可能不相邻
  3. Heap之间有prev指针指向前一个Heap
  4. 最上面的Heap,也有top chunk

每个Heap里面也是由chunk组成,使用和main_arena完全相同的管理方式管理空闲chunk。 多个arena之间是通过链表连接的。如下图:

 arena链表

main arena和普通arena的区别 main_arena是为一个使用brk指针的arena,由于brk是堆顶指针,一个进程中只可能有一个,因此普通arena无法使用brk进行内存分配。普通arena建立在mmap的机制上,内存管理方式和main_arena类似,只有一点区别,普通arena只有在整个arena都空闲时,才会调用munmap把内存还给操作系统。

一些特殊情况的分析

根据上文所述,glibc在调用malloc_trim时,需要满足如下2个条件:

1. size(top chunk) > 128K
2. brk = top chunk->base + size(top chunk)

假设,brk指针上面的空间已经被占用,无法通过移动brk指针获得新的地址空间,此时main_arena就无法扩容了吗? glibc的设计考虑了这样的特殊情况,此时,glibc会换用mmap操作来获取新空间(每次最少MMAP_AS_MORECORE_SIZE<1M>)。这样,main_arena和普通arena一样,由非连续的Heap块构成,不过这种情况下,glibc并未将这种mmap空间表示为Heap,因此,main_arena多个块之间是没有联系的,这就导致了main_arena从此无法归还给操作系统,永远保留在空闲内存中了。如下图示:

 main_arena无法回收

显而易见,此时根本不可能满足调用malloc_trim的条件2,即:brk !== top chunk->base + size(top chunk),因为此时brk处于堆顶,而top chunk->base > brk.

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include <malloc.h>

#define ARRAY_SIZE 127
char cmd[1024];

void print_info()
{
    struct mallinfo mi = mallinfo();           
    system(cmd);
    printf("\theap_malloc_total=%lu heap_free_total=%lu heap_in_use=%lu\n\
            \tmmap_total=%lu mmap_count=%lu\n", mi.arena, mi.fordblks, mi.uordblks, mi.hblkhd, mi.hblks);
}

int main(int argc, char** argv)
{
    char** ptr_arr[ARRAY_SIZE];
    int i;
    char*  mmap_var;
    pid_t  pid;
    pid = getpid();
    sprintf(cmd, "ps aux | grep %lu | grep -v grep", pid);
    /* mmap占据堆顶后1M的地址空间 */
    mmap_var = mmap((void*)sbrk(0) + 1024*1024, 127*1024, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); 
    printf("before malloc\n");
    print_info();

    /* 分配内存,总大小超过1M,导致main_arena被拆分 */
    for( i = 0; i < ARRAY_SIZE; i++) {
        ptr_arr[i] = malloc(i * 1024);
    }   
    printf("\nafter malloc\n");
    print_info();
    /* 释放所有内存,观察内存使用是否改变 */
    for( i = 0; i < ARRAY_SIZE; i++) {
        free(ptr_arr[i]);
    }
    printf("\nafter free\n");
    print_info();
    munmap(mmap_var, 127*1024);
    return 1;
}

 异常运行

作为对比,去除掉brk上面的mmap区再次运行后结果如下:

 正常运行

可以看出,异常情况下(brk无法扩展),free的内存没有归还操作系统,而是留在了main_arena的unsorted_list了;而正常情况下,由于满足执行malloc_trim的条件,因此,free后,调用了sbrk(-size)把内存归还了操作系统,main_arena内存响应减少。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

网络编程之IO多路复用

目录 一. 同步与阻塞 1.1 同步阻塞 1.2 同步非阻塞 1.3 异步阻塞 1.4 异步非阻塞 1.5 I/O多路 二.多路复用的技术 2.1 UNIX I/O Models 2.1.1 blocking I/O 2.1.2 nonblocking I/O 2.1.3 I/O Multiplexing Model 2.1.4 SIGIO 2.1.5 asynchronous I/O 2.2 IO多路复…

【拓扑排序】课程表问题

一、拓扑排序问题描述 给定&#xff1a;一系列任务 &#xff08;A&#xff0c;B&#xff0c;C …&#xff09; 任务间的依赖关系 (B 和 C 必须在 A 之前完成&#xff0c; …) 输出&#xff1a;这些任务间的合法执行顺序 &#xff08;C – B – A – …&#xff09; 总之&am…

「图文讲解」浏览器原理与页面渲染过程

相信大家在面试的时候面试官总是会问一个问题&#xff1a;“可以说下浏览器从输入url到渲染页面完成过程吗&#xff1f;”&#xff0c;非官方标准答案来啦&#xff5e; 那我们首先需要先来讲一下浏览器是什么&#xff0c;浏览器其实就是一个应用软件&#xff0c;在学习操作系统…

《数据结构、算法与应用C++语言描述》线性表-数组描述

《数据结构、算法与应用C语言描述》线性表-数组描述 5表示在 数据结构算法与应用C语言描述 书中所在章节。 本文包含了《数据结构、算法与应用C语言描述》第五章主要练习题答案&#xff0c;给出了线性表数组描述完整测试代码。 5.1 数据对象 5.1.1 定义 数据对象(data obj…

java开发的师生评教小程序学生对老师评价老师对班级评价打分题单选题意见框系统选课系统

简介 源码1.0&#xff08;源码2.0选课功能&#xff0c;请往下看&#xff09; 师生评教小程序&#xff0c;学生可以对老师进行评价&#xff0c;老师可以对班级行进评级。管理员可以创建不同的评教模板&#xff08;单选题0分或者10分&#xff0c;打分题0-10分&#xff0c;意见框…

三分钟彻底搞懂paint,repaint,update!

最近总结了一下java中的paint&#xff0c;repaint和updata三者之间的关系&#xff0c;首先咱们都知道用paint方法来绘图&#xff0c;用repaint重绘&#xff0c;用update来写双缓冲。但是他们之间是怎么来调用的呢&#xff0c;咱们来分析一下(想直接看结果&#xff0c;请跳过分析…

2022卡塔尔世界杯感想

一、概述 说说我个人吧&#xff01;在体育活动上面真是没什么兴趣&#xff0c;篮球&#xff0c;足球...等等竞技运动不敏感&#xff01; 今年稍微关注了点世界杯比赛&#xff01;什么原因呢&#xff1f;我有一个爱买彩票/赌球的一个同事&#xff01;随着世界杯的进行&#xf…

风力发电机组机械功率Pm与转子转速Wm关系(Matlab实现)

目录 1 数学模型 2 代码 3 结果 1 数学模型 风力机空气动力学模型 风力涡轮机的动态输出机械扭矩表示为: 其中是空气密度 &#xff0c; A是叶片扫掠面积 &#xff0c; R是风力涡轮机的转子半径 (m)&#xff0c; 是风速 (m/s)。是叶片的功率系数&#xff0c;它是叶片桨距…

linux内网渗透:docker逃逸

Docker逃逸 前言 Docker 逃逸在渗透测试中面向的场景大概是这样&#xff0c;渗透拿到shell后&#xff0c;发现主机是docker环境&#xff0c;要进一步渗透&#xff0c;就必须逃逸到“直接宿主机”。甚至还有物理机运行虚拟机&#xff0c;虚拟机运行Docker容器的情况。那就还要…

基于VUE.JS的招聘系统

开发工具(eclipse/idea/vscode等)&#xff1a;idea 数据库(sqlite/mysql/sqlserver等)&#xff1a;mysql 功能模块(请用文字描述&#xff0c;至少200字)&#xff1a;本文是基于Vue.js对招聘系统的设计与实现&#xff0c;对招聘系统管理员、个人用户、企业用户三个模块功能的完善…

kubernetes学习之路--污点容忍度横向主节点

参考&#xff1a;K8s污点容忍度横向主节点-安全客 - 安全资讯平台 一.kube-scheduler调度 kube-scheduler是Kubernetes 集群的默认调度器&#xff0c;并且是集群控制面(master)的一部分。对每一个新创建的Pod或者是未被调度的Pod&#xff0c;kube-scheduler会选择一个最优的N…

[附源码]Nodejs计算机毕业设计基于图书管理系统Express(程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程。欢迎交流 项目运行 环境配置&#xff1a; Node.js Vscode Mysql5.7 HBuilderXNavicat11VueExpress。 项目技术&#xff1a; Express框架 Node.js Vue 等等组成&#xff0c;B/S模式 Vscode管理前后端分…

43_SDIO基础知识

目录 SDIO协议简介 SDIO设备分类 SD卡物理结构 SD卡寄存器列表 SDIO总线拓扑 SDIO总线 SDIO总线协议 SDIO命令 命令格式 命令类型 响应 SD卡的操作模式 卡识别模式 数据传输模式 STM32 SDIO功能框图 命令通道 命令状态机 数据通道 数据状态机 数据FIFO SDI…

自定义java注解案例

今天与大家分享java注解的基本使用&#xff0c;如有哪里有问题&#xff0c;望大家指教。 目录 1. 什么是java注解 2. java注解的分类 2.1 JDK基本注解 2.2 JDK元注解 3. 自定义注解 3.1 自定义注解语法 4. 注解示例 4.1 定义注解 4.2 拦截器 4.3 使用注解 4.4 测试 …

Kali Linux渗透测试演示——DNS欺骗

目录 一、DNS欺骗 1.介绍 2.原理 二、环境和工具 1.kali Linux和靶机&#xff08;这里选择windows 7&#xff09; 2.ettercap 三、攻击过程 1.首先确认一下Kali和靶机的IP地址以及网关 2.进入ettercap的配置文件 3.打开ettercap&#xff0c;进行主机发现 总结 一、DNS…

MR案例:计算学生成绩

计算学生成绩一、提出任务二、完成任务&#xff08;一&#xff09;准备数据1、在虚拟机上创建文本文件2、上传文件到HDFS指定目录&#xff08;二&#xff09;实现步骤1、创建Maven项目2、添加相关依赖3、创建日志属性文件4、创建成绩映射器类5、创建成绩驱动器类6、启动成绩驱动…

adb常用命令(二)

adb:Android下面一个通用的调试工具管理设备或者手机模拟器的状态&#xff0c;进行手机操作:安装软件、卸载软件、系统升级、运行shell命令等adb命令&#xff1a;一、环境配置1&#xff0c;Java--JDK java -version2&#xff0c;SDK adb versionsdk版本管理&#xff08;SDK与…

Spark-RDD(转换算子、行动算子、序列化、依赖关系、持久化、分区器、文件读取和保存、累加器、广播变量)

文章目录RDDRDD特点核心属性执行原理RDD创建RDD并行度与分区内存数据的分区文件数据的并行度和分区RDD转换算子Value类型mapmapPartitionsmapPartitionsWithIndexflatMapglom(获取分区数组)groupByfilterdistinctcoalesce(缩小/扩大分区)repartition(扩大分区)sortBysample双 V…

人工智能 之 机器学习(Machine Learning)

目录 一&#xff1a;机器学习概述 二&#xff1a;机器学习算法 三&#xff1a;机器学习模型 四&#xff1a;机器学习过程 五&#xff1a;机器学习模型验证 六&#xff1a;sklearn模块 一&#xff1a;机器学习概述 程序化处理和机器学习处理&#xff1b; 主观思维和客观思…

python利用read()、readline()、readlines()读取文件

目录 1.语法和用法 2.实例 1.语法和用法 with open(file,moder,encodingNone) as f:#①读取所有文本data1 f.read()#②读取第一行数据data2 f.readline() #③读取所有文本数据 data3 f.readlines() 说明&#xff1a; with …… as ……&#xff1a;用于命名&#xff0c…