虚拟内存

没有虚拟内存带来的问题 我们在运行程序时,总希望内存越大越好,这样可以运行的程序也可以越来越多,越来越大。但现实是,这样做是有很多成本的。最简单的成本就是钱。过去,爲了解决这个问题,人们写程序时,会考虑到内存大小,如果自己写的程序大过内存,那麽还要做一些工作,比如:想办法,只加载一部分代码,当有更多的需要时,再加载更多。

这是一个相当有开创性的想法。这一想法在后来诞生了虚拟内存的伟大概念。(我瞎掰的)

虚拟内存的基本原理

简单来说,当我们运行一个进程时,计算机并不会把整个程序都加载进内存里,而是放在硬盘上,当有需要时,再将加载需要的那一部分。

那我们怎麽知道要加载哪一部分?比如说,我的下一条指令是到要变量 B,可是变量 B 没加载进内存,我也不知道它在哪,要怎麽去硬盘里找?

那好吧,爲了做好映射关係,我们还要在进程运行时,给进程一张表,它负责进行内存和硬盘之间的关係。比如说,变量 B 的内存是 Ox11011,发现这在内存里不存在,那麽我们就去表上找,发现它在对应着内存里的 Ox00100,那麽我们就把内存里的那个 B 给它。

但如果每一个变量,每个字都做映射,都要到硬盘里找,这实在是太麻类了。更重要的是,对硬盘做一次查询,所消耗的时间太长了,在 CPU 看来,每进行一次,都要花费 10 个月的时间。爲了省时省力,我们把程序分成多个部分,每一部分叫做一个虚拟页。同时,内存里也分成多个页,叫物理页。每一个物理页都可以容纳一个虚拟页。而映射它们的那张表,叫页表。

现在我们来看看一个实例,虚拟地址是如何转换成物理地址的。

一般而言,计算机是通过多级页表进行转换的,但爲了简单,这里就讨论只有一张页表的情况。

现在设有 14 位的虚拟地址,要转换成 12 位的真实地址。其中,页表大小爲 64(2的6次方) B。

通过虚拟地址位数和页大小,我们可以换算得到,共有 21426=256\cfrac{2^{14}}{2^6}=25626214​=256 项。也就是说,要 8 位数才能表示完所有页(VPN)。那麽 14−8=614-8=614−8=6 来表示偏移量(VPO)。

由虚拟地址的偏移量,我们可以知道真实地址的偏移量。(两者相等)

比如下面,我们已知虚拟地址爲 00101100100010 (VPN(8) VPO(6))

那麽 00101100 爲虚拟页的首地址(页码)。100010 爲虚拟地址的偏移量。

通过查页表,将 00101100 映射爲真实地址,假设爲 11010011,再加上偏移量 100010 得到真实地址 11010011100010.

00101100100010 => 11010011100010

到这里,地址转换就完成了!

如果是多级页表,也只是通过多个 虚拟首地址(页码) 的转换而已。变成 VPN VPN VPN VPN … VPO。

它带来的好处

简化链接、简化内存分配(不连续)、进程上的安全(不会干扰到其它进程)

虚拟内存的出现,给我们带来了意想不到的好处。

首先,它简化了链接过程。当程序编译完成时,程序里的每一个变量,每一个函数,还只是对应一个相对的地址,想要执行,还得有一个动态链接过程。由于虚拟页的存在,生成的可执行文件的地址可以独立于物理内存,这样设计链接器时,就简单得多。

另外,因爲虚拟页与物理页之间是靠页表进行映射的,因此,一个进程的不周数据在物理内存里,可以不连续。毕竟查表就能知道它们的位置了,连不连续已经不重要了。于是,这样内存可以更动态,更轻鬆的进行分配。

最后,可以虚拟化进程了。进程因爲页表的存在,不会存在相互干扰了。两个进程因爲页表与页表之间不同,映射到的地方不同,那怕虚拟地址相同,物理内存上也不会相同,它们之间彷彿有一道牆,阻止它们相互侵犯。

虚拟化

没错,地址也可以虚拟化。还有多少东西可以虚拟化呢?真的好期待啊。