什么?内存爆了?详细解读虚拟内存机制

news2025/3/12 14:26:32

不知道大家在运行自己写的程序时,有没有发现一个问题:就是物理机器明明只有8G内存,但是我们运行的程序却可以申请到16G的内存?或者说机器上运行的多个进程,占用的总内存已经远超物理内存了,却还能正常工作。其实,这都要归功于CPU和操作系统设计的虚拟内存的机制。所谓虚拟内存,就是机器上运行的一个个的进程,访问的都是虚拟的内存,比如C语言中的指针指向的内存地址,或者gdb调试工具看到的地址,都是虚拟的,并不是机器上的实际物理内存。

而物理内存,简单说就是那根内存条,是机器真正实际可以访问的物理内存空间。你的内存条是 1G 的,那计算机可用的物理内存就是 1G。这个内存条加电以后就可以存储数据了。在早期的 CPU 指令集里,从内存中加载数据,向内存中写入数据都是直接操作物理内存的。也就是说每一个数据存储在内存的什么位置,都由程序员自己负责。例如,8086 这款 40 年前的 CPU 的 mov 指令就可以直接访问物理内存。

1、为什么要使用虚拟内存如果只有单个进程独享整个物理内存,当然没有什么问题。但是如果有多个进程,必然要求程序员手动对数据进行布局,那么内存不够用怎么办呢?而且,每个进程分配多少内存,如何保证指令中访存地址的正确性,这些问题都全部要程序员来负责。

还有,当两个进程要同时对同一个物理内存地址进行读写时,显然是有冲突的。随着计算机上要运行的程序越来越多,这个问题也越来越突出。

​那既然直接访问物理内存效率那么低,现在还有开发人员用这种模式吗?

其实也还是有的。在嵌入式设备中,手动管理内存的操作还是广泛存在的。这是因为在嵌入式开发中,往往没有进程的概念,也就是说整个应用独享全部内存,所以手动管理内存才有可能性。在单进程的系统中,所有的物理资源都是单一进程在管理,直接管理物理内存的操作复杂度还可以接受。

因为直接使用物理地址存在前面所说的问题,所以CPU和操作系统联合设计出了虚拟地址的机制,就是给所有程序可见的都是虚拟地址,这块虚拟地址非常大,并且是连续的,每个程序都可以操作虚拟内存地址,至于说这个虚拟内存对应的是哪块物理内存,交给CPU和操作系统就好了,这样就大大提高了程序的开发效率。

Intel从80286 CPU开始,改变了8086直接访问物理内存的方式,在CPU芯片内部集成了内存管理单元(MMU),进程访问的虚拟内存地址通过MMU,转换成物理地址,然后再通过物理地址访问内存。

​2、进程的虚拟内存空间是什么样的计算机的虚拟内存大小是不一样的。虚拟地址空间往往与机器字宽有关系,下面是32位和64位系统下进程的虚拟地址空间:

​可以看出:

  • 32 位系统上,指向内存的指针是 32 位的,所以它的虚拟地址空间是 2 的 32 次方,也就是 4G。其中,内核空间占用 1G,位于最高处,剩下的 3G 是用户空间。

  • 64 位系统上,指向内存的指针是 64 位的,但在 64 位系统里只使用了低 48 位,所以它的虚拟地址空间是 2 的 48 次方,也就是 256T。其中,内核空间和用户空间都是 128T,分别占据整个内存空间的最高和最低处,剩下的中间部分是未定义的。

进程在用户态时,只能访问用户空间内存;只有进入内核态后,才可以访问内核空间内存。虽然每个进程的地址空间都包含了内核空间,但这些内核空间,其实关联的都是相同的物理内存。这样,进程切换到内核态后,就可以很方便地访问内核空间内存。

​3、进程申请内存时就会对应到物理内存吗既然每个进程都有一个这么大的地址空间,那么所有进程的虚拟内存加起来,自然要比实际的物理内存大得多。所以,并不是所有的虚拟内存都会分配物理内存,只有那些实际使用的虚拟内存才分配物理内存,并且分配后的物理内存,是通过内存映射来管理的。

虚拟空间页面与物理空间页面的映射关系,如下图:

