【40】理解内存(上):虚拟内存和内存保护是什么?★★★★★

【计算机组成原理】学习笔记——总目录

引言

计算机有五大组成部分,分别是:运算器、控制器、存储器、输入设备和输出设备。如果说计算机最重要的组件,是承担了运算器和控制器作用的 CPU,那内存就是我们第二重要的组件了。内存是五大组成部分里面的存储器,我们的指令和数据,都需要先加载到内存里面,才会被 CPU 拿去执行。

专栏第 9 讲,我们讲了程序装载到内存的过程。
在这里插入图片描述

可以知道,在我们日常使用的 Linux 或者 Windows 操作系统下,程序并不能直接访问物理内存(需要虚拟内存地址到物理内存地址的映射)

我们的内存需要被分成固定大小的页(Page),然后再通过虚拟内存地址(Virtual Address)到物理内存地址(Physical Address)的地址转换(Address Translation),才能到达实际存放数据的物理内存位置。而我们的程序看到的内存地址,都是虚拟内存地址

既然如此,这些虚拟内存地址究竟是怎么转换成物理内存地址的呢?这一讲里,我们就来看一看。

一、简单页表

想要把虚拟内存地址,映射到物理内存地址,最直观的办法,就是来建一张映射表。这个映射表,能够实现虚拟内存里面的页,到物理内存里面的页的一一映射。这个映射表,在计算机里面,就叫作页表(Page Table)

页表把一个内存地址分为**页号(Directory)偏移量(Offset)**两个部分。

以一个32位的内存地址为例来理解:

  • 其实,前面的高位,就是内存地址的页号。后面的低位,就是内存地址里面的偏移量。
  • 做地址转换的页表,只需要保留虚拟内存地址的页号和物理内存地址的页号之间的映射关系就可以了。
  • 同一个页里面的内存,在物理层面是连续的。
  • 以一个页的大小是 4K 字节(4KB)为例,我们需要 20 位的高位,12 位的低位。

在这里插入图片描述
总结一下,对于一个内存地址转换,其实就是这样三个步骤

  1. 把虚拟内存地址,切分成页号和偏移量的组合;
  2. 从页表里面,查询出虚拟页号,对应的物理页号(这之间有一个映射关系,通过虚拟页号,按索引查询物理页[虚拟页号]=物理页的页号);
  3. 直接拿物理页号,加上前面的偏移量,就得到了物理内存地址。

在这里插入图片描述
【计算一个页表需要的内存空间】
看起来这个逻辑似乎很简单,很容易理解,不过问题马上就来了。你能算一算,这样一个页表需要多大的空间吗?

我们以 32 位的内存地址空间为例:
32 位的内存地址空间,页表一共需要记录 2^ 20 个到物理页号的映射关系。这个存储关系,就好比一个 2^20 大小的数组。
一个页号是完整的 32 位的 4 字节(Byte),这样一个页表就需要 4MB 【2^20x4/1024/1024=4MB】的空间。听起来 4MB 的空间好像还不大啊,毕竟我们现在的内存至少也有 4GB,服务器上有个几十 GB 的内存和很正常。
在这里插入图片描述

【每一个进程,都有属于自己独立的虚拟内存地址空间】
不过,这个空间可不是只占用一份哦。我们每一个进程,都有属于自己独立的虚拟内存地址空间。这也就意味着,每一个进程都需要这样一个页表。不管我们这个进程,是个本身只有几 KB 大小的程序,还是需要几 GB 的内存空间,都需要这样一个页表。

如果你用的是 Windows,你可以打开你自己电脑上的任务管理器看看,现在你的计算机里同时在跑多少个进程,用这样的方式,页表需要占用多大的内存。【我的电脑计算:138x4MB=552MB
在这里插入图片描述

这还只是 32 位的内存地址空间,现在大家用的内存,多半已经超过了 4GB,也已经用上了 64 位的计算机和操作系统。这样的话,用上面这个数组的数据结构来保存页面,内存占用就更大了【按每页4KB算,2^52*8/1024/1024/1024=33554432GB,计算的对吗?这也太大了吧】。那么,我们有没有什么更好的解决办法呢?你可以先仔细思考一下。

二、多级页表

1、引言

