认识虚拟内存

什么是虚拟内存

虚拟内存是计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续的可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。目前,大多数操作系统都使用了虚拟内存,如Windows家族的“虚拟内存”;Linux的“交换空间”等。

为什么需要虚拟内存

我们知道程序执行指令的时候,程序计数器是顺序地一条一条指令执行下去,这一条条指令就需要连续地存储在一起,所以就需要这块内存是连续的。物理内存是有限的,如果多个程序同时运行的话,访问同一个物理地址的话,就有可能内存地址冲突,怎么办呢?

这时就需要虚拟内存发挥的作用了,程序里有指令和各种内存地址,系统从物理内存申请一段地址,与这个程序指令里用到的内存地址建立映射关系,这样实际程序指令执行的时候,会通过虚拟内存地址,找到对应的物理内存地址执行。对于任何一个程序来说,它看到的都是同样的内存地址。我们只需要维护一个虚拟内存到物理内存的映射表即可。

这种从物理内存申请一段地址建立映射的方法,我们称其为内存分段

看似解决了上面的问题,但这里又引起了新的问题,即内存碎片。由于物理内存每次申请一段地址的时候,都是连接的,如果有三个程序分别执行,中间的程序执行完后,内存也释放回了可用物理内存,此时又来第四个程序,有可能出现内存不足的情况,因为物理内存已经没有了连续的可用地址,如下图所求,共1G内存的情况。

57211af3053ed621aeb903433c6c10d1

假如程序X需要内存为256MB的话,可以看到两个128MB内存是非连续的,程序无法运行,白白浪费掉了中间的128MB空间,这种情况就是内存碎片

怎么解决呢,只能将Python程序运行信息先存储到硬盘上,先释放最下面的256MB空间,然后再重新载入,这样会腾出256MB的空间。

这种将页面写入磁盘的行为我们称为换出,从磁盘载入内存称为换入。

虽然解决了内存碎片的总是,但由于内存和磁盘两者的速度相差实在太大了,看来这种办法效率太差了,还有其它好的办法没有呢?

内存分页

从上面的介绍可以看出主要问题是因为内存碎片内存交换粒度太大,导致大量的内存交换成本过高,如果我们把这个交换粒度变小一些是不是就好多了呢。

我们将物理内存和虚拟内存都按固定大小进行分页,从虚拟内存到物理内存的映射由原来的段映射调整为了按页映射。

由于内存空间都是预先划分好的,也就没有了不能使用的碎片,而只有被释放出来的很多 4KB 的页。即使内存空间不够,需要让现有的、正在运行的其他程序,通过内存交换释放出一些内存的页出来,一次性写入磁盘的也只有少数的一个页或者几个页,不会花太多时间,让整个机器被内存交换的过程给卡住。

内存映射

上面我们讲过程序运行时,需要将虚拟内存地址即程序指令中用到的地址通过页表来转换成对应的物理内存地址,那么两者之间又是如何实现转换的呢?

每个进程都对应自己的虚拟地址,虚拟地址可分为“虚拟页号”和“偏移量”两部分。

我们的程序看到的内存地址,都是虚拟内存地址。

程序运行时通过三步来实现数据读取

  1. 读取虚拟内存地址的“虚拟页号”值
  2. 在“页表”中根据虚拟页号查找对应的物理地址页号(每个页表,保存每个物理内存页的起始地址)
  3. 将物理内存页基地址+虚拟内存地址的偏移量得到真正的物理内存地址
07cd4c3344690055240f215404a286dd

在加载程序的时候,不再需要一次性都把程序加载到物理内存中。我们完全可以在进行虚拟内存和物理内存的页之间的映射之后,并不真的把页加载到物理内存里,而是只在程序运行中,需要用到对应虚拟内存页里面的指令和数据时,再加载到物理内存里面去。

如果发现尚未为虚拟内存分配物理内存,则申请新的内存,此现象称为“缺页”,等使用完后再释放内存。

对于物理内存,操作系统把它分成一块一块大小相同的页,这样更方便管理,例如有的内存页面长时间不用了,可以暂时写到硬盘上,称为换出。一旦需要的时候,再加载进来,叫作换入。这样可以扩大可用物理内存的大小,提高物理内存的利用率。其中换入与换出是以页为单元,每页大小一般为4KB。

