虚拟内存

为了更加有效地管理内存,现代系统提供了一种对主存的抽象概念,叫做虚拟内存。虚拟内存提供了三个重要的能力:

  • 它将主存看成是一个存储在磁盘上的地址空间的高速缓存,在主存中只保存活动区域,并根据需要在磁盘和主存之间来回传送数据。
  • 为每个进程提供了一致的地址空间,从而简化了内存管理
  • 保护了每个进程的地址空间不被其他进程破坏

一、物理和虚拟地址

计算机系统的主存被组织成一个由M个连续的字节大小的单元组成的数组。,每字节都有一个唯一的物理地址。第一个字节的地址为0,接下来的字节地址为1,再下一个为2,依此类推。CPU访问内存的最自然的方式就是使用物理地址。这种方式称为物理寻址。

现代处理器使用的是一种称为虚拟寻址的寻址形式。CPU通过生成一个虚拟地址来访问主存,这个虚拟地址在被传送到内存之前先转换成适当的物理地址。将一个虚拟地址转换为物理地址的任务叫做地址翻译

二、地址空间

地址空间是一个非负整数地址的有序集合。如果地址空间中的整数是连续的,那么它就是一个线性地址空间。

在一个带虚拟内存的系统中,CPU从一个有N=2^n个地址的地址空间中生成虚拟地址,这个地址空间称为虚拟地址空间。

一个系统还有一个物理地址空间,对应于系统中物理内存的M个字节。M不要求是2的幂,但是为了简化讨论,假设M=2^n。

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

三、虚拟内存作为缓存的工具

概念上而言,虚拟内存被组织为一个由存放在磁盘上的N个连续的字节大小的单元组成的数组。每字节都有一个唯一的虚拟地址作为数组的索引。磁盘上数组的内容被缓存在主存中。

和存储器层次结构中其他缓存一样,磁盘上的数据被分割成块,这些块作为磁盘和主存之间的传输单元。

虚拟内存系统通过将虚拟内存分割为称为虚拟页的大小固定的块来处理这个问题。

类似地,物理内存被分割为物理页。物理页也被称为页帧。

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

  • 未分配的:虚拟系统还没分配的页。未分配的块没有任何数据和它们相关联,因此也就不占用任何磁盘空间。
  • 缓存的:当前已经缓存在物理内存中的已分配页。
  • 未缓存的:未缓存在物理内存中的已分配页。

3.1页表

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

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

3.2页命中

当CPU想要读取虚拟内存中的某个页时(假设该页已经被缓存),地址翻译硬件将虚拟内存地址作为一个索引来定位页表中的对应表项。并通过表项中的存放的相应物理页的起始位置来访问,这个物理页中缓存着该虚拟页的内容。这就是页命中。

3.3缺页

当主存并没有缓存某个虚拟页,然而CPU想要读取该页的数据时,就会产生缺页。缺页会触发缺页异常。缺页异常调用内核中的缺页异常处理程序,该程序会在主存中选择一个牺牲页来将要访问的页缓存在主存中,并且更新页表信息。

在磁盘和主存之间传送页的活动称为交换或者页面调度

3.4分配页面

调用malloc函数会在虚拟内存中分配一个虚拟页,并且更新页表信息。

3.5局部性保证了页面调度的良好性能

当了解了虚拟内存的概念之后,我们的第一印象通常是它的效率会非常低。因为不命中的处罚很大,有可能页面调度会破坏程序的性能。实际上,虚拟内存工作得相当好,这主要归功于局部性。

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

只要我们的程序拥有良好的时间局部性,虚拟内存系统就能工作得相当好,但是,当然不是所有的程序都能展现良好的时间局部性。如果工作集的大小超出了物理内存的大小,那么程序将产生一种不幸的状态,叫做抖动,这时页面将不断地换进换出。虽然虚拟内存通常是有效的,但是如果一个程序性能十分慢,就应该考虑以下是否发生了抖动。

四、虚拟内存作为内存管理的工具

