【编译、链接、装载十四】堆与内存管理

news2024/9/27 21:20:23

【编译、链接、装载十四】堆与内存管理

  • 一、堆与内存管理
    • 1、什么是堆
  • 二、Linux进程堆管理
  • 三、Windows进程堆管理
  • Q&A

一、堆与内存管理

相对于栈而言, 堆这片内存面临一个稍微复杂的行为模式: 在任意时刻, 程序可能发出请求, 要么申请一段内存, 要么释放一段已申请过的内存, 而且申请的大小从几个字节到数GB都是有可能的, 我们不能假设程序会一次申请多少堆空间, 因此, 堆的管理显得较为复杂。 下面让我们来了解一下堆的工作原理。

1、什么是堆

光有栈对于面向过程的程序设计还远远不够, 因为栈上的数据在函数返回的时候就会被释放掉, 所以无法将数据传递至函数外部。 而全局变量没有办法动态地产生, 只能在编译的时候定义, 有很多情况下缺乏表现力。 在这种情况下, 堆(Heap) 是唯一的选择。

堆是一块巨大的内存空间, 常常占据整个虚拟空间的绝大部分。 在这片空间里, 程序可以请求一块连续内存, 并自由地使用, 这块内存在程序主动放弃之前都会一直保持有效。 下面是一个申请堆空间最简单的例子。

int main()
{
char * p = (char*)malloc(1000);
/* use p as an array of size 1000*/
free(p);
}

在第3行用malloc申请了1000个字节的空间之后, 程序可以自由地使用这1000个字节, 直到程序用free函数释放它。

那么malloc到底是怎么实现的呢?

有一种做法是, 把进程的内存管理交给操作系统内核去做, 既然内核管理着进程的地址空间, 那么如果它提供一个系统调用, 可以让程序使用这个系统调用申请内存, 不就可以了吗? 当然这是一种理论上可行的做法,但实际上这样做的性能比较差,因为每次程序申请或者释放堆空间都需要进行系统调用。 我们知道系统调用的性能开销是很大的, 当程序对堆的操作比较频繁时, 这样做的结果是会严重影响程序的性能的。

比较好的做法就是程序向操作系统申请一块适当大小的堆空间, 然后由程序自己管理这块空间, 而具体来讲,管理着堆空间分配的往往是程序的运行库。

运行库相当于是向操作系统“批发”了一块较大的堆空间, 然后“零售”给程序用。 当全部“售完”或程序有大量的内存需求时, 再根据实际需求向操作系统“进货”。 当然运行库在向程序零售堆空间时, 必须管理它批发来的堆空间, 不能把同一块地址出售两次, 导致地址的冲突。 于是运行库需要一个算法来管理堆空间, 这个算法就是堆的分配算法。 不过在了解具体的分配算法之前, 我们先来看看运行库是怎么向操作系统批发内存的。

二、Linux进程堆管理

进程的地址空间中, 除了可执行文件、 共享库和栈之外, 剩余的未分配的空间都可以被用来作为堆空间。 Linux下的进程堆管理稍微有些复杂, 因为它提供了两种堆空间分配的方式, 即两个系统调用: 一个是 brk() 系统调用, 另外一个是 mmap()。

  • linux下的malloc是采用brk实现的,还是mmap实现的?
    glibc的malloc函数是这样处理用户的空间请求的: 对于小于128KB的请求来说, 它会在现有的堆空间里面, 按照堆分配算法为它分配一块空间并返回; 对于大于128KB的请求来说, 它会使用 mmap() 函数为它分配一块匿名空间, 然后在这个匿名空间中为用户分配空间。

  • brk
    brk() 的作用实际上就是设置进程数据段的结束地址, 即它可以扩大或者缩小数据段(Linux下数据段和BSS合并在一起统称数据段) 。 如果我们将数据段的结束地址向高地址移动, 那么扩大的那部分空间就可以被我们使用, 把这块空间拿来作为堆空间是最常见的做法之一 。

可以使用man 命令查看brk的用法。命令为:man brk
我的系统当时还出了问题,删除了/usr/bin下的man文件夹,重新安装后才解决
在这里插入图片描述

