例行前言:
本篇不是学习课程时的笔记,是重看这本书时的简记。对于学习本课程的同学,未涉及的内容不代表考试不涉及(mmap,动态存储器分配,linux虚拟存储器)。本章的大部分内容已经在OS中学习过了,但本章内容介绍虚拟存储器的视角不同于OSTEP中的视角,在学习本节后,应该对虚拟存储器有更全面的了解,包括虚拟存储器的作用,映射机制,地址翻译过程等。地址翻译一节没有完整的包含TLB的翻译过程图,在P545的图中加上TLB就更加完整了,可以自行绘制。
计算机系统-虚拟存储器
虚拟存储器是系统提供的一种抽象,为每个进程提供了一致的地址空间,简化了存储器管理,保护了每个进程的地址空间不被其他进程破坏。虚拟存储器是计算机系统最重要的概念之一,本章主要描述虚拟存储器的工作机制,以及应用程序如何使用和管理虚拟存储。
1.地址空间与寻址
寻址
早期的计算机没有虚拟存储器抽象,采用物理寻址方式直接进行寻址。现代处理器都采用虚拟寻址。所有处理器直接处理的都是虚拟地址,访问存储器时,虚拟地址会通过硬件部件**MMU(存储器管理单元)**翻译为物理地址。翻译所需要的一些表和信息存放在主存中,由操作系统管理。
地址空间
虚拟存储器中的地址都在虚拟地址空间当中,与物理地址空间相对应。每个数据对象都在虚拟地址空间和物理地址空间各有一个地址。
2.虚拟存储器
本节主要通过三个角度来理解虚拟存储器的作用和工作机制,这些角度和OSTEP中看待虚拟存储器的方式有所不同,OSTEP中重点介绍的是虚拟存储器到物理存储器的映射方式。
- 虚拟存储器作为缓存的工具
- 虚拟存储器作为存储器管理的工具
- 虚拟存储器作为存储器保护的工具
2.1虚拟存储器作为缓存
虚拟存储器是物理存储器的抽象,而物理存储器(主存)被视为低存储层次的硬盘的缓存。在本节,视已分配的虚拟页在物理磁盘上存在(以下三种情况之一),缓存在物理存储器中。从这个角度来说,在本小节提到的虚拟页,都可以直接视为是磁盘上的数据,而本小节的内容,实际上是讲主存是如何作为磁盘的缓存的。
虚拟存储器被划分为固定大小的页,作为磁盘和主存等层次之间的传输单元。与之对应,物理存储器也被划分为页。虚拟存储器中的页有三种情况:
- 未分配的页:在虚拟存储器中不存在,没有分配的页
- 缓存的:缓存在物理存储器中的已分配页
- 未缓存的:没有还存在物理存储器中的,存在于虚拟存储器中的已分配的页
DRAM组织结构
用SRAM表示L1、L2等高速缓存,DRAM表示主存。
主存DRAM的不命中由磁盘来处理,而磁盘IO的开销非常大,因此虚拟页通常比较大,通常4KB-2MB。虚拟页以全相联的方式缓存在主存中。为了尽量避免不命中处罚,替换策略也设计得更加精密。且因为对磁盘的访问时间长,DRAM总是采取写回,而不是直写。
页表
虚拟存储器系统通过页表来判断虚拟页是否存在于主存上,如果存在,在哪个物理页上,如果不在,则要到磁盘中找到该页,放入主存。
这些工作是软硬件共同完成的,MMU负责地址的翻译,而OS维护页表等信息,从磁盘读入页等工作。当访存发生缺页和违反存储器保护等异常时,也是由OS的异常处理程序来进行处理。
页表是页表条目PTE构成的数组。虚拟地址空间的每个页有一个PTE(多级页表可以减少页表项的浪费,不为未分配的页分配页表项)。每个PTE都有多个位,存储了许多信息。在这里,重点关注有效位。有效位为有效则PTE中含有虚拟页的物理页号,否则要根据PTE中的地址部分判断页是否分配,如果地址为空,则该页未分配,地址不为空,则地址为页在磁盘上的地址,可以从磁盘中读入该页。
缺页
虚拟页不在主存中,即DRAM缓存不命中现象称为缺页。MMU进行地址翻译时,从PTE的有效位得出该页没有缓存到DRAM中,触发缺页异常。
OS的缺页异常处理程序会从磁盘中读入该页到DRAM,可能还需要替换掉其他页,然后修改PTE并返回。异常处理程序返回后将重新执行缺页指令,这次就可以页命中了。
在磁盘和主存之间传送页的活动叫做交换或者页面调度。现代处理器都采用按序调度的方式,只有当不命中发生时,才从磁盘换入页面到主存中。
2.2虚拟存储器作为存储器管理的工具
虚拟存储器的存在使对存储器管理更加便捷,主要有以下四点的体现:
- 简化链接:独立的地址空间允许每个进程的存储器映像使用相同的基本格式,例如32位Linux系统上.text节总是从0x8048000处开始。这样的一致性简化了链接器的设计和实现,链接器只需要按照规定的映像格式为每个整合的节分配虚拟地址。
- 简化加载:加载可执行文件和共享对象文件也因虚拟存储器而简化。所有的节被分配的地址,只需要将相应页的PTE标记为无效,并指向磁盘中文件的具体位置,当页被初次引用时,就会自动调入该页。
- 简化共享:虚拟存储器使不同进程可以将虚拟页映射到相同的物理页面,从而实现进程之间的代码和数据的共享,例如共享库,父子进程的COW。
- 简化存储器分配:OS可以为进程分配连续的且足够大的虚拟存储器页面,映射到物理存储器的任意页。
2.3虚拟存储器作为存储器保护的工具
虚拟存储器还在映射到物理页的过程中实现了存储器保护。通过PTE的一些额外的位来控制对虚拟页的访问权限,包括读写权限位,访问特权位(SUP)等。如果指令违反了访问权限,就会触发故障,这种异常常被报告为段错误(segmentation fault)。
3.地址翻译
本节说明地址翻译和访问存储的具体过程。
首先,我们要明确一下需要考虑的部件,在实际的计算系统中,关于存储器访问的部件有:Cache,MMU,DRAM(主存),磁盘,TLB。
现代处理器中,通常访问Cache是采用物理地址,这样高速缓存不用处理存储器保护问题,并且也可以实现虚拟页面映射到同一个物理页面。
因此,虚拟地址总是先通过MMU翻译为物理地址,然后到Cache/主存中读取数据。当翻译地址检查到缺页异常或者其他违反访问权限异常时,异常处理程序介入处理。地址翻译所需要的PTE也需要访问Cache和主存读取,为了加速地址翻译,计算机采用TLB来缓存PTE。TLB和Cache一样是一个缓存,因此也有Cache中的组织方式,查找等问题。
综上,地址翻译的过程如下:
- 处理器产生一个对虚拟地址的访存指令,将虚拟地址VA交给MMU
- MMU将VA传给TLB,读取PTE。如果TLB中没有VA相应的PTE,则要到Cache、主存中读取PTE,并存入TLB。
- MMU根据PTE,将虚拟地址翻译成物理地址,将物理地址发送到Cache。如果发生缓存未命中,会访问主存。
- 高速缓存/主存将数据返回给CPU。
上述翻译过程没有考虑缺页异常,如果MMU通过PTE判断发生了缺页,由OS处理,从磁盘调入页到主存中,重新执行该指令。
4.存储器映射
Linux会将虚拟存储器区域与磁盘上的对象关联起来,初始化这个虚拟存储器区域的内容。虚拟存储器可以映射到两种类型的对象:
- Unix中的普通文件:区域可以映射到文件的连续部分,如果区域比文件大,用0来填充余下部分
- 匿名文件:内核创建的全0文件。当第一次引用区域的一个虚拟页面时,会在主存中找到一个页用全0覆盖,更新PTE,不需要从磁盘读取页。映射到匿名文件的区域中的页也叫做请求二进制零的页面
共享对象
一个数据对象可以被映射到虚拟存储器的一个区域,既可以作为私有对象,也可以作为共享对象。映射到共享对象的虚拟存储器区域叫做共享区域,映射到私有对象的虚拟存储器区域为私有区域。
私有对象的生命周期的开始和共享对象是一样的。在存储器中只有一份该对象,多个进程共享这个对象,映射到相同的物理存储器。当进程需要写自己的私有区域的数据时,就会触发一个故障,处理程序会拷贝一份数据放在物理存储器其他的地方,作为该进程自己的一份数据。这个过程就是COW(COPY ON WRITE),写时拷贝机制。
fork函数
在理解上述过程后,就能明白fork函数创建新进程的机制了。fork会为新进程创建虚拟存储器,以及页表的拷贝,父进程和子进程的页面都标记为只读,并将区域标记为私有的写时拷贝,当两个进程中的任一个进行写操作时,就创建新的物理页面,复制一份数据,因此父进程和子进程实现了独立的私有地址空间。
execve函数
execve函数加载和执行一个新的程序,替代当前程序。这个过程需要以下几个步骤:
- 删除已存在的区域:删除当前进程的已存在的区域
- 映射私有区域:为新程序的代码、数据等创建新的私有区域结构,并建立映射。代码和数据映射到文件中,bss区域映射到匿名文件
- 映射共享区域:如果程序与共享对象链接,则进行动态链接,将共享对象映射到虚拟地址空间中的共享区域
- 设置PC,执行程序