虚拟内存大大简化了内存管理,并提供了一种自然的保护内存的方法。

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

虚拟内存简化了链接、加载、代码和数据共享,以及应用程序的内存分配。

  • 简化链接。每个进程独立的虚拟内存空间允许每个进程的内存映像使用相同的基本格式,而不管代码和数据实际存放在物理内存的何处。比如,一个给定的Linux系统上的每个进程都是用类似地内存格式。对于64位地址空间,代码段总是从虚拟地址0x400000开始。数据段根在代码段之后,中间有一段符合要求的对齐空白。栈占据用户进程地址空间最高的部分,并向下生长。这样的一致性极大地简化了链接器的设计和实现,允许链接器生成完全链接的,这些可执行文件是独立于物理内存中代码和数据的最终位置的。
  • 简化加载。虚拟内存还使得容易想内存中加载可执行文件和共享对象文件。要把目标文件中的代码和数据加载到一个新创建的进程中,Linux加载器将为代码和数据分配虚拟页,把他们标记为未缓存的,将页表条目指向目标文件中适当的位置。有趣的是,加载器从来不从磁盘到内存实际复制任何数据。在每个页初次被引用时,要么是CPU取指令时引用的,要么是一条正在执行的指令引用一个内存位置时引用的,虚拟内存系统会按照需要自动地调度数据页。
  • 简化共享。独立的虚拟地址空间为操作系统提供了一个管理用户进程和操作系统自身之间共享的一致机制。一般而言,每个进程都有自己私有的代码、数据、堆以及栈区域。这是不和其他进程共享的。在这种情况中,操作系统创建页表,将相应的虚拟页映射到不连续的物理页面。但是,在一些情况中,还是需要进程来共享代码和数据。比如每个进程必须调用相同的操作系统内核代码,而每个C程序都会调用C标准库中的程序。操作系统通过将不同进程中适当的虚拟页面映射到相同的物理页面,从而安排多个进程共享这部分代码的一个副本,而不是在每个进程中都包括单独的内核和C标准库的副本。
  • 简化内存分配。虚拟内存为向用户进程提供一个简单的分配额外内存的机制,当一个运行在用户进程中的程序要求额外的堆空间时,操作系统分配一个适当数字个的连续的虚拟内存页面,并且将它们映射到物理内存中任意位置的k个任意的物理页面。由于页表的存在,操作系统没有必要分配连续的物理内存页面,页面可以随机地分散在物理内存中。

五、虚拟内存作为内存保护的工具

任何现代计算机系统必须为操作系统提供手段来控制对内存系统的访问。比如不应该允许一个用户进程修改它自己的制度代码段,更不应该允许它修改或者读取任何内核中的代码和数据结构。

虚拟内存系统所提供的独立的地址空间使得区分不同进程的私有内存变得很容易,通过在页表这一数据结构上设置访问标志位就可以实现控制对一个虚拟页面内容的访问。

如果某条指令违反了访问控制,CPU就会触发一个故障,将控制传递给一个内核中的异常处理程序。Linux shell一般将这种异常报告为段错误。

六、Linux虚拟内存系统

在这一小节中我们的目标时对Linux的虚拟内存系统做一个描述。大致了解一个世纪的操作系统是如何组织虚拟内存,以及如何处理缺页的。

Linux为每个进程维护了一个单独的虚拟地址空间,如下图所示。

内核虚拟内存包含内核中的代码和数据结构。内核虚拟内存的某些区域被映射到所有进程共享的物理页面,比如图中所表示的物理内存和内核代码和数据。值得一提的是,内核将大小等于主存的虚拟页面都映射到了相应的一组连续的物理页面。这为内核提供了一种便利的方法来访问物理内存中任何特定的位置。这就是图中的物理内存部分。内核虚拟内存的其他区域包含每个进程都不相同的内容。

6.1Linux虚拟内存区域