0cf2f08e1ceda473df71189334857cf0

每个进程都有操作系统为自己分配的一块地址连接的虚拟内存,同时也有自己的页表。页表中所有页表项必须提前建好,并且要求是连续的。如果不连续,就没有办法通过虚拟地址里面的页号找到对应的页表项了。

为了节省内存,则使用一种类似于多叉树的“多级页表”数据结构来存储虚拟内存和物理内存的对应关系。

多级页表将虚拟内存的“虚拟页号“分成了4段,从高到低,分成 4 级到 1 级这样 4 个页表索引。

614034116a840ef565feda078d73cb76
5ba17a3ecf3f9ce4a65546de480fcc4e

每级页表包含多个条目,每级条目又都保存了下级条目的地址,按4-1级顺序查找虚拟内存对应的物理页基地址。

参考

https://time.geekbang.org/column/article/95209

https://time.geekbang.org/column/article/110474

https://time.geekbang.org/column/article/95223

https://www.cnblogs.com/zhenbianshu/p/10300769.html

FreeBSD下查看物理内存大小

法一:

[root@www ~]# cat /var/run/dmesg.boot | grep memory
real memory  = 2147483648 (2048 MB)
avail memory = 2091028480 (1994 MB)

法二:

[root@www ~]# sysctl -a | grep hw.physmem
hw.physmem: 2134253568

第二个命令和第一个命令有些差别,这个基本上可以忽略.换成MB的话,基本上也是2G的

ECC ,R-ECC,FBD ECC内存的分别

FBD即Fully-buffer DIMM(全缓存模组技术),它是一种串行传输技术,可以提升内存的容量和传输带宽.是Intel在DDR2、DDR3的基础上发展出来的一种新型内存模 组与互联架构,既可以搭配现在的DDR2内存芯片,也可以搭配未来的DDR3内存芯片。FB-DIMM可以极大地提升系统内存带宽并且极大地增加内存最大容量。

FB-DIMM技术是Intel为了解决内存性能对系统整体性能的制约而发展出来的,在现有技术基础上实现了跨越式的性能提升,同时成本也相对低廉。在整 个计算机系统中,内存可谓是决定整机性能的关键因素,光有快的CPU,没有好的内存系统与之配合,CPU性能再优秀也无从发挥。这种情况是由计算机原理所 决定的,CPU在运算时所需要的数据都是从内存中获取,如果内存系统无法及时给CPU供应数据,CPU不得不长时间处在一种等待状态,硬件资源闲置,性能 自然无从发挥。对于普通的个人电脑来说,由于是单处理器系统,目前的内存带宽已经能满足其性能需求;而对于多路的服务器来说,由于是多处理器系统,其对内 存带宽和内存容量是极度渴求的,传统的内存技术已经无法满足其需求了。这是因为目前的普通DIMM采用的是一种“短线连接”(Stub-bus)的拓扑结 构,这种结构中,每个芯片与内存控制器的数据总线都有一个短小的线路相连,这样会造成电阻抗的不继续性,从而影响信号的稳定与完整,频率越高或芯片数据越 多,影响也就越大。虽然Rambus公司所推出的的XDR内存等新型内存技术具有极高的性能,但是却存在着成本太高的问题,从而使其得不到普及。而FB- DIMM技术的出现就较好的解决了这个问题,既能提供更大的内存容量和较理想的内存带宽,也能保持相对低廉的成本。FB-DIMM与XDR相比较,虽然性 能不及全新架构的XDR,但成本却比XDR要低廉得多。

与现有的普通DDR2内存相比,FB-DIMM技术具有极大的优势:在内存频率相同的情况下目前能提供四倍于普通内存的带宽,并且能支持的最大内存容量也 达到了普通内存的24倍,系统最大能支持192GB内存。FB-DIMM最大的特点就是采用已有的DDR2内存芯片(以后还将采用DDR3内存芯片),但 它借助内存PCB上的一个缓冲芯片AMB(Advanced Memory Buffer,高级内存缓冲)将并行数据转换为串行数据流,并经由类似PCI Express的点对点高速串行总线将数据传输给处理器。

