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)、标志位等信息。

Linux虚拟内存技术初窥

图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所示。
Linux虚拟内存技术初窥

图2 Linux中物理内存的表示

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

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

图3 页表查找流程示意图

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

Linux虚拟内存技术初窥

首发于个人微信公众号TensorBoy。微信扫描上方二维码或者微信搜索TensorBoy并关注,及时获取更多最新文章!
C++ | Python | Linux | 原理 | 源码,有一起玩耍的么?