CSI-IX:虚拟存储器-走进系统内核

前言

         如果别人问我,从开发人员的角度来讲,操作系统的哪部分内容是最为重要的,那么,我会毫不犹豫的说:虚拟存储器。可能从开始接触计算机系统到现在,我们对于虚拟存储器的概念和原理都还半知半解。甚至很多人,错误的以为理解虚拟存储器并不能给开发工作带来直接的好处,所以干脆放弃去了解虚拟存储器到底是什么样的,是如何贯穿整个系统层面。事实上,了解虚拟存储器如何工作是理解整个系统如何工作的关键一步,同时,这对于我们理解常见的与存储器有关的错误有一定的帮助。

         虚拟存储器是一个抽象的概念,并不是一个实体硬件,简单的来说,它是由磁盘和内存所构成的存储机制。虚拟存储器是硬件异常、硬件地址翻译、主存、磁盘文件和内核软件的完美交互,它为每个进程提供一个大的、一致的私有地址空间。通过一个清晰的机制,虚拟存储器提供三个重要的能力:

a)      它将主存看做是一个存储在磁盘上的地址空间的高速缓存,在主存中只保存活动区域,并根据需要在磁盘和主存之间来回传送数据,通过这种方式,它高效地使用了主存。

b)      它为每个进程提供了一致的地址空间,从而简化了存储器管理。

c)      它保护了每个进程的地址空间不被其他进程破坏。

1.物理和虚拟寻址

         计算机系统的主存被组织成一个由M个连续的字节大小的单元组成的数组。每字节都有一个唯一的物理地址(Physical Address ,PA)。早期的PC使用物理寻址,它的寻址方式如图9-1,而现代PC处理器使用的是一种称为虚拟寻址的寻址形式,如图9-2所示。

                       CSI-IX:虚拟存储器-走进系统内核_第1张图片CSI-IX:虚拟存储器-走进系统内核_第2张图片

         使用虚拟寻址时,CPU通过生成一个虚拟地址(VirtualAddress ,VA)来访问主存,这个虚拟地址在被送到存储器之前先转换成适当的物理地址,将一个虚拟地址转换为物理地址的任务叫做地址翻译。地址翻译需要CPU硬件和操作系统之间的紧密合作。CPU芯片上有个叫做存储器管理单元(MMU)的专用硬件,利用存放在主存中的查询表来动态翻译存储地址,该表的内容是由操作系统管理的。

2.虚拟存储器作为缓存的工具

         虚拟存储器(VM)被组织为一个由存放在磁盘上的N个连续的字节大小的单元组成的数组。每个字节都有一个唯一的虚拟地址,这个唯一的虚拟地址是作为数组的索引的。磁盘上数组的内容被缓存在主存中。和存储器层次结构中其他缓存一样,磁盘上的数据被分割成块,这些块作为磁盘和主存之间的传输单元。VM系统通过将虚拟存储器分割为称为虚拟页(virtual Page,VP)的大小固定的块来处理这个问题。每个页的大小为P=2p字节。类似的,物理存储器也被分割为物理页(Physical Page ,PP),大小也为P字节。

在任意时刻,虚拟页面的集合都分为三个不相交的子集:

(1)    未分配的:VM系统还未分配(或者创建)的页。未分配的块没有任何数据和它们相关联,因此也就不占用任何磁盘空间。

(2)    缓存的:当前缓存在物理存储器中的分配页

(3)    未缓存的:没有缓存在物理存储器中的已分配页。    

如图展示的是一个8个虚拟页的小虚拟存储器。虚拟页0和3还未分配,因此在磁盘上不存在。虚拟页1、4和6被缓存在物理存储器中。页2、5和7已经被分配了,但是当前并未缓存在主存中。

                                                           CSI-IX:虚拟存储器-走进系统内核_第3张图片

2.1DRAM缓存的组织结构

            DRAM缓存表示的是虚拟存储器系统的缓存,它在主存中缓存虚拟页。回想存储层次结构,我们知道SRAM要比DRAM要快大约10倍,而DRAM要比磁盘快100 000 多倍。因此DRAM缓存中的不命中要比SRAM缓存中的不命中要昂贵的多。

           因为大的不命中处罚和访问第一字节的开销,虚拟页往往很大,典型地是4KB~2MB。由于大的不命中处罚,DRAM缓存是全相联的,也就是说,任何虚拟页都可以放置在任何的物理页中。不命中时的替换策略也很重要,因为替换错了虚拟页的处罚也非常高。因此与硬件对SRAM缓存相比,操作系统对DRAM缓存使用了更复杂精密的替换算法。最后,因为对磁盘的访问时间很长,DRAM缓存总是使用写回,而不是直写。