与普通的DIMM模块技术相比,FB-DIMM与内存控制器之间的数据与命令传输不再是传统设计的并行线路,而采用了类似于PCI-Express的串行 接口多路并联的设计,以串行的方式进行数据传输。在这种新型架构中,每个DIMM上的缓冲区是互相串联的,之间是点对点的连接方式,数据会在经过第一个缓 冲区后传向下一个缓冲区,这样,第一个缓冲区和内存控制器之间的连接阻抗就能始终保持稳定,从而有助于容量与频率的提升。

ECC是“Error Checking and Correcting”的简写,中文名称是“错误检查和纠正”。ECC是一种能够实现“错误检查和纠正”的技术,ECC内存就是应用了这种技术的内存,一 般多应用在服务器及图形工作站上,这将使整个电脑系统在工作时更趋于安全稳定。
ECC内存即纠错内存,是在数据位上额外的位存储一个用数据加密的代码。当数据被写入内存,相应的ECC代码与此同时也被保存下来。当重新读回刚才存储的 数据时,保存下来的ECC代码就会和读数据时产生的ECC代码做比较。如果两个代码不相同,他们则会被解码,以确定数据中的那一位是不正确的。然后这一错 误位会被抛弃,内存控制器则会释放出正确的数据。被纠正的数据很少会被放回内存。假如相同的错误数据再次被读出,则纠正过程再次被执行。重写数据会增加处 理过程的开销,这样则会导致系统性能的明显降低。如果是随机事件而非内存的缺点产生的错误,则这一内存地址的错误数据会被再次写入的其他数据所取代。

使用ECC校验的内存,会对系统的性能造成不小的影响,不过这种纠错对服务器等应用而言是十分重要的,带ECC校验的内存价格比普通内存要昂贵许多,一般用户不建议使用!
要了解ECC技术,就不能不提到Parity(奇偶校验)。在ECC技术出现之前,内存中应用最多的是另外一种技术,就是Parity(奇偶校验)。我们 知道,在数字电路中,最小的数据单位就是叫“比特(bit)”,也叫数据“位”,“比特”也是内存中的最小单位,它是通过“1”和“0”来表示数据高、低 电平信号的。在数字电路中8个连续的比特是一个字节(byte),在内存中不带“奇偶校验”的内存中的每个字节只有8位,若它的某一位存储出了错误,就会 使其中存储的相应数据发生改变而导致应用程序发生错误。而带有“奇偶校验”的内存在每一字节(8位)外又额外增加了一位用来进行错误检测。比如一个字节中 存储了某一数值(1、0、1、0、1、0、1、1),把这每一位相加起来(1+0+1+0+1+0+1+1=5)。若其结果是奇数,对于偶校验,校验位就 定义为1,反之则为0;对于奇校验,则相反。当CPU返回读取存储的数据时,它会再次相加前8位中存储的数据,计算结果是否与校验位相一致。当CPU发现 二者不同时就作出视图纠正这些错误,但Parity有个缺点,当内存查到某个数据位有错误时,却并不一定能确定在哪一个位,也就不一定能修正错误,所以带 有奇偶校验的内存的主要功能仅仅是“发现错误”,并能纠正部分简单的错误。

通过上面的分析我们知道Parity内存是通过在原来数据位的基础上增加一个数据位来检查当前8位数据的正确性,但随着数据位的增加Parity用来检验 的数据位也成倍增加,就是说当数据位为16位时它需要增加2位用于检查,当数据位为32位时则需增加4位,依此类推。特别是当数据量非常大时,数据出错的 几率也就越大,对于只能纠正简单错误的奇偶检验的方法就显得力不从心了,正是基于这样一种情况,一种新的内存技术应允而生了,这就是ECC(错误检查和纠 正),这种技术也是在原来的数据位上外加校验位来实现的。不同的是两者增加的方法不一样,这也就导致了两者的主要功能不太一样。它与Parity不同的是 如果数据位是8位,则需要增加5位来进行ECC错误检查和纠正,数据位每增加一倍,ECC只增加一位检验位,也就是说当数据位为16位时ECC位为6 位,32位时ECC位为7位,数据位为64位时ECC位为8位,依此类推,数据位每增加一倍,ECC位只增加一位。总之,在内存中ECC能够容许错误,并 可以将错误更正,使系统得以持续正常的操作,不致因错误而中断,且ECC具有自动更正的能力,可以将Parity无法检查出来的错误位查出并将错误修正。