什么是虚拟内存?

前言

  本文目的是回顾与梳理所学知识,采用费曼学习法帮助自己巩固学习的同时试图教会不会的人。所以本文不适用于快速阅读提炼知识点

概述

   虚拟内存是虚拟机进行内存管理的一个重要的技术。操作系统将通过一种机制将虚拟内存和物理内存映射起来。
  学编程的应该都有这么一个概念 “引入一个新的技术,就必然会带来新的问题。"因为,没有十全十美的机制和技术,只有最合适的。那么虚拟内存到底给我们带来了哪些好处,它自己又有哪些新的问题呢?

什么是虚拟内存地址和物理地址

  • 我们程序所使用的内存地址叫做虚拟内存地址(Virtual Memory Address)
  • 实际存在硬件里面的空间地址叫物理内存地址(Physical Memory Address)。

  这就好像我们网购的地址,中国湖南省邵阳市xx区 一样是虚拟地址,以前的邵阳不叫邵阳叫宝安,以前的赵云放到现在也只能大喊一句,我乃石家庄赵子龙。不管怎么命名这块地还是在这里,这个是不变的,也就是物理地址。

为什么需要虚拟内存

  系统运行时,经常会有多个进程在同时运行。如果,这时候我的进程A先在一个物理地址上给我新加了一个值比如说,作者点赞数量 +100。紧接着后面一个 进程B 也要对我这个地址的值做一个操作,我点赞数量 +100这个值给覆盖了。这我能忍吗?肯定不能忍。我能忍,操作系统也不能忍。
  很明显,进程直接访问物理地址是不可靠的。

直接使用物理地址还要求程序清楚对每一个操作的变量的地址位置和分布,还有内存分配与回收等许多的问题。这种不便性就要求引入新的技术来解决——虚拟内存。

  那虚拟内存是如何解决这件事的呢?操作系统会给每一个进程分配进程独享的虚拟内存。进程直接对自己的虚拟内存进行操作,然后操作系统会自己映射到物理地址。这样一来既解决了进程之间互相干扰的问题,又方便我们操作和分配内存。
  操作系统对内存进行划分与映射的方式主要有两类:分段分页。而且这两种方式也可以结合。

内存映射

  首先在讲分段和分页之前我们需要先知道什么是内存碎片,什么又是内存交换。这个用于比较两个映射机制的优缺点。

内存碎片

在这里插入图片描述

内存外部碎片:内存外部碎片指的是物理内存中的一些零散的空闲块或未分配的空间,它们位于已分配的内存块之间或者是未分配内存区域的边界上。这些零散的空闲块无法被利用,虽然它们的总和可能足够容纳一个新的进程或者内存分配请求,但是由于它们的分散性,无法满足连续的内存需求。内存外部碎片是由于内存分配和释放造成的,当进程释放了一部分内存但不连续时,就会产生内存外部碎片。
  此时如果要打开一个 256MB 内存的程序就会失败,虽然剩下的内存和刚刚被释放出的内存刚好够,却无法分配给该程序。

内存内部碎片:内存内部碎片指的是已经分配给进程但实际上没有被使用的内存空间。当进程请求一定大小的内存块时,内存管理系统可能会分配比请求的大小更大的内存块,这样就会产生内部碎片。因为进程无法充分利用这些额外的内存空间,它们被浪费掉了。内存内部碎片主要是由于内存分配策略或对齐要求造成的。
  就好像系统给抖音分配了 256MB 的内存,但是它还有32MB没有用完,但是这部分也无法被其他进程使用,所以被浪费掉了。

内存交换

  当系统中运行的进程需要更多内存空间但物理内存已满时,操作系统可以选择将某些进程或者进程的页面交换到辅助存储设备上。这个过程涉及到将页面的内容从物理内存复制到磁盘上的交换文件中(换出),然后在需要时再将页面从交换文件中加载回物理内存(换入)。

  内存交换的目的是为了扩展可用的内存容量,以满足进程的需求。它允许运行在物理内存中的进程数目超过物理内存的限制。当某个进程被交换出去时,它的页面在物理内存中空出空间,可以被其他进程使用。内存交换也会引入一定的性能开销。因为磁盘的访问速度比内存慢得多,所以将页面交换到磁盘上会导致访问延迟增加,从而影响系统的响应时间和性能。因此,内存交换只在必要时才会发生,通常是在系统内存紧张且没有其他更好的选择时。

内存分段

  内存分段的思路就是,根据逻辑上的属性进行判断,比如:栈段、堆段、代码段、数据段。根据不同属性的段所需要的空间大小来分配一块儿连续内存。
我们看看CPU是如何找到我们想要的地址。
在这里插入图片描述

段选择因子段内偏移量

  段选择因子就保存在段寄存器里面。段选择子里面最重要的是段号,用作段表的索引。段表里面保存的是这个段的基地址段的界限特权等级等。

  虚拟地址中的段内偏移量应该位于 0 和段界限之间,如果段内偏移量是合法的,就将段基地址加上段内偏移量得到物理内存地址。

标志位
  这里面保存了,段属性(数据段、代码段)、段的特权等级、段操作位(用于进行一些段的特殊操作)。

  在上面,知道了虚拟地址是通过段表与物理地址进行映射的,分段机制会把程序的虚拟地址分成 4 个段,每个段在段表中有一个项,在这一项找到段的基地址,再加上偏移量,于是就能找到物理内存中的地址,如下图:

在这里插入图片描述

总结

  内存分段虽然解决了我们直接访问物理内存的缺点,但是仍然有不足之处。由于内存分段所分配的大小是根据段的需要。虽然避免了内存的内部碎片,但是由于段内存不能恰到好处的分配在合适的位置上,还是会导致内存外部碎片的产生。
  在内存紧张,不得不进行内存交换的时候,回收段内存的时候由于比较大往往会导致磁盘有很大的IO开销会影响系统性能。