2.2页表

           同任何缓存一样,虚拟存储器系统必须有某种方法来判定一个虚拟页是否存放在DRAM中的某个地方。如果是,系统还必须确定这个虚拟页存放在哪个物理页中。如果不命中,系统必须判断这个虚拟页存放在磁盘的哪个位置,在物理存储器中选择一个牺牲页,并将虚拟页从磁盘拷贝到DRAM中,替换这个牺牲页。

           这些功能是由许多软硬件联合提供的,包括操作系统软件、MMU(存储管理单元)中的地址翻译硬件和一个存放在物理存储器中叫做页表的数据结构,页表将虚拟页映射到物理页。每次翻译硬件将一个虚拟地址转换为物理地址时都会读取页表。操作系统维护页表的内容,以及在磁盘与DRAM之间来回传送页。

           一个页表的基本组织结构。页表就是一个页表条目(Page Table Entry,PTE)的数组。虚拟地址空间中的每个页在页表中一个固定偏移量处都有一个PTE。为了我们的目的,我们将假设每个PTE是由一个有效位和一个n位地址字段组成的。有效位表明了该虚拟页当前是否被缓存在DRAM中。如果设置了有效位,那么地址字段就表示DRAM中相应的物理页的起始位置,这个物理页中缓存了该虚拟页。如果没有设置有效位,那么一个空地址表示这个虚拟页还未分配。否则,这个地址就指向该虚拟页在磁盘上的起始位置。

                                                    CSI-IX:虚拟存储器-走进系统内核_第4张图片

上图展示的是一个8个虚拟页和4个物理页的系统的页表。四个虚拟页VP1、VP2、VP3和VP7当前被缓存在DRAM中。两个页VP0和VP5还未被分配,而剩下的页VP3、VP6已经被分配的,但是当前还未被缓存。

2.3页命中

在上图中,当CPU读取包含在VP2中的虚拟存储器的一个字时,由于VP2是被缓存在DRAM中的,地址翻译硬件将虚拟地址作为一个索引来定位PTE2,并从存储器中读取它。因为设置了有效位,那么地址翻译硬件就知道VP2是缓存在存储器中的了。所以它使用PTE中的物理存储器地址,构造出这个字的物理地址。

                                                    CSI-IX:虚拟存储器-走进系统内核_第5张图片

2.4 缺页

           DRAM缓存不命中称为缺页。下图展示的是缺页之前的页表状态。CPU引用VP3中的一个字时,VP3并未缓存在DRAM中。地址翻译硬件从存储其中读取PTE3,从有效位推断出VP3未被缓存,并且触发一个缺页异常。

           缺页异常调用内核中的缺页异常处理程序,该应许会选择一个牺牲页,在此例中就是存放在PP3中的VP4。如果VP4已经被修改了,那么内核就会将它拷贝回磁盘。接着,内核从磁盘拷贝VP3到存储器中的PP3,更新PTE3,随后返回。当异常处理程序返回时,它会重新启动导致缺页的指令,该指令会把导致缺页的虚拟地址重发送到地址翻译硬件。这时,VP3已经缓存在主存中了,那么页命中也能由地址翻译硬件正常处理了。

                                         CSI-IX:虚拟存储器-走进系统内核_第6张图片

                   CSI-IX:虚拟存储器-走进系统内核_第7张图片

2.5 分配页面

当操作系统分配一个新的虚拟存储器页时,例如,调用malloc的结果,系统会在磁盘上创建空间,并更新页表,使页表条目指向磁盘上这个新建的页面,从而分配虚拟页。

如下图我们创建的VP5.

                                              CSI-IX:虚拟存储器-走进系统内核_第8张图片

 

          可能,我们开始会怀疑虚拟存储器的效率,因为涉及到磁盘交互,不命中的处罚很大,我们会担心页面调度会破坏程序新能。实际上,虚拟存储器工作得相当好,这主要归功于我们的老朋友局部性(locality)。

         尽管整个运行过程中程序引用的不同页面的总数可能超出物理存储器总的大小,但是局部性原则保证了再任意时刻,程序将往往在一个较小的活动页面集合上工作,这个集合叫做工作集或者常驻集。在初始开销,也就是将工作集页面调度到存储器中之后,接下来的针对这个工作集的引用将导致命中,而不会产生额外的磁盘流量。

         只要我们的程序有好的时间局部性,虚拟存储器系统就能工作得相当好。但是,并不是所有的程序都能展现良好的时间局部性。如果工作集的大小超出了物理存储器的大小,那么程序将产生一种不幸的状态,叫做颠簸,这时页面将不断地换进换出。

