Linux虚拟内存技术初窥

1. 为什么要用虚拟内存

总所周知,从做系统的主要作用是对计算机资源的管理以及程序调度,者其中就包括对内存的管理。现在很多的系统都是用虚拟内存技术来对内存的管理,所谓虚拟内存,就是一种让应用程序觉得它拥有一个很大的内存可以使用,例如对于一个64位的操作系统,操作系统会给应用程序制造一种它可以有2^64Bytes那么大的内存可以使用的假象,虽然实际情况可能是这个电脑上只有4G的内存。
对于一个多任务操作系统,如果不适用虚拟内存,计算机所拥有的那点内存显然不够分,虽然我们可以选择增加物理内存的方式让程序拥有更多的内存可以使用,但是内存的价格毕竟在哪里。
另外,即便你并不在乎价格,多大的内存都能随便买得起,但也并不意味这你装了多大内存就有多少内存可以使用,不同CPU的架构限制了它对内存地址的访问能力。例如一个64位架构的CPU可能它只实现了44位,那么意味着它能访问的物理内存只有2^44Bytes这么大。
除了上面所说的资源抽象的作用,虚拟内存还具有以下两个优点:

  1. 信息隔离:虚拟内存使得每个进程都有自己的一个地址空间,每个进程之间不能相互访问对方的地址空间,这就增加了安全性;
  2. 错误隔离:每个进程内部的错误只会影响到该进程,而不会危及别的进程。

2. 术语

2.1. 地址空间

地址空间简单来说就是一个进程能够访问的所有虚拟地址,例如在64位系统中一个程序拥有的地址空间为0~0xFFFFFFFFFFFFFFFF

2.2 虚拟地址

虚拟地址(Virtual Address,VA),就是地址空间中的任意一个地址;

2.3. 物理地址

物理地址(Physical Address,PA),CPU访问内存所使用的真实地址;

2.4 页

虚拟内存中一段连续的内存区域,整个虚拟内存被分成多个大小相等的页,不同架构的CPU定义的页的大小可能不同,例如4KB、8KB等。

2.5 页帧

物理内存中一段连续的区域,整个物理内存被分成多个大小相等的页帧,不同架构的CPU定义的页帧的大小可能不同,例如4KB、8KB等。一般情况下页和页帧的大小是一样的,但在某些架构的CPU上页和页帧的大小可能不一样。

2.4 页表

保存虚拟地址与物理地址映射关系的一个表,Linux中页表分为多级结构,每一级都占据整个页帧。页帧只存在与物理内存上并且不能交换,因此页帧的大小的大小其实也限制了地址空间的大小。例如一个64位的操作系统,虽然理论上它的地址空间应该是0-2^64 Byte,但是在页帧大小为4KB,拥有三级页表的情况下,地址空间只有0-2^39那么大。这是怎么算出来的呢?
在三级页表中,这三级分别称为PGD(Page Global Directory)、PMD(Page Middle Directory)和PTE(Page Table Entry),他们的关系如图1所示,PGD指向PMD,PMD指向PTE,PTE最终指向页帧。PGD和PMD中存储的下一级的物理地址,而PTE中存储的内容包括PFN(Page Frame Number)、标志位等信息。

图1 页表结构示意图

由于页帧的大小已经是4KB,64位系统中也表中的每一项大小为64位也就是8Byte,那么一个页帧能存储的表项的数目为512条。由于只有一个页帧存储PGD,那么只需要使用9位就能够索引到该页帧中的所有表项,因此虚拟地址中使用9位表示PGD中某一项。同理,PMD与PTE也只用9位,因此PGD、PMD与PTE占了虚拟地址中的27位。当最终通过PTE找到存储数据的真正页帧,由于该页帧中保存的是实际的数据,通常以Byte为最小地址索引单元,因此4KB就能分成1024*4这么多小块,需要12位才能索引完,合起来就需要39位。为了能使用更大的地址空间,就需要通过采取更大页帧或者增加页表级数的方法,例如如果使用8KB的页帧,则地址空间可以扩展到43位,而如果再扩展一级页表,则可以有48位地址空间可以使用。

2. 怎么表示物理内存

