malloc内存的分配原理

malloc内存的分配原理

一、Linux虚拟内存的分布

一部分是内核虚拟存储器,另一部分是进程虚拟存储器。

如下图:(由上往下 高地址到低地址)

在这里插入图片描述

  • 内存虚拟存储器是由内核管理,用户代码不可见
  • 进程虚拟存储器是用户态所能触及到的部分

关于进程虚拟存储器的部分:

  1. 只读段:该部分空间只能读,不可写;(包括:代码段、rodata 段(C常量字符串和#define定义的常量) )
  2. 数据段:保存全局变量、静态变量的空间;
  3. 堆 :就是平时所说的动态内存, malloc/new 大部分都来源于此。其中堆顶的位置可通过函数 brk 和 sbrk 进行动态调整。
  4. 文件映射区域:如动态库、共享内存等映射物理空间的内存,一般是 mmap 函数所分配的虚拟地址空间。(堆和栈之间)
  5. 栈:用于维护函数调用的上下文空间,一般为 8M ,可通过 ulimit –s 查看。

如下图::(由上往下 地址低到高)
在这里插入图片描述

二、malloc 和 free 是如何运作的

由于采取了虚拟内存解决内存容量较小的问题,所以缺页中断是必然存在的,linux下查看缺页中断的方法:

ps aux 查看当前系统存在的进程信息(包括虚拟内存和实际的物理内存)

ps -o majflt(大错误),minflt(小错误) -C program 命令查看该进程的缺页中断的次数

注意:

一个进程使用了mmap将很大的数据文件映射到进程的虚拟地址空间,我们需要重点关注majflt的值,因为相比minflt,majflt对于性能的损害是致命的,随机读一次磁盘的耗时数量级在几个毫秒,而minflt只有在大量的时候才会对性能产生影响。

那么发生了缺页中断后,执行了哪些操作:
1、检查要访问的虚拟地址是否合法
2、查找/分配一个物理页
3、填充物理页内容(读取磁盘,或者直接置0,或者啥也不干)
4、建立映射关系(虚拟地址到物理地址)

重新执行发生缺页中断的那一条指令

如果第3步,需要读取磁盘,那么这次缺页中断就是majflt,否则就是minflt。

首先从操作系统的角度来看,进程分配动态内存有两种方式 mmap(不考虑共享内存)和brk

  1. brk是将数据段(.data)的最高地址指针_edata往高处推得到申请的地址
  2. mmap是在栈和堆文件映射区域得到分配的虚拟地址空间

注意:

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

malloc分配内存有两种情况:
在这里插入图片描述

情况一:malloc小于128k内存的时候,使用brk分配内存,将_edata往高地址推,返回虚拟地址,不对应物理内存(只是虚拟地址还未初始化,直到第一次读/写的时候分配物理内存,然后与虚拟地址建立映射关系)如上图D=malloc(100k)

情况二:malloc大于128内存的时候,不是去选用_edata指针,系统会使用mmap从堆和栈之间的文件映射区域分配一块虚拟内存的地址。如上图C=malloc(200k)

在这里插入图片描述
内存的释放:
使用brk分配的内存要等待虚拟高地址内存释放后虚拟低地址内存才能释放(如上图free(B)),这也是内存碎片产生的原因,(当然上图free(B),这片内存就变成了一块空闲的内存,可以重用)mmap分配的内存可以munmap单独释放。最高空闲地址内存超过128k,就会执行内存紧缩,空闲的虚拟内存和物理内存都会被释放,__edata指针会向低地址推进。

我认为注意的点:

  1. 虚拟内存和物理内存的关系:虚拟内存在进程看来是一块连续的地址空间,而实际上它对应的物理内存大多是分散的物理内存碎片,有些存储在外部的磁盘上,在需要的时候进行交换,所以虚拟内存和物理内存是一种映射的关系。进程不是一被装入就被分配物理内存,而是在运行时第一次对内存读写时,产生页面中断,才会被分配相应的物理内存。
  2. 申请的内存过大时,会造成内核态cpu消耗较大,malloc调用mmap系统调用向系统申请分配内存,请求结束的调用munmap释放内存,每个请求可能会对应多个缺页中断,导致内核态cpu消耗大。

参考链接:linux环境内存分配原理–虚拟内存 mallocinfo - dzqabc - 博客园 (cnblogs.com)

三、如何在代码中有效解决申请内存中内存碎片化和减少多次系统调用的消耗cpu的问题

​ 从上述描述中我们可以得知,我们在代码中调用free()释放内存,不一定真正意义上释放了内存,因为使用brk分配的内存要等待虚拟高地址内存释放后虚拟低地址内存才能释放,所以要是free掉了低地址内存而高地址内存没有释放,那么实际上free()其实是做了用工,其对应的虚拟内存和物理内存都未真正释放掉。这就是很难避免的内存碎片化的问题。

​ 内存碎片化很显然,浪费了内存,以至于可能会导致,内存不够直接退出,这显然是不合理。所以在高可用的软件中就出现了内存池的概念。

​ 内存池的简介:

​ 内存池是池化技术中的一种形式。通常我们在编写程序的时候回使用 new delete 这些关键字来向操作系统申请内存,而这样造成的后果就是每次申请内存和释放内存的时候,都需要和操作系统的系统调用打交道,从堆中分配所需的内存。如果这样的操作太过频繁,就会找成大量的内存碎片进而降低内存的分配性能,甚至出现内存分配失败的情况。

​ 而内存池就是为了解决这个问题而产生的一种技术。从内存分配的概念上看,内存申请无非就是向内存分配方索要一个指针,当向操作系统申请内存时,

​ 操作系统需要进行复杂的内存管理调度之后,才能正确的分配出一个相应的指针。而这个分配的过程中,我们还面临着分配失败的风险。

​ 所以,每一次进行内存分配,就会消耗一次分配内存的时间,设这个时间为 T,那么进行 n 次分配总共消耗的时间就是 nT;如果我们一开始就确定好我们可能需要多少内存,那么在最初的时候就分配好这样的一块内存区域,当我们需要内存的时候,直接从这块已经分配好的内存中使用即可,那么总共需要的分配时间仅仅只有 T。当 n 越大时,节约的时间就越多。

参考链接:https://blog.csdn.net/xjtuse2014/article/details/52302083

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值