3.虚拟存储器作为存储器管理的工具

         到目前为止,我们都假设有一个单独的页表,将一个虚拟地址空间映射到物理地址空间。实际上,操作系统为每个进程提供了一个独立的页表,因而也就是一个独立的虚拟地址空间。

例如,如下图,进程i的页表将VP1映射到PP2,VP2映射到PP7。相似的,进程j的页表将VP1映射到PP7,VP2映射到PP10,多个虚拟页面可以映射到同一个共享物理页面上。

                                            CSI-IX:虚拟存储器-走进系统内核_第9张图片

         按需调度页面和独立的空间结合,对系统中存储器的使用和造成了深远的影响。特别地,VM简化了和加载、代码和数据共享,以及应用程序的存储器分配。

(1)    简化链接。独立的地址空间允许每个进程的存储器映像使用相同的格式,而不管代码和数据实际存放在物理存储器的何处。如Linux系统中每个进程的文本节总是从虚拟地址0x8048000处开始,数据和.bss节紧跟其后,这样的一致性极大简化链接器的设计和实现,链接器生成的可执行文件总是独立于物理存储器中代码和数据的最终位置。

(2)    简化加载。虚拟存储器还很容易向存储器中加载可执行文件和共享对象文件。在ELF可执行文件中,.text和.data节是连续的。要把这些节加载到一个新创建的进程中,Linux加载器分配虚拟页的一个连续的片,从地址0x8048000处开始,把这些虚拟页标记为无效的,将页表条目指向文件目标中适当的位置。加载器从不实际拷贝任何数据从磁盘到存储器。在每个页初次被引用时,要么是CPU取指令时引用的,要么是一条正在执行的指令引用一个存储器位置时引用时,虚拟存储系统会按照需要自动调用数据页。

(3)    简化共享。独立地址空间为操作系统提供了一个管理用户进程和操作系统自身之间共享的一个机制。一般而言,每个进程都有自己私有的代码、数据、堆以及栈区域,是不和其他进程共享的。但是在一些情况下,比如每个进程都调用了相同的操作系统内核代码,操作系统通过将不同进程中适当的虚拟页面映射到相同的物理页面,从而安排多个多个进程共享这部分代码的一个拷贝。

(4)    简化存储器分配。虚拟存储器为用户进程提供一个简单的分配额外存储器的机制。

当一个运行在用户进程中的程序要求额外的堆空间时,操作系统分配一个适当数字(如k)个连续的虚拟存储器页面,并将它们映射到物理存储器中任意位置的k个任意物理页面。由于页表工作的方式,操作系统没必要分配k个连续的物理存储器页面。页面可以随机分散在物理存储器中。

4.虚拟存储器作为存储器保护的工具

         现代计算机系统都提供为操作系统提供了控制对存储器系统访问的手段。如不应该让应用进程修改它的只读文本段,不应该允许它读或者修改任何内核中的代码,也不应该允许它读写其他进程的私有存储器。在虚拟存储器中,我们通过PTE页表条目来对访问的页面进行控制。我们通过在PTE中添加访问标记,如SUP(表示运行在内核模式下才可访问页面),READ,WRITE等标记来控制,如果某些指令违反这些许可条件,就会触发一个一般保护故障,Unix异常报告为“段错误”。

                                     CSI-IX:虚拟存储器-走进系统内核_第10张图片

5.地址翻译

         地址翻译的目的就是将虚拟页面的虚拟地址映射到物理地址的过程,这个过程是由硬件和操作系统合作完成的。在了解整个之前,我们先了解下可能涉及的一些符号。

                                        CSI-IX:虚拟存储器-走进系统内核_第11张图片

最基本的翻译过程是由MMU(存储管理单元)和页表来完成的。CPU中的一个控制寄存器,页表基址寄存器(PTBR)指向当前页表。N位虚拟地址包含了两个部分:一个p位的虚拟页面偏移(VPO)和一个(N-p)位的虚拟页号(VPN).MMU利用VPN选择适当的PTE。然后取出页表条目中的物理页号(PPN)和虚拟地址的(VPO)        串联起来,就得到相应的物理地址。需要注意,物理页面和虚拟页面的大小相同,所以VPO也是物理页面的页面偏移。

                                    CSI-IX:虚拟存储器-走进系统内核_第12张图片