Glibc中还有一个函数叫sbrk, 它的功能与brk类似, 只不过参数和返回值略有不同。 sbrk以一个增量(Increment) 作为参数, 即需要增加(负数为减少) 的空间大小, 返回值是增加(或减少) 后数据段结束地址, 这个函数实际上是对brk系统调用的包装, 它是通过 brk() 实现的。

  • mmap
    mmap() 的作用和Windows系统下的VirtualAlloc很相似, 它的作用就是向操作系统申请一段虚拟地址空间, 当然这块虚拟地址空间可以映射到某个文件(这也是这个系统调用的最初的作用) , 当它不将地址空间映射到某个文件时, 我们又称这块空间0为匿名(Anonymous) 空间, 匿名空间就可以拿来作为堆空间。

man mmap在这里插入图片描述
mmap的前两个参数分别用于指定需要申请的空间的起始地址和长度,如果起始地址设置为0, 那么Linux系统会自动挑选合适的起始地址。prot/flags这两个参数用于设置申请的空间的权限(可读、 可写、 可执行) 以及映射类型(文件映射、 匿名空间等) , 最后两个参数是用于文件映射时指定文件描述符和文件偏移的, 我们在这里并不关心它们。

三、Windows进程堆管理

  • 每个线程的栈都是独立的, 每个线程默认的栈大小是1MB.

栈的位置则在0x00 030 000和EXE文件后面都有分布, 可能有读者奇怪为什么Windows需要这么多栈呢? 我们知道, 每个线程的栈都是独立的, 所以一个进程中有多少个线程, 就应该有多少个对应的栈, 对于Windows来说, 每个线程默认的栈大小是1MB, 在线程启动时, 系统会为它在进程地址空间中分配相应的空间作为栈, 线程栈的大小可以由创建线程时CreateThread的参数指定。在这里插入图片描述

在分配完上面这些地址以后, Windows的进程地址空间已经是支离破碎了。 当程序向系统申请堆空间时, 只好从这些剩下的还没有被占用的地址上分配。 Windows系统提供了一个API叫做VirtualAlloc(), 用来向系统申请空间, 它与Linux下的mmap非常相似。 实际上VirtualAlloc()申请的空间不一定只用于堆, 它仅仅是向系统预留了一块虚拟地址, 应用程序可以按照需要随意使用。

在使用 VirtualAlloc() 函数申请空间时, 系统要求空间大小必须为页的整数倍, 即对于x86系统来说, 必须是4096字节的整数倍。 很明显, 这就是操作系统的“批发”内存的接口函数了, 4096字节起批, 而且只能是4096字节的整数倍, 多了少了都不行。

那么应用程序作为最终的“消费者”, 如果它直接向操作系统申请内存的话, 难免会造成大量的浪费,比如程序只需要4097个字节的空间, 它也必须申请8192字节。当然, 在Windows下我们也可以自己实现一个分配的算法, 首先通过VirtualAlloc向操作系统一次性批发大量空间, 比如10MB, 然后再根据需要分配给程序。 不过这么常用的分配算法已经被各种系统、 库实现了无数遍, 一般情况下我们没有必要再重复发明轮子, 自己再实现一个,用现成的就可以了。 在Windows中, 这个算法的实现位于堆管理器(Heap Manager) 。

堆管理器提供了一套与堆相关的API可以用来创建、 分配、 释放和销毁堆空间:

  • HeapCreate: 创建一个堆。
  • HeapAlloc: 在一个堆里分配内存。
  • HeapFree: 释放已经分配的内存。
  • HeapDestroy: 摧毁一个堆。

这四个API的作用很明显,

  • HeapCreate就是创建一个堆空间, 它会向操作系统批发一块内存空间(它也是通过VirtualAlloc()实现的) ,
  • 而HeapAlloc就是在堆空间里面分配一块小的空间并返回给用户, 如果堆空间不足的话, 它还会通过VirtualAlloc向操作系统批发更多的内存直到操作系统也没有空间可以分配为止。
  • HeapFree和HeapDestroy的作用就更不言而喻了