​这个图中,我们需要理解的有这几点:

  • 虽然虚拟内存提供了很大的空间,但实际上进程启动之后,这些空间并不是全部都能使用的。

  • 开发者必须要使用 malloc 等分配内存的接口才能将内存从未分配状态变成已分配状态。在你得到一块虚拟内存以后,这块内存就是未映射状态,因为它并没有被映射到相应的物理内存;直到对该块内存进行读写时,CPU 就会去访问这个虚拟内存, 这时会发现这个虚拟内存没有映射到物理内存, CPU 就会产生缺页中断,进程会从用户态切换到内核态,并将缺页中断交给内核的 Page Fault Handler (缺页中断函数)处理,这时才会真正地为它分配物理内存。然后这个页面才能成为正常页面。

  • 在虚拟内存中连续的页面,在物理内存中不必是连续的。只要维护好从虚拟内存页到物理内存页的映射关系,你就能正确地使用内存了。这种映射关系是操作系统通过页表来自动维护的,不必你操心。

相关视频推荐

Linux内核源码分析之《物理内存与虚拟内存》

剖析linux内核MMU机制详解

庞杂的内存问题,如何理出自己的思路出来,让你开发与面试双丰收

免费学习地址: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等),免费分享

​4、虚拟地址和物理地址是怎么映射的虚拟地址和物理地址的映射机制,经历了从内存分段到分页的过程,我们先来看内存分段。

4.1 内存分段内存分段机制,简单理解就是根据程序申请使用内存的需要,来把物理内存分成一段一段内存来管理,比如程序需要100M的内存,分段机制就给1段100M连续空间的物理内存与之对应。

在地址寻址上,分段机制维护有段表,并且将虚拟地址分为两部分,一部分是段表项的编号,另外一部分是段内偏移量。每个段表项里面有段的起始地址和段的边界长度,其中段内偏移量应该小于段的边界长度。每个段在段表中有一个项,在这一项找到段的基地址,再加上偏移量,于是就能找到物理内存中的地址。

比如要访问的虚拟地址A,段表项编号为2,段内偏移量是400 ,段表项2的起始地址是1000,边界长度是500,我们可以计算出A对应的物理地址为,段表项2起始地址 1000 + 偏移量 400 = 1400。

分段机制解决了程序使用物理地址存在的问题,但是也有一些不足之处:

  • 存在外部内存碎片:因为每段大小长度不一样,各个段之间会存在多段大小不一样且不连续的空隙,比如存在两段不连续的100M空间,这个时候即使刚好有个进程(例如下图中的进程B)需要申请200M空间,也是没法直接使用这两段100M空间的,因为它们是不连续的。

  • 换入换出效率低:针对两段不连续100M空间不能给需要200M空间的进程B的问题,其实也是可以有办法解决的,就是将其它进程(比如进程A)暂时不用的内存,先通过swap机制写入到磁盘中(换入),等给这个进程B分配好内存后,再从磁盘把进程A回写到内存的另外一段空间(换出)。但是因为磁盘的读写速度比内存读写慢太多了,这个过程会产生性能瓶颈。

​4.2 内存分页由于分段存在前面说的问题,所以提出了内存分页机制。内存分页改变了分段这种粗犷的内存管理方式,它将整个虚拟内存和物理内存空间分成一段段固定大小的片,虚拟内存和物理内存的映射以这个片为最小单位进行管理,我们把这个片称为页,在linux系统上,页的大小为4KB。

分段机制用段表来进行虚拟地址和物理地址的映射,分页机制是用页表来进行映射的。在分页机制下,虚拟地址分为两部分,页编号和页偏移。页编号作为页表的索引,页表包含物理页编号,这个物理页编号与页偏移的组合就形成了物理内存地址。

​内存分页由于内存空间都是预先划分好的,页与页之间是紧密排列的,不会存在像分段中存在的段与段之间的空隙,所以不会有外部碎片。但是分页最小的管理单位是页,即使需要的内存没有一页,也会分配一个页,所以也会存在内部碎片,有内存浪费的情况。

4.3 多级页表上面的页表映射关系看起来还是比较简单的,不过我们得考虑一下页表的大小问题,因为页表是存储在内存当中的,而内存大小比较有限。

