首页 > 技术文章 > 信息安全系统设计基础第十四周总结

20135305yg 2015-12-08 23:39 原文

第9章 虚拟存储器

9.1 物理和虚拟

(1)一个使用物理寻址的系统:当CPU执行这条加载指令时,它会产生一个有效的物理地址,通过存储器总线,把它传递给主存。主存取出从物理地址4处开始的4字节的字,并将它返回给CPU,CPU会将它存放在一个寄存器里。 

(2) 一个使用虚拟寻址的系统:使用虚拟寻址时,CPU通过生成一个虚拟地址来访问主存,这个虚拟地址在被送到存储器之前先转换成适当的物理地址。

(3)地址翻译:将一个虚拟地址转换为物理地址的任务。

  存储器管理单元(MMU:利用存放在主存中的查询表来动态翻译地址。

9.2 地址空间

(1)地址空间:一个非负整数地址的有序集合:

      {0, 1, 2, 3,...}

线性地址空间:地址空间的整数是连续的。

虚拟地址空间:在一个带虚拟存储器的系统中,CPU从一个N = 2n              {0, 1, 2, 3, …, N-1}

物理地址空间:{0, 1, 2, 3, …, M-1}

(2)虚拟存储器的基本思想:允许每个数据对象有多个独立地址,其中每个地址都选自一个不同的地址空间。

(3)主存中的每个字节都有一个选自虚拟地址空间虚拟地址和一个选自物理地址空间的物理地址。

9.3 虚拟存储器作为缓存的工具

(1)虚拟存储器(VM)被组织为一个有存放在磁盘上的N个连续的字节大小的单元组成的数组。每个虚拟页的大小为P = 2p(2的p次方)。物理存储器杯分割为物理页(PP),大小也为P字节(物理页也称为页帧)

(2)任意时刻,虚拟页面的集合都分成3个不相交的子集

未分配的:VM系统还有未分配(或创建)的页;不占任何磁盘空间

缓存的:当前缓存在物理存储器中的已分配页。

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

(3)DRAM缓存的组织结构

SRAM缓存:表示位于CPU和主存之间的L1、L2和L3高速缓存。

DRAM缓存:表示虚拟存储器系统的缓存,它在主存中缓存虚拟页。

(4)页表

页表就是一个页表条目(PTE)的数组。每个PTE由一个有效位和一个n位地址字段组成的。有效位表明了该虚拟页是否缓存在DRAM中。(DRAM是全相连的,任意物理页都可以包含任意虚拟页)。

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

(6)缺页:DRAM的缓存不命中。

交换或页面调度:在磁盘和存储器之间传送页的活动。

按需页面调度:页从磁盘换入(或者页面调入)DRAM和从DRAM换出(或者页面调出)磁盘。一直等待,直到最后时刻,也就是有不命中发生时,才换入页面的这种策略。

VM缺页(之前):对VP3中的字的引用不命中,从而触发了缺页

VM缺页(之后):缺页处理程序选择VP4作为牺牲页,并从磁盘上用VP3的拷贝取代它。在缺页处理程序重新启动导致缺页的指令之后,该指令将从存储器中正常地读取字,而不会再产生异常。

(7)颠箥:工作集的大小超出物理存储器的大小。

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

(1)VM如何为进程提供独立的地址空间。操作系统为系统中的每个进程都维护一个独立的页表:(多虚拟页面可以映射到同一个共享物理页面上)

在这个示例中,进程i的页表将VP1映射到PP2,VP2映射到PP7.相似的,进程j的页表将VP1映射到PP7,VP2映射到PP 10.

(2)VM简化了链接和加载、代码和数据共享,以及应用程序的存储器分配。

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

SUP位表示进程是否必须运行在内核(超级用户)模式下才能访问该页。运行在内核模式中的进程可以访问任何页面,但是运行在用户模式的进程只允许访问那些SUP为0的页面。

如上图:进程i运行在用户模式下,那么它有读VP 0和VP 1 的权限。然而,不允许它访问VP 2.

9.6 地址翻译

(1)页面命中: CPU硬件执行的步骤

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

第二步:MMU生成PTE地址,并从高速缓存/主存请求得到它。

第三步:高速缓存/主存向MMU返回PTE.

第四步;MMU构造物理地址,并把它传送给高速缓存/主存。

第五步:高速缓存/主存返回所请求的数据字给处理器。

(2)物理缺页:要求硬件和操作系统内核完成:

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

第二步:MMU生成PTE地址,并从高速缓存/主存请求得到它。

第三步:高速缓存/主存向MMU返回PTE.

第四步:PTE中的有效位是0,所以MMU触发了一次异常,传递CPU中的控制到操作系统内核中的缺页异常处理程序。

第五步:缺页处理程序确定出物理存储器中的牺牲页,如果这个页已经被修改了,则把它换出到磁盘。

第六步:缺页处理程序页面调入新的页面,并更新存储器中的PTE.

第七步:缺页处理程序返回到原来的进程,再次执行导致缺页的指令。CPU将引起缺页的虚拟地址重新发送给MMU.

(3)TLB(翻译后备缓冲器):有T = 2t(2的t次方),那么TLB索引(TLBI)是由VPN的t个最低位组成的,而TLB标记(TLBT)是由VPN中剩余的位组成的。

(4)TLB命中

第一步:CPU产生一个虚拟地址

第二步和第三步:MMU从TLB中取出相应的PTE.

第四步:MMU将这个虚拟地址翻译成一个物理地址,并且将它发送到高速缓存/主存。

第五步:高速缓存/主存将所请求的数据字返回给CPU.

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

(5)多级页表

一级页表中的每个PTE负责映射虚拟地址中的一个4MB的片,这里的每一片都是由1024个连续的页面组成的。比如,PTE0映射第一片,PTE1映射接下来的一片,以此类推。如果地址空间是4GB,1024个PTE已经足够覆盖真整个空间。

(6)综合:端到端的地址翻译: A ---> B ---> C

假设:

存储器是按字节寻址的。

存储器访问是针对1字节的字的。

虚拟地址是14位长的(n = 14).

物理地址是12位长的(m = 12)。

页面大小是64字节(P = 64)。

9.8 存储器映射

(1)定义:Linux通过将一个虚拟存储器区域与一个磁盘上对象关联起来,已初始化这个虚拟存储器区域的内容。

(2)虚拟存储器区域可以映射到两种类型的对象中的一种

a.Unix文件系统中的普通文件:一个区域可以映射到一个普通磁盘文件的连续部分。

b.匿名文件:一个区域也可以映射到一个匿名文件,匿名文件是由内核创建的,包含的全是二进制零。由于磁盘和存储器之间并没有实际的数据传送,映射到匿名文件的区域中的页面有时也叫二进制零的页。

(3)共享对象

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

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

A.进程1将一个对象映射到它的虚拟存储器的一个区域中;            B.进程2映射到同一个共享对象之后;

 

(4)一个私有的写时拷贝对象:

A.两个进程都映射了私有的写时拷贝对象之后:

如A展示了一种请况,其中两个进程将一个私有对象映射到他们虚拟存储器的不同区域,但是共享这个对象同一个物理拷贝。对于每个映射私有对象的进程,相应私有区域的页表条目都被标记为只读,并且区域结构被标记为私有的写时拷贝。只要没有进程师徒写它自己的私有区域,它们就可以继续共享物理存储器中对象的一个单独拷贝。然而,只要有一个进程试图写私有区域内的某个页面,那么这个写操作就会触发一个保护故障。

B.进程2写了私有区域的一个页之后:

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

(5)execve函数:

假设运行在当前进程中的程序:

Execve(  “ a.out ”,   NULL,    NULL);

加载并运行a.out需要以下几个步骤:

l  删除已存在的用户区域。

l  映射私有区域。为新程序的文本、数据、bss和stack区域创建新的结构。

l  映射共享区域。

l  设置程序计数器。

(6)使用Mmap函数的用户级存储器映射

Mmap函数要求内核创建一个新的虚拟存储器区域,最好是从地址start开始的一个区域,并将文件描述fd指定的对象的一个连续的片映射到这个新的区域。连续的对象片的大小为length字节,从距文件开始处偏移量为offset字节的地方开始。

(7)munmap函数删除虚拟存储器的区域

#include  <unistd.h>

#include  <sys/mman.h>

int  munmap( void *start,   size_t length);

Munmap函数删除从虚拟地址start开始的,由接下来的length字节组成的区域。接下来对已删除区域的引用会导致段错误。

9.9 动态存储分配

(1)分配器的分类:

显式分配器:要求应用显式地释放任何已分配的块。C程序通过调用malloc函数来分配一个块,并通过调用free函数来释放一个块。

隐式分配器:要求适配器检测一个已分配块何时不再被程序所使用,那么就释放这个块。隐式分配器也叫垃圾收集器,而自动释放未使用的已分配的块的过程叫做垃收集。

(2)Malloc和free函数:

#include  <stdlib.h>

void   *malloc(size_t   size) ;

Malloc函数返回一个指针,指向大小为至少size字节的存储器块,这个块会为可能包含在这个块内的任何数据对象类型做对齐。

     下图展示了Malloc和free函数是如何管理一个C程序的16字小的堆。每个方框代表一个4字节的字。粗线标出的矩形对应于已分配块(有阴影的)和空闲块(无阴影的)。初始时,堆是有一个大小为16个字、双字对齐的、空闲块组成的。

a.程序请求一个4字的块。malloc的响应是:从空闲块的前部切出一个4字的块,并返回一个指向这个块的第一字的指针。

b.程序请求一个5字的块。malloc的响应是:从空闲块的前部分配一个6字的块。在本例中,malloc在块里填充了一个额外的字,是为了保持空闲块是双字边界对齐的。

c.程序请求一个6字的块。而malloc就从空闲块的前部切出一个6字的块。

d.程序释放在图b中6字的块。注意:在调用free返回之后,指针p2仍然指向被释放了的块。应有责任在它被一个新的malloc调用重新初始化之前不再使用p2.

e.程序请求一个2字的块。在这种情况下,malloc分配在前一步中被释放了的块一部分,并返回一个指向这个新块的指针。

(3)为什么要使用动态存储器分配:经常直到程序实际运行时,它们才知道某些数据结构的大小。

(4)分配器的要求和目标

A.要求

l  处理任意请求序列。一个应用可以有任意的分配请求和释放请求序列,只要满足约束条件:每个释放请求必须对应于一个当前已分配的顺序,这个块是由一个以前的分配器请求获得的。

l  立即响应请求。不允许分配器为了提高性能重新排列或者缓冲请求。

l  只使用堆:为了使分配器可拓展。

l  对齐块(对齐要求):可以保存任何类型的数据对象。

l  不修改已分配的块

B.目标

  1. 最大吞吐率:每个单位时间里完成的请求数。合理性能是指一个妇分配请求的最糟运行时间与空闲块的数量呈线性关系,而一个释放请求的运行时间是个常数。
  2. 最大化存储器利用率:给定n个分配和释放请求的某种顺序:

                R0, R1, R2, .... , RN

如果一个应用程序的一个p字节的块,那么得到的已分配的有效载荷是p字节。在请求RK完成之后,聚集有效载荷表示为PK,为当前已分配的块的有效载荷之和,而HK表示堆的当前的大小。

   UK   =    maxi<k Pi / Hk

分配器的目的是在整个序列中使峰值利用率Un-1最大化。

(5)碎片

1.分类

内部碎片:在一个已分配块比有效载荷大时发生的。

         内部碎片的量化 = (已分配块大小 - 有效载荷大小).在任意时刻,内部碎片数量只取决于以前请求的模式和分配器的实现方式。

外部碎片:当空闲存储器合计起来足够满足一个分配请求,但是没有一个单独的空闲块足够大可以来处理这个请求是发生。

外部碎片比内部碎片的量化要困难的多,因为它不仅取决于以前的请求模式和分配器的实现,还取决于将来请求的模式。

(6)隐式空闲链表

  1. 一个简单的堆块模式

     一个块由一个字的头部、有效载荷,以及可能的一些额外的填充组成的。头部编码了这个块的大小(包括头部和所有的填充),以及这个块是已分配的还是空闲的。如果我们强加一个双字的约束条件,那么块大小就总是8的倍数,且块大小的最低位3位总是零。因此,我们只需要存储大小的29个高位,释放剩余的低3位来编码其他信息。

例如:假设我们有一个已分配的块,大小为24(0x18)字节,那么它的头部是: 0x00000018  |  0x1  =  0x00000019

若一个块大小为40(0x28)字节的空闲块有如下头部:

             0x00000028  |  0x0  =  0x00000028

堆组织为一个连续的已分配块和空闲块的队列:

(7)带边界的标记的合并

1.前面的与后面的块都是已分配的

2.前面的块是已分配的,后面的块空闲

3.前面的块空闲,后面的块已分配

4.后面的块和前面的块都空闲

当我们试图在存储器中合并当前的块和后面的块时,只要在前面的块是空闲的,才会需要用到它的脚部,如果我们把前面块的已分配/空闲位存放在当前块中多出来的低位中,那么已分配的块就不需要脚部了,这样我们就可以把多出来的空间用作有效载荷。

(8)显式空闲链表

先进先出(FLFO:,维护链表,将新释放的块位置在链表的开始处。释放一个块可以在常数时间内完成,合并也是

l  按照地址顺序来维护链表,其中链表中每个块的地址都小于它后继的地址

缺点:空闲块必须足够大,以包含所有需要的指针,以及头部和可能的脚部。这就导致了更小块大小,也潜在地提高了内部碎片的程度。

(9)分离的空闲链表

1)      简单分离存储:每个大小类的空闲链表包含大小相等的块,每个块的大小就是这个大小类中最大元素的大小。

2)      分离适配

3)      伙伴系统:是分离适配的一种特例,其中每个大小类都是2的幂。基本的思路是假设一个堆的大小为2m个字,我们为每个块大小2k维护一个分离空闲块,其中0 ≤ k ≤ m.请求块大小向上舍入到最接近的2 的幂。最开始,只有一个大小为2m个字的空闲块。

主要优点:快速搜索和快速合并

主要缺点:要求块大小为2的幂可能导致显著的内部碎片。

9.10垃圾回收

  1. 垃圾收集器:是一种动态存储分配器,它自动释放程序不再需要的已分配块。自动回收堆存储器的过程叫做垃圾收集。在一个支持垃圾收集的系统中,应用显式分配堆块,但是从不显式地释放它们。
  2. 垃圾收集器的基本知识:垃圾收集器将视为一张有向可达图。下图的节点被分成一组根节点和一组堆节点。每个堆节点对应于对中的一个已分配块。

当存在一条任意根节点出发并到达p的有效路径时,我们说节点p是可达的。不可达节点对应于垃圾,是不能被应用再次使用的

推荐阅读