Q&A

  • Q: 我可以重复释放两次堆里的同一片内存吗?
    A: 不能。 几乎所有的堆实现里, 都会在重复释放同一片堆里的内存时产生错误。 glibc甚至能检测出这样的错误, 并给出确切的错误信息。

  • Q: 我在有些书里看到说堆总是向上增长, 是这样的吗?
    A: 不是, 有些较老的书籍针对当时的系统曾做出过这样的断言, 这在当时可能是正确的。 因为当时的系统多是类unix系统, 它们使用类似于brk的方法来分配堆空间, 而brk的增长方向是向上的。 但随着Windows的出现, 这个规律被打破了。 在Windows里, 大部分堆使用HeapCreate产生, 而HeapCreate系列函数却完全不遵照向上增长这个规律。

  • Q: 调用malloc会不会最后调用到系统调用或者API?
    A: 这个取决于当前进程向操作系统批发的那些空间还够不够用, 如果够用了, 那么它可以直接在仓库里取出来卖给用户; 如果不够用了, 它就只能通过系统调用或者API向操作系统再进一批货了。

  • Q: malloc申请的内存, 进程结束以后还会不会存在?
    A: 这是一个很常见的问题, 答案是很明确的: 不会存在。 因为当进程结束以后, 所有与进程相关的资源, 包括进程的地址空间、 物理内存、打开的文件、 网络链接等都被操作系统关闭或者收回, 所以无论malloc申请了多少内存, 进程结束以后都不存在了。

  • Q: malloc申请的空间是不是连续的?
    A: 在分析这个问题之前, 我们首先要分清楚“空间”这个词所指的意思。 如果“空间”是指虚拟空间的话, 那么答案是连续的, 即每一次malloc分配后返回的空间都可以看做是一块连续的地址; 如果空间是指“物理空间”的话, 则答案是不一定连续, 因为一块连续的虚拟地址空间有可能是若干个不连续的物理页拼凑而成的。

参考
1、《程序员的自我修养链接装载与库》

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

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

相关文章

请求响应相关知识点

这里写目录标题 请求响应概述 请求postman各种参数的封装以及接收简单参数原始方式springboot方式映射解决参数不匹配小结 实体参数简单的实体参数复杂的实体参数注意点 数组集合参数数组集合总结 日期参数json参数模拟发送注意点服务端注意点 路径参数各个参数总结 响应注解统…

66、基于51单片机超声波测距LCD1602显示报警系统设计(程序+原理图+PCB源文件+Proteus仿真+参考论文+开题报告+任务书+元器件清单等)

前 言 超声波具有指向性强,能量消耗缓慢,在介质中传播的距离较远,因而超声波经常用于距离的测量,如测距仪和物位测量仪等都可以通过超声波来实现。利用超声波检测往往比较迅速、方便、计算简单、易于做到实时控制,并且…

JDK8新特性-中部

文章目录 一、方法引用1.1 为什么要有方法引用?1.1.1 Lambda表达式冗余1.1.2 解决方案 1.2 方法引用的格式1.2 .1 对象::方法名1.2.2 类名::静态方法名1.2.3 类名::引用实例方法1.2.4 类名::构造器1.2.5 数组::构造器 二、Stream API2.1 集合处理的弊端2.2 Steam流式思想概述2…

HttpRunner抓包工具之HttpRunner介绍

HttpRunner简介: HttpRunner 是一款面向HTTP(S) 协议的通用测试框架,只需编写维护一份YAML/JSON 脚本,即可实现自动化测试、性能测试、线上监控、持续集成等多种测试需求。 作者李隆之前是大疆的测试开发工程师,项目起源于大疆内部…

关于数据分析中NumPy,Pandas,看完这一篇基本够了

前言 数据分析是Python的重要应用领域之一:Python在数据分析领域有着广泛的应用,许多数据科学家和分析师使用Python作为主要的数据分析工具。学好数据分析可以让你更好地应用Python来解决实际问题,并提升在数据分析领域的竞争力。 在当今信…

04 todoList案例

React全家桶 一、案例- TODO List 综合案例 功能描述 动态显示初始列表添加一个 todo删除一个 todo反选一个 todotodo 的全部数量和完成数量全选/全不选 todo删除完成的 todo 1.1 静态组件构建 将资料包中的todos_page/index.html中核心代码添加到Todo.jsx文件中,…

高速电路设计系列分享-ADC电源的设计

文章目录 概要整体架构流程技术名词解释技术细节小结 概要 提示:这里可以添加技术概要 如今,在设计人员面临众多电源选择的情况下,为高速ADC设计清洁电源时可能会面临巨大挑战。在利用高效开关电源而非传统LDO的场合,这尤其重要。…

liunx服务器使用selenium