以32位linux系统为例,我们来计算一下:32位系统,一个进程的虚拟内存空间为4GB,linux的页大小为4KB,所以需要4GB/4KB=(1024*1024)页,内存分页每一个页表项大小为4字节,可以对应1个页,所以需要的页表项大小就是1024*1024*4 = 4M字节。

4M大小看起来没多大,但是这只是一个进程需要的页表项大小,如果有100个进程,1000个进程呢?显然页表项占用的内存空间就会非常多,而且这还只是32位系统,如果是64位系统,所需要的页表项就更多了。

为了解决页表项过多的问题,Linux 提供了两种机制,也就是大页(HugePage)和多级页表。

大页,顾名思义,就是比普通页更大的内存块,常见的大小有 2MB 和 1GB。页大小变大了,所需要的页自然就少了,页表项也变少了,页表占用的空间也就变少了。大页通常用在使用大量内存的进程上,比如 Oracle、DPDK 等。

多级页表就是把内存分成区块来管理,将原来的映射关系改成区块索引和区块内的偏移。由于虚拟内存空间通常只用了很少一部分,那么,多级页表就只保存这些使用中的区块,这样就可以大大地减少页表的项数。

32位系统使用的是两级页表,将虚拟地址分为三部分:

  • 22~31(10位):一级页号(页目录表)

  • 12~21(10位):二级页号

  • 0~11(12位): 页内偏移量

10位的一级页号,可以表示0~1023,每一个一级页号可以对应一个内存块号,通过这个内存块号,可以找到一个二级页表,在二级页表中可以找到二级页号对应的内存块,然后再加上12位的页内偏移量,就可以算出对应的物理内存。

​所以我们可以知道:

  • 一级页表只有1个,一个一级页表有1024个页表项,每个页表项记录一个二级页表号,总共可以记录1024个二级页表,占用内存大小1024*4 = 4KB

  • 二级页表有1024个,每个页表有1024个页表项,总共占用内存大小1024*1024*4 = 4M

看起来二级页表总共需要4KB+4M的内存空间来存放所有页表项,比一级页表项占用的空间还要多,但实际并不是这样的。

根据局部性原理可知,很多时候,进程在一段时间内只需要访问某几个页面就可以正常运行了。因此没有必要让整个页面都常驻内存,不用把所有的页表都调入内存,只在需要它时才调入,所以只需要一张索引表来告诉我们第几张页表该上哪里去找,就能解决页表的查询问题。建立多级页表的目的在于建立索引,以便不用浪费主存空间去存储无用的页表项,也不用盲目地顺序式查找页表项。

所以,如果某个一级页表的页表项没有被用到,也就不需要创建这个页表项对应的二级页表了,即可以在需要时才创建二级页表,所以两级页表的方式实际上比一级页表大大节省了页表项的内存空间占用。

上面为了理解简单,是以32位系统为例,在我们现在实际使用的linux 64位系统上,采用的方案是4级页表,分别是:

  • PGD:page Global directory(47-39), 页全局目录

  • PUD:Page Upper Directory(38-30),页上级目录

  • PMD:page middle directory(29-21),页中间目录

  • PTE:page table entry(20-12), 直接页表

​4.4 TLBCPU在执行指令时,通过MMU进行虚拟地址到物理地址的转换,这个转换关系是存在页表中的,而页表是存在于内存中的,相对于访问CPU的寄存器或者Cache,访问内存还是要慢得多,所以,根据程序运行的局部性原理以及参照CPU的三级缓存设计思想,我们把页表中的热点表项存到了CPU中叫做TLB(Translation Lookaside Buffer)的硬件里面了。

有了 TLB 后,那么 CPU 在寻址时,会先查 TLB,如果没找到,才会继续查常规的页表。TLB的访问速度非常快,和寄存器相当,比L1访问还快。有了TLB之后,CPU访问某个虚拟内存地址的过程如下:

​5、Linux系统中如何查看进程实际占用的内存大小前面说的是CPU和操作系统对于虚拟内存的映射管理机制,由于程序使用的都是虚拟内存,那在Linux系统上,如何查看进程实际占用的内存大小呢?

我们一般都会用top命令去查看单个进程占用的内存大小,但是我们得结合前面的理论知识去理解top命令查看的参数的实际意义:

​这些数据,包含了进程最重要的几个内存使用情况,我们来看一下:

  • VIRT 是进程虚拟内存的大小,只要是进程申请过的内存,即便还没有真正分配物理内存,也会计算在内。

  • RES 是常驻内存的大小,也就是进程实际使用的物理内存大小,但不包括 Swap 和共享内存。

  • SHR 是共享内存的大小,比如与其他进程共同使用的共享内存、加载的动态链接库以及程序的代码段等。

  • %MEM 是进程使用物理内存占系统总内存的百分比。

这里我们需要注意两点:

  • 虚拟内存通常并不会全部分配物理内存。从上面的输出,你可以发现每个进程的虚拟内存都比常驻内存大得多。

  • 共享内存 SHR 并不一定是共享的,比方说,程序的代码段、非共享的动态链接库,也都算在 SHR 里。当然,SHR 也包括了进程间真正共享的内存。所以在计算多个进程的内存使用时,不要把所有进程的 SHR 直接相加得出结果。

好了,虚拟内存机制的内容就先讲到这里了。

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

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

相关文章

在windows上安装Cmake软件

Cmake是一个跨语言、跨平台、开源的编译工具,可以编译C、C、Note.js、JavaScript、C#、Java、Python、Php、Object-C、Ruby等工程,需要设置对应的src源码目录、ext第三方依赖目录、CMakeList.txt构建列表,再使用cmake命令即可。     2023年…

Spring Boot日志基础使用 设置日志级别

然后 我们来说日志 日志在实际开发中还是非常重要的 即可记录项目状态和一些特殊情况发生 因为 我们这里不是将项目 所以 讲的也不会特别深 基本还是将Spring Boot的日志设置或控制这一类的东西 相对业务的领域我们就不涉及了 日志 log 初期最明显的作用在于 开发中 你可以用…

SpringBoot介绍与搭建

SpringBoot Spring Boot 是由 Pivotal 团队提供的在 spring 框架基础之上开发的框架, 其设计目的是用来简化应用的初始搭建以及开发过程。对spring搭建过程中的繁琐模板配置以及版本依赖问题进行解决(优化)不使用xml进行配置,提供其他的方式进行配置,使…

6种超简单方法告诉你卧室墙面该怎么去装饰

创意 1:夏日宁静 潮汐的变化并不意味着你的夏日精神也要随之改变。用海洋壁画装饰海滩吧,让咸咸的空气、沙滩上的脚趾和木板道上的梦想随风飘散。 创意 2:漂浮艺术 如果你是那种喜欢少走弯路的人,可以考虑完全避开 “墙面艺术&qu…

LabVIEW液压支架控制系统的使用与各种配置的预测模型的比较分析

LabVIEW液压支架控制系统的使用与各种配置的预测模型的比较分析 模型预测控制在工业中应用广泛。这种方法的优点之一是在求解最优控制问题时能够明确考虑对输入和输出状态施加的约束。控制对象模型用于有限时间范围内最优控制的实时计算。所使用的数学设备允许从具有单输入和单…

​7.3 项目3 贪吃蛇(控制台版) (A)​

C自学精简实践教程 目录(必读) 主要考察 模块划分 / 文本文件读取 UI与业务分离 / 模块划分 控制台交互 / 数据抽象 需求 用户输入字母表示方向,实现贪吃蛇游戏 规则:碰到边缘和碰到蛇自己都算游戏结束 输入文件格式 data.txt 6 7 0 0 0 0 0 0…

CSS中你不得不知道的盒子模型

目录 1、CSS的盒子模型1.1 css盒子模型有哪些:1.2 css盒子模型的区别1.3 通过css如何转换css盒子模型 1、CSS的盒子模型 1.1 css盒子模型有哪些: 标准盒子模型、怪异盒子模型(IE盒子模型) 1.2 css盒子模型的区别 标准盒子模型&a…

人工智能安全吗?OpenAI正在让大模型和人类“对齐”-确保ChatGPT比人类聪明的同时还遵循人类意图...

“ 人工智能的发展给人类带来福祉的同时,也存在巨大的风险。为了防止人工智能走向不受控制的方向,对齐技术应运而生。通过人工智能安全技术的研究与探索,我们期望在人工智能能力成熟前建立起有效的对齐机制,让人工智能能够真正为人…