Linux将虚拟内存组织成一些连续的区域集合,这个区域称为段。一个区域就是已经分配了的虚拟内存页的连续片。这些页是以某种方式相关联的。比如,代码段、数据段、堆、共享库段,以及用户栈都是不同的区域。每个存在的虚拟页都保存在某个区域中,已经被分配但不属于某个区域的虚拟页是不存在的,没被分配的虚拟内存空间不能被进程引用,同时也不占用任何额外资源。

图中还强调对于每个进程,内核都会维护一个单独的task结构。该结构的元素包含或者指向内核运行该进程所需要的所有信息,比如PID,指向用户栈的指针等等。

task结构中的一个条目指向mm结构,该结构描述了虚拟内存的当前状态。

6.2Linux缺页异常处理

当MMU在试图翻译某个虚拟内存地址A时,触发了一个缺页故障。这个异常将导致控制转移到内核的却也处理程序,处理程序随后就会执行下面的步骤。

  1. 虚拟地址A是合法的吗?也就是说,处理程序首先会判断地址A是否属于某个段内。如果不属于,那么该处理程序就会触发一个段错误。
  2. 试图进行的内存访问是否合法?也就是说,处理程序会检查进程是否有读写或者执行或者区域内的页面的权限。如果不合法,那么处理程序就会触发一个保护异常,终止该进程。
  3. 如果1,2步骤都通过了,那么内核就确认这个缺页是由于堆合法的虚拟地址进行合法的操作造成的。这时内核就会选择一个牺牲页面,传入一个新的页面并且更新页表。当缺页处理程序返回时,CPU重新启动引起缺页的指令。

6.3内存映射

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

  1. Linux文件系统中的普通文件:一个区域可以映射到一个普通磁盘文件的连续部分。比如一个可执行目标文件,它会以页大小被分成很多片段,每一片段包含一个虚拟页面的初始内容。因为是按需进行页面调度,所以这些虚拟页面没有实际交换进入物理内存,直到CPU第一次引用到页面。如果区域比文件还要大,那么就用0来填充剩余部分。
  2. 匿名文件:一个区域也可以映射到一个匿名文件,匿名文件是由内核创建的,包含的内容全是二进制零。

无论是以上哪种情况,一旦一个虚拟页面被初始化了,它就在一个由内核维护的专门的交换文件之间换来换去。交换文件也叫做交换空间或者交换区域。

6.4 使用mmap函数的用户级内存映射

Linux进程可以使用mmap函数来创建新的虚拟内存区域,并将对象映射到这些区域中。

#include <unistd.h>
#include <sys/mman.h>void *mmap(void *start, size_t length, int port, int flags, int fd, off_t offset);
//若映射成功则返回指向映射区域的指针,若出错则为MAP_FAILEDint munmap(void *start, size_t length);
//成功返回0,出错返回-1

mmap函数要求内核创建一个新的虚拟内存区域,最好是从地址start开始的一个区域,并将文件描述符fd指定的对象的一个连续片映射到这个区域,连续的对象片大小为length字节,从距文件开始处偏移量为offset字节的地方开始。start地址仅仅是一个暗示,通常被定义为NULL。参数port指定了新的因施工和的虚拟内存区域的访问权限为。参数flags指定了对象类型。

munmap函数删除从虚拟地址start开始,由接下来length字节组成的区域。

