2017-2018-1 学号20155311 《信息安全系统设计基础》第11周学习总结
2017-2018-1 学号20155311 《信息安全系统设计基础》第11周学习总结
教材学习内容总结
第九章 虚拟存储器
- 为了更加有效地管理存储器并且少出错,现代系统提供了一种对主存的抽象概念,叫做虚拟存储器(VM)。虚拟存储器是硬件异常、硬件地址翻译、主存、磁盘文件和内核软件的完美交互,它为每个进程提供了一个大的、一致的和私有的地址空间。通过一个很清晰的机制,虚拟存储器提供了三个重要的能力:
1)它将主存看成是一个存储在磁盘上的地址空间同,主存中只保存活动区域,并根据需要在磁盘和主存之间来回传送数据,通过这种方式,它高效地使用了主存。
2)它为每个进程提供了一致的地址空间,从而简化了存储器管理。
3)它保护了每个进程的地址空间不被其他进程破坏。
9.1 物理和虚拟寻址
- 当CPU执行这条加载指令时,它会生成一个有效物理地址,通过存储器总线,把它传递给主存。主存取出从物理地址4处开始的4字节的字,并将它返回给CPU,CPU会将它存放在一个寄存器里。早期的PC使用物理寻址,而且诸如数字信号处理器、嵌入式微控制器以及Cray超级计算机这样的系统仍然继续使用这种寻址方式。
9.2 地址空间
地址空间是一个非负整数地址的有序集合。
一个地址空间的大小是由表示最大地址所需要的位数来描述的,例如一个包含N=2的n次方个地址的虚拟地址空间就叫做一个n位地址空间。现代系统典型地支持32位或者64位虚拟地址空间,一个系统还有一个物理地址空间,它与系统中物理存储器的M个字节相对应。
9.3 虚拟存储器作为缓存的工具
概念上而言,虚拟存储器被组织为一个由存放在磁盘上的N个连续的字节大小的单元组成的数组。每字节都有一个唯一的虚拟地址,这个唯一的虚拟地址是作为到数组的索引的。磁盘上数组的内容被缓存在主存中。和存储器层次结构中其他缓存一样,磁盘(较低层)上的数据被分割成块,这些块作为磁盘和主存(较高层)之间的传输单元。VM系统通过将虚拟存储器分割为称为虚拟页的大小固定的块来处理这个问题。每个虚拟页的大小为P=2的p次方字节。类似地,物理存储器被分割为物理页大小也为P字节(物理页也称为页帧。
9.3.1 dram缓存的组织结构
为了有助于清晰地理解存储 中不同的缓存概念,我们将使用术语SRAM缓存来表示位于CPU和主存之间的L1、L2和L3高速缓存,并且用术语DRAM缓存来表示虚拟存储器系统的缓存,它在主存中缓存虚拟页。
在存储层次结构中,DRAM缓存的位置对它的组织结构有很大的影响。回想一下SRAM比DRAM要快大约10倍,而DRAM要比磁盘快大约100000多倍。因此,DRAM缓存中的不命中比起SRAM缓存中的不命中要昂贵得多,因为DRAM缓存不命中要由磁盘来服务,而SRAM缓存不命中通常是由基于DRAM的主存来服务的。而且,从磁盘的一个扇区读取第一字节的时间开销要比从这个扇区中读连续的字节慢大约100000倍。归根到底,DRAM缓存的组织结构完全是由巨大的不命中开销驱动的。
因为大的不命中处罚和访问第一字节的开销,虚拟页往往很大,典型地是4KB~2MB。由于大的不命中处罚,DRAM缓存是全相联的,也就是说,任何虚拟页都可以放置在任何的物理页中。不命中时的替换策略也很重要,因为替换错了虚拟页的处罚也非常之高。因此,与硬件对SRAM缓存相比,操作系统对DRAM缓存使用了更复杂精密的替换算法。最后,因为对磁盘的访问时间很长,DRAM缓存总是使用写回,而不是直写。
9.3.2 页表
同任何缓存一样,虚拟存储器系统必须有某种方法来判定一个虚拟页是否存放在DRAM中的某个地方。如果是,系统还必须确定这个虚拟页存放在哪个物理页中。如果不命中,系统必须判断这个虚拟页存放在磁盘的哪个位置,在物理存储器中选择一个牺牲页,并将虚拟页从磁盘拷贝到DRAM中,替换这个牺牲页。
这些功能是由许多软硬件联合提供的,包括操作系统软件、MMU(存储器管理单元)中的地址翻译硬件和一个存放在物理存储器中叫做页表的数据结构,页表将虚拟页映射到物理页。每次地址翻译硬件将一个虚拟地址转换为物理地址时都会读取页表。操作系统负责维护页表的内容,以及在磁盘与DRAM之间来回传送页。
9.3.3 页命中
9.3.4 缺页
在虚拟存储器的习惯说法中,DRAM缓存不命中称为缺页。
缺页异常调用内核中的缺页异常处理程序,该程序会选择一个牺牲页,在此例中就是存放在PP3中的VP4。如果VP4已经被修改了,那么内核就会将它拷贝回磁盘。无论哪种情况,内核都会修改VP4的页表条目,反映出VP4不再缓存在主存中这一事实。
接下来,内核从磁盘拷贝VP3到存储器中的PP3,更新PTE3,随后返回。当异常处理程序返回时,它会重新启动导致缺页的指令,该指令会把导致缺页的虚拟地址重发送到地址翻译硬件。但是现在,VP3已经缓存在主存中了,那么页命中也能由地址翻译硬件正翻译硬件正常处理了。图4展示了在缺页之后我们的示例页表的状态:
9.3.5 分配页面
下图展示了当操作系统分配一个新的虚拟存储器页时对我们示例页表的影响,例如调用malloc的结果。
9.4 虚拟存储器作为存储器管理的工具
按需页面调度和独立的虚拟地址空间的结合,对系统中存储器的使用和管理造成了深远的影响。特别地,VM简化了链接和加载、代码和数据共享,以及应用程序的存储器分配。
简化链接
简化加载
简化共享
简化存储器分配
9.5 虚拟存储器作为存储器保护的工具
任何现代计算机系统必须为操作系统提供手段来控制对存储器系统的访问。不应该允许一个用户进程修改它的只读文本段。而且也不应该允许它读或修改任何内核中的代码和数据结构。不应该允许它读或者写其他进程的私有存储器,并且不允许它修致任何与其他进程共享的虚拟页面,除非所有的共享者都显式地允许它这么做(通过调用明确的进程间通信系统调用)。
就像我们所看到的,提供独立的地址空间使得分离不同进程的私有存储器变得容易。但是,地址翻译机制可以以一种自然的方式扩展到提供更好的访问控制。因为每次CPU生成一个地址时,地址翻译硬件都会读一个PTE,所以通过在PTE上添加一些额外的许可位来控制对一个虚拟页面内容的访问十分简单。下图展示了大致的思想。在这个示例中,每个PTE中已经添加了三个许可位。SUP位表示进程是否必须运行在内核超级用户)模式下才能访问该页。
9.6 地址翻译
所有符号如下:
- 当页面命中时,CPU硬件执行的步骤
第一步:处理器生成一个虚拟地址并把它传送给MMU
第二步:MMU生成PTE地址,并从高速缓存/主存请求得到它
第三步:高速缓存/主存向MMU返回PTE
第四步:MMU构造物理地址并把它传送给高速缓存/主存
第五步:高速缓存/主存返回所请求的数据字给处理器。
页面命中完全是由硬件来处理的,与之不同的是,处理缺页要求硬件和操作系统内核协作完
第一步到第三步:和图中的第一步到第三步相同;
第四步:PTE中的有效位是零,所以MMU触发了一次异常,传递CPU中的控制到操作系统内核中的缺页异常处理程序。
第五步:缺页处理程序确定出物理存储器中的牺牲页,如果这个页面已经被修改了,则把它换出到磁盘。
第六步:缺页处理程序页面调入新的页面,并更新存储器中的PM。
第七步:缺页处理程序返虚拟地址重新发送给MMU。因为虚拟页面现在缓回到原来的进程,再次执行导致缺页的指令。CPU将引起缺页的现在缓存在物理存储器中,所以就会命中,在MMU执行了图中的步骤之后,主存就会将所请求字返回给处理器。
9.8 存储器映射
Linux(以及其他一些形式的Unix)通过将一个虚拟存储器区域与一个磁盘上的对象(object)关联起来,以初始化这个虚拟存储器区域的内容,这个过程称为存储器映射(memory mapping)。虚拟存储器区域可以映射到两种类型的对象的一种:
Unix文件上的普通文件:一个区域可以映射到一个普通磁盘文件的连续部分,例如一个可执行目标文件。文件区(section)被分成页大小的片,每一片包含一个虚拟页面的初始化内容。因为按需进行页面高度,所以这些虚拟页面没有实际进行物理存储器,直到CPU第一次引用到页面(即发射一个虚拟地址,落在地址空间这个页面的范围之内)。如果区域文件区要大,那么就用零来填充这个区域的余下部分。
匿名文件:一个区域也可以映射到一个匿名文件,匿名文件是由内核创建的,包含的全是二进制零。CPU第一次引用这样一个区域内的虚拟页面时,内核就在物理存储器中找到一个合适的牺牲页面,如果该页面被修改过,就将这个页面换出来,用二进制零覆盖牺牲页面并更新页表,将这个页面标记为是驻留在存储器中的。注意在磁盘和存储器之间没有实际的数据传送。因为这个原因,映射到匿名文件的区域中的页面有时也叫做请求二进制零的页。
9.8.1 再看共享对象
存储器映射的概念来源于一个聪明的发现:如果虚拟存储器系统可以集成到传统的文件系统中,那么就能提供一种简单而高效的把程序和数据加载到存储器中的方法。
正如我们已经看到的进程这一抽象能够为每个进程提供自己私有的虚拟地址空间,可以免受其他进程的错误读写。不过,许多进程有同样的只读文本区域例如,每个运行Unix外壳程序tcsh的进程都有相同的文本区域。而且,许多程序需要访问只读运行时库代码的相同拷贝。例如,每个C程序都需要来自标准c库的诸如printf这样的函数。那么,如果每个进程都在物理存储器中保持这些常用代码的复制拷贝,那就是极端的浪费了。幸运的是,存储器映射给我们提供了一种清晰的机制,用来控制多个进程如何共享对象。
一个对象可以被映射到虚拟存储的一个区域,要么作为共享对象,要么作为私有对象。如果一个进程将一个共享对象映射到它的虚拟地址空间的一个区域内,那么这个进程对这个区域的任何写操作,对于那些也把这个共享对象映射到它们虚拟存储器的其他进程而言也是可见的。而且,这此变化也会反映在磁盘上的原始对象中。(IPC的一种方式)
另一方面,对一个映射到私有对象的区域做的改变,对于其他进程来说是不可见的,并且进程对这个区域所做的任何写操作都不会反映在磁盘上的对象中。一个映射到共享对象的虚拟存储器区域叫做共享区域。类似地,也有私有区域。
共享对象的关键点在于即使对象被映射到了多个共享区域,物理存储器也只需要存放共享对象的一个拷贝。
一个共享对象(注意,物理页面不一定是连续的。)
私有对象是使用一种叫做写时拷贝(copy-on-write)的巧妙技术被映射到虚拟存储器中的。对于每个映射私有对象的进程,相应私有区域的页表条目都被标记为只读,并且区域结构被标记为私有的写时拷贝。
9.8.2 再看fork函数
当fork函数被当前进程调用时,内核为新进程创建各种数据结构,并分配给它一个唯一的PID。为了给这个新进程创建虚拟存储器,它创建了当前进程的mm_struct、区域结构和页表的原样拷贝。它将两个进程中的每个页面都为标记只读,并将两个进程中的每个区域结构都标记为私有的写时拷贝。
当fork在新进程中返回时,新进程现在的虚拟存储器刚好和调用fork时存在的虚拟存储器相同。当这两个进程中的任一个后来进行写操作时,写时拷贝机制就会创建新页面,因此,也就为每个进程保持了私有地址空间的抽象概念。
9.8.4 使用mmap函数的用户级存储器映射
mmap函数要求内核创建一个新的虚拟存储器区域是,最好是从地址start开始的一个区域,并将文件描述符fd指定的对象的一个连续的片(chunk)映射到这个新区域。连续的对象片大小为length字节,从距文件开始处偏移量为offset字节的地方开始。start地址仅仅是一个暗示,通常被定义为NULL。
9.9 动态存储器分配
动态存储器分配器维护着一个进程的虚拟存储器区域,称为堆。
存储器分配器有两种:显示分配器,隐式分配器。
9.9.1 malloc和free函数
显示分配器包括:malloc和free,这两个函数就是两个分配器,只是职能不同。
隐式分配器,也叫垃圾收集器,看来只有垃圾收集的功能。没有相应的分配功能。
malloc返回一个指针,指向大小为至少size字节的存储块。遇到问题的话,就返回NULL。
sbrk扩展和收缩堆。
freee参数必须为malloc,calloc,realloc返回的指针。其没有返回值。
9.9.2 为什么要使用动态存储器分配
这里的存储器分配器,是对堆内部空间的分配和释放。和堆大小没有关系。
直到程序实际运行时,程序才知道某些数据结构的大小。
9.9.3 分配器的要求和目标
显式分配器必须在一些相当严格的约束条件下工作:
(1)处理任意请求序列
(2)立即响应请求
(3)只使用堆
(4)对齐块(对齐要求)——在大多数系统中,这意味着分配器返回的块是8字节边界对齐的。
不修改已分配的块——像压缩已分配块这样的技术是不允许被使用的(这里是指已分配的块,而不是堆,sbrk函数可以扩展和收缩堆)
分配器有两个目标:
(1)最大化的吞吐率——其实就是以最快的方式执行诸如malloc和free这样的函数;
(2)最大化存储器利用率——通常用峰值利用率来表示存储器利用率,这个值是有效载荷/堆大小。
这两个目标其实是相互抑制的。
9.9.4 碎片
堆的碎片:内部碎片和外部碎片。
内部碎片——对齐约束条件和具体实现中有最小分配块的要求。
外部碎片——有很多空间块,但满足不了一个分配请求。
9.9.6 隐式空闲链表
隐式空闲链表——空闲块通过头部的大小字段隐含地连接在一起。
9.9.7 放置已分配的块
当一个应用请求一个k字节的块时,分配器搜索空闲链表,查找一个足够大可以放置所请求块的空闲块,执行这种搜索的方式由放置策略确定。
首次分配:从头开始搜索空闲链表
下一次适配:从上一次查询结束的地方开始搜索
最佳适配:检查每个空闲块,选择适合所请求大小的最小空闲块
9.10 垃圾收集578
垃圾收集:采用何种方式来辨别垃圾呐,系统采用的是图的方式进行,将存储器视为一张有向图,每个节点是根节点或堆节点,节点表示的一个分配块,如果在一个块中指向另一个块的某个位置,就连接,当存在一条从任意根节点出发并到达堆节点的有向路径时,视为可达,那些不可达的节点就是垃圾节点。再进行回收时,有两个阶段,标记和回收,标记垃圾节点,清除。
9.10.1 垃圾收集器的基本知识
垃圾收集器将存储器视为一张有向可达图,其形式如图所示。该图的节点被分成一组根节点和一组堆节点每个堆节点对应于堆中的一个已分配块。有向边p一q意味着块p中的某个位置指向块q中的某个位置。根节点对应于这样一种不在堆中的位置,它们中包含指向堆中的指针。这些位置可以是寄存器、栈里的变量,或者是虚拟存储器中读写数据区域内的全局变量。
9.10.2 mark&sweep垃圾收集器
mark&sweep垃圾收集器由标记和清除阶段组成
9.11 c程序中常见的与存储器有关的错误
9.11.1 间接引用坏指针
经典的scanf错误
在这种情况下,scanf将把val的内容解释为一个地址,并试图将一个字写到这个位置。在最好的情况下,程序立即以异常中止。在最糟糕的情况下,val的内容对应于虚拟存储器的某合法的读/写区域,于是我们就覆盖了存储器,这通常会在相当长的段时间以后造成灾难性的、令人困惑的后果。
9.11.2 读未初始化的存储器
一个常见的错误