【狂神】Spring5(Aop的实现方式)

今天没有偷懒,只是忘了Mybatis,所以去补课了~ ┏━━━━━━━━━━━━━━━┓ NICE PIGGY PIG.. ┗━━━━━━━△━━━━━━━┛ ヽ(・ω・)ノ | / UU 1.Aop实现方式一 1.1、什…

Redis-Cluster集群操作--添加节点、删除节点

一、环境部署 部署好Redis-Cluster集群,参考上个本人的博客:Redis-Cluster集群的部署(详细步骤)_是胡也是福的博客-CSDN博客 新准备一台机器,修改主机名,关闭防火墙和selinux,参考&#xff1a…

MySQL 使用规范 —— 如何建好字段和索引

一、案例背景 二、库表规范 1. 建表相关规范 2. 字段相关规范 3. 索引相关规范 4. 使用相关规范 三、建表语句 三、语句操作 1. 插入操作 2. 查询操作 四、其他配置 1. 监控活动和性能: 2. 连接数查询和配置 本文的宗旨在于通过简单干净实践的方式教会读…

【Python】应用:Python数据分析基础

😏★,:.☆( ̄▽ ̄)/$:.★ 😏 这篇文章主要介绍Python数据分析基础。 无专精则不能成,无涉猎则不能通。——梁启超 欢迎来到我的博客,一起学习,共同进步。 喜欢的朋友可以关注一下,下次…

解决:在宝塔站点上添加域名(8080,888等端口)显示“端口范围不合法“

在宝塔上给站点添加域名访问时,有时候需要部署站点的端口为8080或者888端口。但是添加之后显示: 解决方法 点击宝塔上的文件 切换到根目录搜索 public.py 包含子目录 选择这个: 修改其中的checkport函数: 最后,重启面…

【CSS】CSS 布局——浮动

浮动的主要用途是在一个容器中创建多列布局,以及在文本周围环绕图片等内容。然而,由于现代的 CSS 布局技术的发展,浮动已经不再是主要的布局方法,但了解它仍然是有用的。 下面我将详细解释 CSS 浮动的用法、特性和影响。 浮动的…

泛型-使用总结

泛型: 1、介绍: ! 2、泛型类: 3、接口: 4、泛型方法: 泛型:不支持多态 !

常见路由跳转的几种方式

常见的路由跳转有以下四种&#xff1a; 1. <router-link to"跳转路径"> /* 不带参数 */ <router-link :to"{name:home}"> <router-link :to"{path:/home}"> // 更建议用name // router-link链接中&#xff0c;带/ 表示从根…

sql:SQL优化知识点记录(八)

&#xff08;1&#xff09;索引面试题分析 所谓索引&#xff1a;就是排好序的快速查找数据结构&#xff0c;排序家查找是索引的两个用途 select * 在where使用到了索引&#xff0c;当select * 有模糊查询%在左边索引会失效 当select * where后面索引的顺序发生变化&#xff0…

51单片机项目(7)——基于51单片机的温湿度测量仿真

本次做的设计&#xff0c;是利用DHT11传感器&#xff0c;测量环境的温度以及湿度&#xff0c;同时具备温度报警的功能&#xff1a;利用两个按键&#xff0c;设置温度阈值的加和减&#xff0c;当所测温度大于温度阈值的时候&#xff0c;蜂鸣器就会响起&#xff0c;进行报警提示。…

浅谈MES系统中的物料管理

导 读 ( 文/ 2269 ) 物料管理是对企业在生产中使用的各种物料的采购、保管和发放环节进行计划与控制等管理活动的总称。物料管理是企业生产执行的基础&#xff0c;它接收来自生产执行层的物料请求&#xff0c;通过一系列物料管理活动的执行&#xff0c;对生产执行层进行及…

长城网络靶场,第一题笔记

黑客使用了哪款扫描工具对论坛进行了扫描&#xff1f;&#xff08;小写简称&#xff09; 第一关&#xff0c;第三小题的答案是awvs 思路是先统计查询 然后过滤ip检查流量 过滤语句&#xff1a;tcp and ip.addr ip 114.240179.133没有 第二个101.36.79.67 之后找到了一个…