内存分页

  分页是把整个虚拟和物理内存空间切成一段段固定尺寸的大小。这样一个连续并且尺寸固定的内存空间,我们叫页(Page)。在 Linux 下,每一页的大小为 4KB。通过页表完成地址的映射。页表是存储在内存里的,内存管理单元 (MMU)就做将虚拟内存地址转换成物理地址的工作。

  而当进程访问的虚拟地址在页表中查不到时,系统会产生一个缺页异常,进入系统内核空间分配物理内存、更新进程页表,最后再返回用户空间,恢复进程的运行。
在这里插入图片描述
  由于页式存储中,每一页的大小都是大小固定的,而且页与页之间是紧密排列的。所以不会有内存外部碎片的产生,但不是所有的进程内存都是 4KB 的整数倍,所以在避免了内存外部碎片的同时,不可避免的会产生内存的内部碎片。由于大小只有4KB,因此浪费的内存碎片也比较少。也正是因为内存碎片产生的大小比较小,能够存储的数据也较小,因此在进行内存交换的时候会比段式存储更快

  这么看起来页式存储的缺点好像只有不多的内存内部碎片。但是真的只是如此吗?由于虚拟内存需要将全部的内存都映射到,此时如果有很多进程在同时运行,那么光是维护这个映射关系的开销都是有些令人无法接受的。

在 32 位的环境下,虚拟地址空间共有 4GB,假设一个页的大小是 4KB(2^12),那么就需要大约 100 万 (2^20) 个页,每个「页表项」需要 4 个字节大小来存储,那么整个 4GB 空间的映射就需要有 4MB 的内存来存储页表。
这 4MB 大小的页表,看起来也不是很大。但是要知道每个进程都是有自己的虚拟地址空间的,也就说都有自己的页表。那么,100 个进程的话,就需要 400MB 的内存来存储页表,这是非常大的内存了,更别说 64 位的环境了。

为了解决存储页表带来的空间上的浪费,引入了多级页表的方法。

多级页表

在这里插入图片描述
  引入多级列表以后,你可能会问,分了二级表,映射 4GB 地址空间就需要 4KB(一级页表)+ 4MB(二级页表)的内存,这样占用空间不是更大了吗?当然不是,根据局部性原理可以知道,我们其实用不到这么多的页表。

局部性原理(Principle of Locality)是计算机系统设计中的一个重要原则,指出程序在执行过程中往往倾向于访问邻近的内存位置或使用相似的数据项。局部性原理包括两个主要方面:

  1. 时间局部性(Temporal Locality):时间局部性指的是程序在一段时间内对同一数据的重复访问倾向。如果一个数据项被访问过,那么在不久的将来可能会再次被访问。例如,循环结构。

  2. 空间局部性(Spatial Locality):空间局部性指的是程序在一段时间内倾向于访问相邻的内存位置。当程序访问某个内存地址时,它很可能会在不久之后访问其附近的内存地址。这可以是因为程序连续地处理数组或数据结构的元素,或者利用了数据的位置相关性。

  如果某个一级页表的页表项没有被用到,也就不需要创建这个页表项对应的二级页表了,即可以在需要时才创建二级页表。做个简单的计算,假设只有 20% 的一级页表项被用到了,那么页表占用的内存空间就只有 4KB(一级页表) + 20% * 4MB(二级页表)= 0.804MB,这对比单级页表的 4MB 是不是一个巨大的节约?

总结一下

  1. 如果某个一级页表的页表项没有被用到,也就不需要创建这个页表项对应的二级页表了。
  2. 对比原来需要 一百万个页才能映射完物理内存,如今只要,1024个页就行了
  3. 局部性原理

  上面三点构成了多级页表的优势与简单页表的区别。

TLB

  即使后面要映射的物理内存增加,我们仍然可以通过增加页表的层数来缩减空间上的开销,但是时间上的开销呢?多级地址的映射和转换都需要额外的时间开销,这个时候就要引入一个新的方法了 TLB

TLB也是利用了局部性原理在CPU中,缓存最近常被访问的页表项。CPU会通过 MMU(内存管理单元)

总结

虚拟内存的主要作用包括:

扩展可用内存容量:虚拟内存允许每个进程使用比实际物理内存更大的地址空间。通过将不常用的数据和指令存储在磁盘上,只在需要时才将其加载到物理内存中,可以允许运行在内存中的进程数目超过物理内存的限制。(通过内存交换)

内存管理和保护:虚拟内存通过分页或分段的方式将进程的地址空间划分为固定大小的块(页或段),并进行内存管理和保护。它可以将物理内存划分为页面或段的框架,并将虚拟地址映射到物理地址,以实现内存的动态分配和回收,并提供访问权限控制,保护进程间的内存隔离。(通过分配相互隔离的虚拟内存)

提高程序的执行性能:虚拟内存利用了局部性原理,将进程所需的数据和指令按需加载到内存中。它可以将常用的页面或段保留在内存中,提高访问速度和程序的执行性能。同时,虚拟内存还可以使用页面置换算法将不常用的页面交换到磁盘上,以便为常用的页面腾出更多的内存空间。

简化编程和提高系统的可靠性:虚拟内存为程序员提供了一个统一的、连续的地址空间,使得编程更加简化。程序可以使用相对于虚拟地址的偏移量来访问内存,而无需考虑物理内存的实际布局。此外,虚拟内存还提供了内存保护和错误处理机制,当程序访问非法内存地址或发生错误时,可以捕获并进行相应的处理,提高系统的可靠性。

参考文章:小林Coding

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值