仔细想一想,我们其实没有必要存下这 2^20 个物理页表啊。大部分进程所占用的内存是有限的,需要的页也自然是很有限的。我们只需要去存那些用到的页之间的映射关系就好了。如果你对数据结构比较熟悉,你可能要说了,那我们是不是应该用哈希表(Hash Map)这样的数据结构呢?

很可惜你猜错了。在实践中,我们其实采用的是一种叫作多级页表(Multi-Level Page Table)的解决方案。这是为什么呢?为什么我们不用哈希表而用多级页表呢?别着急,听我慢慢跟你讲。

2、整个进程内存空间分配:两头空,中间实

我们先来看一看,一个进程的内存地址空间是怎么分配的。在整个进程的内存地址空间,通常是“两头实、中间空”。在程序运行的时候,内存地址从顶部往下,不断分配占用的栈的空间。而堆的空间,内存地址则是从底部往上,是不断分配占用的。

栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有 的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将 提示overflow。因此,能从栈获得的空间较小。


堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储 的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。

所以,在一个实际的程序进程里面,虚拟内存占用的地址空间,通常是两段连续的空间。而不是完全散落的随机的内存地址。而多级页表,就特别适合这样的内存地址分布。

3、实例分析【一个1级页表可以映射128KB大小的物理内存,一个填满的2级页表可以映射4MB大小的物理内存】

参考文章学习:https://www.polarxiong.com/archives/%E5%A4%9A%E7%BA%A7%E9%A1%B5%E8%A1%A8%E5%A6%82%E4%BD%95%E8%8A%82%E7%BA%A6%E5%86%85%E5%AD%98.html

我们以一个 4 级的多级页表为例,来看一下。同样一个虚拟内存地址,偏移量的部分和上面简单页表一样不变,但是原先的页号部分,我们把它拆成四段,从高到低,分成 4 级到 1 级这样 4 个页表索引。

在这里插入图片描述
对应的,一个进程会有一个 4 级页表。我们先通过 4 级页表索引,找到 4 级页表里面对应的条目(Entry)。这个条目里存放的是一张 3 级页表所在的位置。4 级页面里面的每一个条目,都对应着一张 3 级页表,所以我们可能有多张 3 级页表

找到对应这张 3 级页表之后,我们用 3 级索引去找到对应的 3 级索引的条目。3 级索引的条目再会指向一个 2 级页表。同样的,2 级页表里我们可以用 2 级索引指向一个 1 级页表。

而最后一层的 1 级页表里面的条目,对应的数据内容就是物理页号了。在拿到了物理页号之后,我们同样可以用“页号 + 偏移量”的方式,来获取最终的物理内存地址。

我们可能有很多张 1 级页表、2 级页表,乃至 3 级页表。但是,因为实际的虚拟内存空间通常是连续的,我们很可能只需要很少的 2 级页表,甚至只需要 1 张 3 级页表就够了

事实上,多级页表就像一个多叉树的数据结构,所以我们常常称它为页表树(Page Table Tree)因为虚拟内存地址分布的连续性,树的第一层节点的指针,很多就是空的,也就不需要有对应的子树了所谓不需要子树,其实就是不需要对应的 2 级、3 级的页表。找到最终的物理页号,就好像通过一个特定的访问路径,走到树最底层的叶子节点

在这里插入图片描述

以这样的分成 4 级的多级页表来看,每一级如果都用 5 个比特表示。那么每一张某 1 级的页表,只需要 2^5=32 个条目。如果每个条目还是 4 个字节,那么一共需要 128 个字节

【一个1级页表可以映射128KB大小的物理内存,一个填满的2级页表可以映射4MB大小的物理内存】
而一个 1 级索引表,对应 32 个 4KB 的(页)也就是 128KB 的(物理内存的)大小。一个填满的 2 级索引表,对应的就是 32 个 1 级索引表,也就是 4MB 的大小。

“32个4KB”中“4KB”的(页)理解【不好理解的点】:
2^12(12:偏移量占用的位数)=4096Byte=4096/1024KB=4KB 所以,一个一级索引表 可以映射 128kB(32*4KB)的物理内存大小。

一个填满的2级索引表: 32*128KB=4096KB=4MB

5、一个程序的实际测算【8M程序:多级页表:需要9KB内存空间;简单页表:需要4MB 内存空间;相差近500倍】