总得来说,当翻译的地址命中时,CPU硬件执行下面的步骤:

1)处理器生成一个虚拟地址,并把它传送给MMU。

2)MMU生成PTE地址,并从高速缓存/主存请求得到它

3)高速缓存/主存向MMU返回PTE

4)MMU构造物理地址。并把它传送给高速缓存/主存

5)高速缓存/主存返回所请求的数据字给处理器

  CSI-IX:虚拟存储器-走进系统内核_第13张图片

当地址不命中时,与上面不同的是,需要处理缺页时的情况:

1-3)同上

4)PTE有效位为0(无效),所以MMU触发了一次异常,传递CPU中的控制到操作系统内核中的缺页异常处理程序

5)缺页处理程序确定出物理存储器中的牺牲页,如果页面被修改过,则把它换出到磁盘

6)缺页处理程序调入新的页面,并更新存储器中的PTE

7)缺页处理程序返回到原来的进程,再次执行导致缺页的指令。CPU将引起缺页的虚拟地址重新发给MMU。接下来就会访问命中。

 CSI-IX:虚拟存储器-走进系统内核_第14张图片

 

结合高速缓存和虚拟存储器

   在CPU和主存之前的L1-cache上可能缓存了PTE,那么地址翻译就会发生在高速缓存超查找之前。

CSI-IX:虚拟存储器-走进系统内核_第15张图片

 

利用TLB加速地址翻译

            可以看到,每次CPU产生一个虚拟地址,MMU必须查阅一个PTE,以便将虚拟地址翻译为物理地址。在最糟糕的情况下,这又会要求从存储器取一次数据,代价是几十到几百个周期。如果PTE碰巧缓存在L1中,那么开销就降为1到2个时钟周期。因此许多系统在MMU中包含了一个关于PTE的小的缓存称为翻译后备缓存器(TLB)来试图消除大的开销。

          TLB是一个小的、虚拟寻址的缓存,其中每行都保存着一个由单个PTE组成的块。TLB通常有高度的相连性。用于组选择和行匹配的索引和标记字段是从虚拟地址中的虚拟页号提取出来的。如果TLB有T=2t个组,那么TLB索引(TLBI)是由VPN的t个最低位组成的,而TLB标记(TLBT)是由VPN的剩余的位组成的。

   CSI-IX:虚拟存储器-走进系统内核_第16张图片

当TBL命中时,所有的地址翻译步骤都是在芯片上的MMU中执行的,因此非常快。

1)      CPU产生一个虚拟地址

2,3) MMU从TLB中取出相应的PTE。

4) MMU将虚拟地址翻译成物理地址,并且将它发送到告诉缓存/主存

5) 高速缓存/主存将所请求的数据字返回给CPU

CSI-IX:虚拟存储器-走进系统内核_第17张图片

当TLB不命中时,MMU必须从L1告诉缓存取出相应的PTE。新取出的PTE存放在TLB中,可能会覆盖一个已经存在的条目。

CSI-IX:虚拟存储器-走进系统内核_第18张图片

6.多级页表

          假设我们有一个32位的地址空间(虚拟地址空间4G)、4KB的页面和一个4字节的PTE,那么即使应用程序只是用一小部分的虚拟地址空间,也需要一个4M的页表驻留在存储器中。这对于多任务的计算机来说将会是极大的浪费,所以现代系统采用层次结构的多级页表来压缩页表。

         假设有如下的虚拟地址空间:存储器的前2K个页面分配了代码和数据,接下来的6K个页面还未分配,再接下的1023个页面也未分配,接下来的1个页面分配给了用户栈。下图展示一个两级页表的层次结构。

                                                         CSI-IX:虚拟存储器-走进系统内核_第19张图片

这里一级页表的每个条目对应二级页表1024个条目,负责映射虚拟地址空间一个4M的片,每片都是由1024个连续的页面组成,可以映射4G的虚拟地址空间。如果使用4字节的PTE,每个一级和二级页表都是4KB字节,这刚好和一个页面的大小是一致的。

7.存储器映射

         Linux通过将一个虚拟存储器区域与一个磁盘上的对象关联起来,以初始化这个虚拟存储器区域的内容,这个过程称为存储器映射。虚拟存储器区域可以映射到两种类型的对象中的一种:

(1)    Unix文件系统中的普通文件:一个区域可以映射到一个普通磁盘文件的连续部分,例如一个可执行目标文件。文件区被分成页大小的片,每一片包含一个虚拟页面的初始内容。

(2)    匿名文件:一个区域也可以映射到一个匿名文件,匿名文件是由内核创建的,包含的全是二进制零。CPU第一引用这样一个区域内的虚拟页面时,内核就在物理存储器中找到一个合适的牺牲页面,如果该页面被修改过,就将这个页面换出来,用二进制零覆盖牺牲页面并更新页表,将这个页面标记为是驻留在存储器中的。注意,在磁盘和存储器之间并没有实际的数据传送。

无论在哪一种情况下,一旦一个虚拟页面被初始化了,它就在一个由内核维护的专门的交换文件(swap file)之间换来换去。交换文件也叫做交换空间或者交换区域,这和windows下的虚拟内存概念很类似,但注意不要和虚拟存储器的概念搞混,在任何时刻,交换空间都限制着当前运行着的进程能够分配的虚拟页面的总数。

7.1 再谈共享对象

在CSI-VIII一篇中,我们介绍了链接,知道了对于多个进程之间可能存在的共享对象(动态共享库),存储器映射给我们提供了一种清晰的机制,用来控制多个进程如何共享对象。

          一个对象可以被映射到虚拟存储器的一个区域,要么作为共享对象,要么作为私有对象。如果一个进程将一个共享对象映射到它的虚拟地址空间的一个区域内,那么进程对于这个区域的任何写操作,对于那些把这个共享对象映射到它们虚拟存储器的其他进程而言也是可见的,而且,这些变化也会反映在磁盘上的原始对象中。

          另一方面,对于一个映射到私有对象的区域做的改变,对于其他进程来说是不可见的,并且进程对这个区域所做的任何写操作都不会反映在磁盘上的对象中。一个映射到共享对象的虚拟存储器区域叫做共享区域,类似的,也有私有区域。

假设进程1和进程2同时共享同一个对象,它们之间的关系将如图所示:

CSI-IX:虚拟存储器-走进系统内核_第20张图片

           私有对象是使用一种叫做写时拷贝的巧妙技术被映射到虚拟存储器中的。一个私有对象开始生命周期的方式基本上于共享对象的一样,在物理存储器中只保存有私有对象的一份拷贝。如下图,两个进程将一个私有对象映射到它们虚拟存储器的不同区域,但是共享这个对象同一个物理拷贝。对于每个映射私有对象的进程,相应私有区域的页表条目都被标记为只读,并且区域结构被标记为私有的写时拷贝。只要没有进程试图写它自己的私有区域,它们就可以继续共享物理存储器中对象的一个单独拷贝。然而,只要有一个进程试图写私有区域内的某个页面,那么这个写操作就会触发一个保护故障。

          当故障处理程序注意得到保护异常是由于进程试图写私有的写时拷贝区域中的一个页面而引起的。它就会在物理存储器中创建这个页面的一个新拷贝,更新页表条目指向这个新拷贝,然后恢复这个页面的可写权限。随后,CPU重新执行写操作,现在新创建的写操作就可以正常执行了。

           关于私有对象的写时拷贝,我们可以想象一种场景,即对于同一个程序的多次执行,从虚拟存储器的来看,每个进程都有其对应的实例及地址空间,但是在进程未写数据到其私有地址空间时,虚拟存储器会让这多个进程共享同一个私有区域,而当进程试图写数据到其私有空间时,就需要拷贝一份私有区域,然后再对其进行写操作,可以看到,这种机制实际上是为了节省更多的存储器资源。

 CSI-IX:虚拟存储器-走进系统内核_第21张图片

 

 

好了,关于虚拟存储器的重要内容及概念就先介绍到这,不过为将这些所有的内容联系起来,我将再写一篇来对虚拟存储器的整个工作过程原理进行梳理,并纠正一些可能存在的疑惑。至于动态存储器的分配,也涉及到虚拟存储器的内容,我会专门在另一篇中介绍,不过前提是我们能够对上面的概念做到充分的了解,最后,我还会基于这几篇内容编写一个模拟的小型的虚拟存储系统,内容将涉及高速缓存、虚拟存储器、地址翻译、动态存储分配等等。

你可能感兴趣的:(深入理解计算机系统)