信息安全系统设计基础第十三周学习总结
第九章 虚拟存储器
虚拟存储器提供了三种重要的能力
- 它将主存看成是一个存储在磁盘上的地址空间的高速缓存,在主存中只保存活动区域,并根据需要在磁盘和主存之间来回传送数据。
- 它为每个进程提供了一致的地址空间,简化了存储器的管理。
- 它保护了每个进程的地址空间不被其他进程破坏
为什么要了解虚拟存储器
- 虚拟存储器是中心的,遍及计算机系统的所有层面。2、虚拟存储器是强大的;3、虚拟存储器是危险的
9.1 物理和虚拟寻址
虚拟寻址:CPU生成虚拟地址访问主存,虚拟地址被翻译成物理地址再送到存储器。
早期PC使用物理寻址。现代处理器使用虚拟寻址。
9.2 地址空间
地址空间是一个非负整数地址的有序集合。如果地址空间中的整数是连续的,则成为线性地址空间。
虚拟地址空间:的大小是由表示最大地址所需要的位数来描述。
9.3 虚拟存储器作为缓存工具
9.3虚拟存储器作为缓存的工具
概念上而言, 虚拟存储器 被组织为一个由“存放在磁盘上的N个连续字节大小的单元”组成的数组。磁盘上的内容被缓存到内存中,内存和磁盘之间采用块block格式传输。由于虚拟存储的技术早于高速缓存的技术,所以当时这种块被称之为页(page),也就是现在的说法。如下图:
上图展示了一个8个虚拟页的小型虚拟存储器。绿色的3个页表示已经缓存到内存了(参考内存中绿色的三个页)连个未分配和三个未缓存的。
由于内存的访问速度比磁盘快100000倍,所以如果内存不命中,那么代价将是非常高的(程序运行将非常慢),另外,读取磁盘某一个扇区第一个字节的开销 比起读取整个扇区的字节慢100000倍。这两个条件决定了虚拟存储的页的大小最好是一个扇区或几个扇区。典型地是4KB-2MB。
还有,即便是有个很大的页,如果操作系统在替换页的操作处理不当,速度将会很慢。缺页是一种异常,名字就叫“缺页”,发生异常后,虚拟存储器加载磁盘到内存并替换牺牲页。操作系统再次运行该指令,就不再缺页了。
当我们与多人了解了虚拟存储器的概念之后,我们的第一印象是:“它的效率应该很低下吧,因为如果不命中,那么代价将非常高。”我们也担心页面的调度破坏程序的性能。实际上,虚拟存储器运行的相当好,主要归功于程序 局部性 (locality).
尽管整个运行过程中程序引用的不同页面的总数可能超出物理存储器的总的大小,但是局部性原则保证了在任意时刻,程序将往往在一个较小的活动页面(active page)集合工作,这个集合叫做工作集。
tips 在Unix系统中使用getrusage函数来检测缺页的数量 。
9.4虚拟存储器作为存储器管理工具
实际上,操作系统为“每一个进程”提供了一个独立的页表,因而也就是一个独立的虚拟地址空间。展现形式就是:让人感觉这个程序正在完全是使用CPU和内存资源。如下图,多个虚拟叶面同事映射到同一个共享物理页面上(动态链接库或者叫共享库就是这个原理)。
进程独立的地址空间允许每个进程的存储器映像使用相同的基本格式,而不管代码和数据实际存放在物理存储器的地方。比如Linux系统上每个进程文本节总是 从虚拟地址0x08048000初开始。总结起来说:虚拟存储器简化了链接、简化了加载、简化共享和简化存储器分配。
9.5虚拟存储器作为存储器保护的工具
现代操作系统都严格限制普通应用程序访问自己的只读数据和别进程的数据,还有操作系统的内核部分。所以虚拟存储器可以在页的开始部分设置几个标志位,用于 标明这个进程是系统进程还是用户进程。如果某一天指令违反了这个条件,那么CPU就触发一个异常。Unix外壳一般将这种异常报告为“段错误 segmentation fault”
9.6地址翻译,有点儿专业。跟应用程序层面关系不大。
9.7案例研究:Intel Core i7/Linux存储器系统
在64位的mac电脑上打印一个变量的指针, 结果指针是6个字节 。这是因为i7的CPU只支持48位的虚拟地址,和52位的物理地址空间。虽然我的mac是i5的但是估计也是48位虚拟地址。其他内容是地址翻译,缺页异常。
Linux虚拟存储器区域:Linux将虚拟存储器组织成一些区域的集合。一个区域(area)就是已经存在着(已分配的)的虚拟存储器的连续片(chunk)
9.8存储器映射
有一个疑问:“电脑4G的内存,加载一个程序很慢,但是看看内存剩余量,还有剩余内存,不应该慢啊。如果升级到8G的内存,程序加载的快了。这是因为操作 系统有一个叫做交换空间的东西,交换空间可能显示虚拟存储器的页数“。明白了,如果内存大,交换空间就大,程序加载就快。
概念:私有有的写时拷贝,理解这个概念很重要。用图来说明:
上图右部分说明了写时拷贝:进程1和2都共享内存中的一组页,但是进程2需要写最后一个页。那么启动写时拷贝,拷贝最后一页到内存其他地方。(内存中的页,可能不是连续的)。
fork函数直接在虚拟存储器上开辟一段,跟主进程一不一样的拷贝,那么,也就跟主进程指向同一个物理存储器。从上图左边部分中我们可以印证一下进程2可以看做是从进程1调用fork函数创建的。
execve是如何加载应用程序的。execve函数在当前进程中加载并运行a.out,用a.out程序有效的替换了当前程序。加载a.out时候需要以下几个步骤:
1)删除已存在的用户区域。
2)映射私有区域。为新的程序的文本、数据、bss、和栈区域创建新的区域结构。
3)映射共享区域。
4)设置程序技术器PC。使之指向文本区域的入口点。
9.9动态存储器分配malloc
动态分配,我自己的理解就是分配在虚拟存储器中的区域,这个区域可能已经在内存中了,也可能在磁盘上。
下面是真正的定义:动态存储器分配器,维护着一个进程的虚拟存储器区域,成为”堆“,对于每一个进程,操作系统内核维护着一个变量brk(break)指向堆的顶部。分配器有两种基本风格。
1)显式分配器:例如C和C++的malloc 和new 运算法。但是需要程序员收到free和delete处理。
2)隐式分配器:也叫做垃圾收集器,其自动释放未使用的已分配的块的过程叫做垃圾收集。例如:Java的垃圾回收机制。
动态存储分配器的要求和目标
●处理任意请求序列。
●立即响应请求。分配器必须立即响应分配请求。因此不允许分配器提高性能,从新排列或者缓冲请求。
●只是用堆。
●对齐块,比如8个字节的对齐。
●不修改已分配的块。不能压缩已分配的块。
目标
●1最大化吞吐率
●2最大化存储器的利用率。天真的程序员经常不正确的假设虚拟存储器是一个无限的资源,事实上,一个系统中被所有进程分配的虚拟存储器的全部数量是受磁盘上 交换空间 的数量限制的。
存储器碎片(这是一个很有意思的话题)。首先介绍内部碎片和外部碎片。如图:
外部碎片要比内部碎片处理复杂得多!分配器还要有其他功能: 合并空闲的块,也是非常有意思的(原书使用19页的量,来描述分配器的原理和动作 ),聪明的程序员采用了很多种技巧来实现,参考p568页。
9.10 垃圾收集(回收)
垃圾收集器就是一个动态分配器,它自动释放这些程序不再需要的已分配的块。垃圾收集可以追溯到20世纪60年代早起在MIT开发的Lisp系统。他是诸如Java、ML、Perl和Mathematica等现代语言系统的一个重要的部分。
垃圾回收的原理,如图。那些灰色的小圆点表示已经是垃圾了,需要清理。
是用专业的词语是:上图是一张可达图,白色的块表示可达,灰色的块表示不可达(从任意根节点出发不可达)。
9.11程序中常见的与存储器有关的错误。
●间接引用坏指针
●读未初始化的存储器
●允许栈缓存去溢出
●假设指针和它们指向的对象是相同大小的
●造成错位
●引用指针,而不是它指向的对象
●误解指针运算
●应用不存在的变量
●应用空闲块中的数据
●引起存储器泄露
总结
虚拟存储器的三个重要功能:
第一,它在内存中自动缓存“最近使用的存放在磁盘上的”虚拟地址空间的内容。
第二,虚拟存储器简化了存储器的管理,简化链接和共享
第三,虚拟存储器通过在每条页表条目中加入保护位从而简化了存储器保护。