我们可以一起来测算一下,一个进程如果占用了 8MB 的内存空间,分成了 2 个 4MB 的连续空间。那么,它一共需要 2 个独立的、填满的 2 级索引表,也就意味着 64 个 1 级索引表2 个独立的 3 级索引表1 个 4 级索引表。一共需要 69 个索引表,每个 128 字节,大概就是 9KB (69x128/1024=83625) 的空间。

比起 4MB 来说,只有其大小的 1/500

【重要理解!必须明白】
理解1:8MB内存空间:栈空间4M,堆空间4M(最上、最下下两部分),需要2个三级索引表【四级索引表的最上、最下两个条目—>检索到2个三级索引表】
理解2:两个填满的二级索引表不连续,一个堆一个栈,地址相距远需要两个三级索引表。

6、多级页表增加了查询时间【4级页表需要4次内存访问】

不过,多级页表虽然节约了我们的存储空间,却带来了时间上的开销,所以它其实是一个“以时间换空间”的策略。原本我们进行一次地址转换,只需要访问一次内存就能找到物理页号,算出物理内存地址。但是,用了 4 级页表,我们就需要访问 4 次内存,才能找到物理页号了。

在优化页表的过程中,我们可以观察到,数组这样的紧凑的数据结构,以及树这样稀疏的数据结构,在时间复杂度和空间复杂度的差异。

7、新的问题

我们在前面两讲讲过,内存访问其实比 Cache 要慢很多。我们本来只是要做一个简单的地址转换,反而是一下子要多访问好多次内存对于这个时间层面的性能损失,我们有没有什么更好的解决办法呢?那请你一定要关注下一讲的内容哦!

三、补充

参考:【虚拟内存与物理内存的联系与区别】https://blog.csdn.net/lvyibin890/article/details/82217193
在这里插入图片描述

在这里插入图片描述在这里插入图片描述
在这里插入图片描述

四、总结【个人总结的重点】(本节比较难理解!!!)

  • 在我们日常使用的 Linux 或者 Windows 操作系统下,程序并不能直接访问物理内存(需要虚拟内存地址到物理内存地址的映射)。
  • 我们的程序看到的内存地址,都是虚拟内存地址。

  • 虚拟内存 和 物理内存 的区别
  • 页表(Page Table):能够实现虚拟内存里面的页,到物理内存里面的页的一一映射。
  • 简单页表
  • 多级页表
    • 整个进程内存空间分配:两头空,中间实
    • 实例分析【32位:一个1级页表可以映射128KB大小的物理内存,一个填满的2级页表可以映射4MB大小的物理内存】
    • 一个程序的实际测算【8M程序:多级页表:需要9KB内存空间;简单页表:需要4MB 内存空间;相差近500倍
    • 多级页表增加了查询时间【时间换空间的策略:4级页表需要4次内存访问】

疑问:

64位操作系统的内存地址是:0~2^64吗?
17,179,869,184GB。但实际64位系统的内存只有16G/8G,那物理内存到底是什么范围呢?
在这里插入图片描述

【解答】:

计算机内存的大小对性能有着决定性的影响,32 位寄存器最大寻址空间为 2的32次方,这就决定了 32 位 Windows 10的最大内存寻址空间为 2的32次方即 4 GB

以此类推,64 位操作系统的内存寻址空间为 2的64次方,我们可以理解为无穷大的内存空间。因此,64 位 ≠ 32 位 x 2,他们寻址空间(即能够使用的内存大小)差别根本不在一个数量级。

32位的CPU(准确的说是运行在32位模式下的CPU)只能寻址最大4GB的内存,受制于此,32位的操作系统也只能识别最大4GB的内存,由于在系统中,除了内存之外,还有很多存储设备,因此,真正可以利用的内存空间肯定小于4GB,也就是我们看到的系统属性中显示的3.xxG

但64位CPU则有了很大改变,64位CPU的最大寻址空间为2的64次方bytes,计算后其可寻址空间达到了惊人的16TB(treabytes),即16384GB17,179,869,184GB(16,777,216TB) 。当然这只是理论,从实际应用上,Windows 7 64bit的各版本分别为8GB-192GB,其中,家庭普通版能支持8GB内存,家庭高级版能支持16GB内存,而专业版、企业版和旗舰版最高可支持192GB内存。
在这里插入图片描述

【计算机组成原理】学习笔记——总目录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ElecNoon

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值