物理内存分为NUMA(Non-Uniform Memory Access)和UMA(Uniform Memory Access)两种类型,虽然我们通常认为CPU访问内存的各个区域的代价是一样的,但是有时候并不是这样。在Linux中,将访问代价一样的内存归为一个Node,UMA只有一个Node,NUMA有多个Node,一般的PC都是UMA类型的。
由于架构的限制,CPU并不能平等使用所有的内存,例如某些DMA处理器只能访问物理内存开始的16M内存。因此,需要将物理内存分为不同的区(Zone),通常可分为以下三个不同的区:ZONE_DMA、ZONE_NORMAL以及ZONE_HIGHMEM,每一个区都表示一块连续的内存。
而每个区可以分为一个个页帧,因此,在Linux中,物理内存的表示如下图2所示。


图2 Linux中物理内存的表示

3. 怎么通过虚拟内存地址找到物理内存地址

物理地址和虚拟地址的映射是通过页表来实现的。那么我们怎么样才能通过页表将虚拟地址转化为物理地址呢?如图3所示,下面简要介绍下页表是怎么工作的。



在Linux中,每个进程有一个指向PGD的指针,通过该指针我们能找到PGD表。找到该PGD后,通过所给的地址的指定9位(如图3中的page index),我们可以找到该虚拟地址所属于的PGD项,该相中保存的是指向PMD的地址,这样,我们就来到该虚拟地址所属于的PMD中。与之前一样,我们在PMD中通过pmd index找到了对应的PTE的地址,我们通过pte index找到了该虚拟地址所映射的页帧。最后,通过offset我们就能确定该虚拟地址所指向的是所找到页帧的哪一字节。

4. 如何实现

1949年10月1日,新中国成立的开国大典,很少人知道,当时天上飞过的不是26架战机,而是17架。那时,我国总共有100多架战机,其中很多已经破烂不堪,飞都很难飞起来,而且还是万国牌。
如何让场面看起来更壮观些?
沉思良久后,周总理提出了一个建议:这17架飞机中有9架速度比较快,阅兵时让它们飞前面,绕一圈后迅速回到队伍最后面,这样就能营造出一种有26架飞机参加阅兵式的感觉。

操作系统对内存的操作和这个飞机飞两遍的操作非常相似。让多个虚拟在 不同时间段与同一个页帧做映射,就能然人柑橘能使用的内存比实际的大。
只是仅仅让多个虚拟页分别于页帧分时映射还不行,还需要有一个地方用于保存他们各自的数据,这个地方就是硬盘。比如某个市场只有一个摊位,张三、李四和王五三班倒的用这个摊位卖东西。他们三人彼此并不知道对方的存在,显然,除了这个摊位,他们三人必须还各种需要一个仓库用于在他们不卖货的时间段存放他们的货物,否则如果他们都货物都直接丢在摊位,那么张三很可能就把李四的东西给卖了。

如图4所示,假设操作系统将两个虚拟页映射到了同一个页帧,这两个虚拟页可以属于同一个地址空间也可以属于不同的地址空间,一般来说应该属于不同的地址空间,假设它们分别称为P1和P2,那么从感觉上这两个虚拟页都是独立的一块内存,虽然实际上他们映射的是同一个页帧,假设这个页帧成为F,但同一时间内只能有一个虚拟页和页帧绑定。此外,P1和P2在磁盘上各自有着一款与它们自身大小相等的空间作为备份,假设分别是B1与B2。某一时刻,F存储的是P1的内容,现在程序访问到了P2,由于P2的目前还没和F绑定,就会触发缺页中断(page fault)。操作系统先挂起当前程序,然后中断处理程序(page fault handler)就会将目前物理内存帧内的内容存储到磁盘B1,而把B2的内容读到F,然后将P2绑定到F,最后恢复被挂起的程序的执行。通过这一换页机制,程序就拿到了P2上的存储的内容。当又需要P1的内容的时候,同样的方法得到,而应用程序并不知道这些,在应用程序看来,它需要的数据一值都在内存中。

图4 换页机制

5. 总结

本文只是对虚拟内存做了简单的介绍,内存是个有意思的东西,程序的一切操作都是围绕它展开,理解了底层对内存的操作,对于理解上层程序对内存所做的各种操作也很有帮助。

欢1迎2关3注4个5人6公7众8号:TensorBoy

你可能感兴趣的:(Linux虚拟内存技术初窥)