文章目录 前言一、服务器下载google-chrome、chromedriver1、下载chrome2、下载chromedriver 二、安装Xvfb和python库pyvirtualdisplay、selenium1、安装Xvfb2、安装python库pyvirtualdisplay 三、配置好后测试总结 前言 最近在本地windows系统(下面简称本地&#…

在线搭建K8S,kubernetes集群v1.23.9,docker支持的最后一个版本

1. 部署环境主机(条件说明) master 192.168.186.128 CentOS Linux release 7.9.2009 (Core) node1 192.168.186.129 CentOS Linux release 7.9.2009 (Core) node2 192.168.186.130 CentOS Linux release 7.9.2009 (Core)2. 系统初始化-所有节点&am…

探索MediaPipe的人像分割

MediaPipe是Google开源的计算机视觉处理框架,基于TensorFlow来训练模型。图像分割模块提供人像分割、头发分割、多类分割。本文主要探索如何实现人像分割,当然在人像分割基础上,我们可以做背景替换、背景模糊。 目录 一、配置参数与模型 1…

负载均衡 Load Balancing

负载均衡 Load Balancing 数据链路层负载均衡网络层负载均衡应用层负载均衡均衡策略与实现轮询与随机随机权重与加权轮询一致性 hash最少活跃数(最少连接数) 对于电商平台而言,随着业务的不断发展壮大,网站访问量和数据量也随之急…

什么是堆栈?c语言实现栈上的pop和push操作

什么是堆栈?c语言实现栈上pop和push操作的算法 堆栈(stack)实现栈上pop和push操作解释 堆栈(stack) 堆栈(stack)是一种数据结构,它具有后进先出(Last-In-First-Out&…

抖音seo矩阵系统源码开发部署思路

抖音SEO矩阵系统源码开发和部署是一项复杂而又关键的任务。在开发阶段,我们需要根据抖音的搜索规则和算法,结合用户搜索意图和关键词,进行深入的研究和分析,以建立一个优秀的SEO矩阵系统。 在部署方面,我们需要遵循以…

完全零基础,如何学习渗透?

网络渗透这一行,做个脚本小子,使使工具啥的,个把月就学会了,这不难。 很多人把网络渗透简单的理解成就是搞网站,这是大错特错的! 不过这也难怪,Web安全那三招两式,最好教也最好学&…

还在找wma格式怎么转换mp3?

在一个遥远的国度里,有一位名叫小芳的歌手,她声音甜美动人,深受人们的喜爱。然而,她的音乐制作团队告诉她,她的歌曲只能以wma格式发布。但是,她的粉丝们大多数使用的是mp3格式的音乐播放器,这让…

【Java】JVM(五)

垃圾回收机制 判断对象的存活 在堆里面存放着几乎所有的对象实例,垃圾回收器在对对进行回收前,要做的事情就是确定这些对象中哪些还是“存活”着,哪些已经“死去”(死去代表着不可能再被任何途径使用得对象了) 可达…

【pytest学习总结2.3】 - 如何使用固定装置fixtures(2)

目录 2.3.8 使用mark给固定装置传递数据 2.3.9 将固定装置工厂化 2.3.10 参数化固定装置 2.3.11 使用带有参数化固定装置的标记 2.3.12 使用来自固定装置功能中的固定装置 - 模块化 2.3.13 按固定装置实例自动分组测试 2.3.14 在类和模块中使用usefixtures 2.3.15 固定…

四.安防摄像机的WDR(HDR) 性能

四.安防摄像机的WDR(HDR) 性能 4.0 概述 WDR就是宽动态,wide dynamic range,或者HDR,high dynamic range,本质上是一回事,没任何区别。那么,到底什么是宽呢?很简单,搞不定的,就是太宽了,比如 摄像机装在室内,室内正常照度500lux,玻璃门外就是10000lux,室内外…

单机取证-鉴于信息安全管理与评估赛项-计算机单机取证特别说明-例题详解-Autopsy使用

芜湖~ 本期针对全国职业技能大赛-信息安全管理与评估赛项分析一下单机取证这个大项 并且取一例题 进行例题讲解 分享一些思路和逻辑 目录 题目 前言分析 .E01 ⽂件 DD 镜像和 E01 镜像的主要区别 如何打开和查看 E01 ⽂件扩展名? 常用工具使用-Autopsy 正…

现在这个年代,还有必要进行JVM调优吗?

导言 随着技术的不断发展,软件开发行业也在日新月异地进步。在过去的几十年里,Java语言和Java虚拟机(JVM)在开发企业级应用方面扮演了重要角色。然而,随着硬件和软件的进步,以及JVM本身的改进,…