用户代码未处理nullreferenceexception_CSAPP 第九章整理 未完成相关推荐

  1. 关于SubSonic3.0生成的表名自动加复数(s)的“用户代码未处理SqlException,对象名‘xxxs‘无效”异常处理

    关于SubSonic3.0生成的表名自动加复数(s)的"用户代码未处理SqlException,对象名'xxxs'无效"异常处理 参考文章: (1)关于SubSonic3.0生成的 ...

  2. java2实用教程第5版第九章_java2实用教程(例子代码)第4版第九章.doc

    java2实用教程(例子代码)第4版第九章 java2实用教程(例子代码)第4版第九章 例9_3 Lt4_3.java public clss Lt4_3 { public static void m ...

  3. 关于SubSonic3.0生成的表名自动加复数(s)的“用户代码未处理SqlException,对象名'xxxs'无效”异常处理...

    使用SubSonic3.0模版生成时,同2.2版本一样,都会自动在一些类似数据库要用到的关键后面加要s(复数),这里也是3.0的一个小Bug,在查询时由于插件并没有完全的去掉s,所以会产生" ...

  4. Windows Server 2008 系统上c#读取Excel遭遇“用户代码未处理 ComException”错误

    以前用代码读取Excel的时候从没有遇到过此错误,开始以为是权限问题,但是用普通的Console Application时却能够读取,放到web application里面就出现此错误: 导致错误的是 ...

  5. 用户代码未处理EntityCommandExecutionmException报错解决方案

    原因可能是(1)没有编译好,清理解决方案,重新生成解决方案.           (2)可能是WebSiteConfiguration.DbProviderName; 中为DbProviderName ...

  6. Silverlight 用户代码未处理 TypeLoadException

    在Silverlight中动态创建Enum时,多次调用改方法出现上图所示错误,后来发现定义名称都是一样的, 在程序中声明全局变量去区别就可以了. int num = 1; private Type C ...

  7. 《C语言程序设计》第五版谭浩强课后答案 第九章《用户自己建立数据类型​》习题答案 (大一大二、考研、计算机二级必看)

    第九章<用户自己建立数据类型​>习题答案 1.定义一个结构体变量(包括年.月.日).计算该日在本年中是第几天,注意闰年问题. 2.写一个函数days,实现第1 题的计算.由主函数将年.月. ...

  8. 第九章:用户自己建立数据类型

    第九章:用户自己建立数据类型 9.1 定义和使用结构体变量 9.1.1 自己建立结构体类型 C语言允许用户自己建立由不同数据类型组成的组合型的数据结构,它称为结构体.可以通过建立结构体类型表示数据结构 ...

  9. thinkPython2考试周吐血整理(第一章到第九章)

    第二章 球体体积是三分之四倍的圆周率乘以半径立方,求半径为5的球体体积. 假如一本书的封面标价是24.95美元,书店打六折.第一本运费花费3美元,后续每增加一本的运费是75美分.问买60本一共得花多少 ...

最新文章

  1. redis设置允许远程访问
  2. javamail.providers not found
  3. 第十七届智能车竞赛研讨会
  4. html a name属性
  5. 解决nginx proxy_pass反向代理cookie,session丢失的问题
  6. 大数据笔记(三十二)——SparkStreaming集成Kafka与Flume
  7. Hexo+码云+git快速搭建免费的静态Blog
  8. php redis zset 延迟队列_PHP + Redis 实现简单消息队列
  9. 电话号码的字母组合Python解法
  10. 文档排序模型--查询似然Query Likelihood
  11. pytorch数据加载时报错OSError: [Errno 22] Invalid argument
  12. 接口interface修饰符相关问题总结
  13. CVPR 2019 | 步步为营!通过迭代式模糊核预测提高超分辨质量
  14. phpmyadmin mysql更新_mysql利用phpmyadmin实现数据库同步更新
  15. go语言的struct
  16. Python计算绘图——曲线拟合问题(转)
  17. 数据结构笔记(二十六)-- 图的存储
  18. Spring的注解@Autowired和@Resource的区别
  19. 海外邮件收发阻碍多?网易企业邮箱为您保驾护航【网易企业邮箱怎么申请】
  20. jq小插件tiptip

热门文章

  1. BERT: Bidirectional Encoder Representations from Transformers双向Transformer用于语言模型 NAACL 2018
  2. 使用Docker搭建私有云笔记
  3. Systrace的用法小结
  4. SQL Server大数据表的分区存储
  5. css 总结内容用到的绝对居中的几种方式
  6. 阻止xap文件在浏览器中缓存
  7. 学习《html5.css3.0》网页布局和样式精粹(第一天)
  8. Java常用接口与类——基本数据类型的包装类
  9. opensource项目_最佳Opensource.com:开放的组织文化
  10. 克服浮躁_建立强大的全球社区时克服挑战