计算机是复杂的机器,旨在执行一项简单的任务:运行程序-浏览器、文本编辑器、网络服务器、视频游戏等-操作数据-照片、音乐、文本文件、数据库等。
在不使用时,这样的程序和数据会和平地存在于硬盘驱动器中,即使您的计算机已关闭,硬盘驱动器也负责使信息保持活动状态。运行应用程序意味着询问处理器(也称为。中央处理单元或CPU)来读取和执行组成计算机程序的机器指令,以及任何附加的数据处理。
硬盘存储了大量信息,但速度非常慢。比处理器慢得多:直接从硬盘读取指令的CPU将成为整个系统的严重瓶颈。为此,程序及其数据首先被复制到主存储器(又名:随机存取存储器或RAM),这是另一个比硬盘驱动器小但速度快得多的存储硬件组件,因此处理器可以从那里读取指令而不会降低速度。
主存储器可以看作是一长串单元,每个单元包含一些二进制数据,并标有一个称为存储器地址的数字。根据系统中可用的主内存量,内存地址的范围从0到N。程序使用的地址范围称为地址空间。
在计算机历史之初(如今在嵌入式系统中也是如此),程序可以访问整个主存储器,而它的管理则留给程序员。为这些机器编写软件很有挑战性:开发人员的部分工作是设计一种管理RAM访问的好方法,并确保整个程序不会溢出可用内存。
随着多任务处理的出现,当多个程序可以在同一台计算机上运行时,事情变得更加棘手。程序员不得不面对新的关键问题:
内存布局-在第一个程序之后位于RAM中的程序的地址空间将偏移一定的量,不再处于初始范围0到N之间,这是开发过程中需要注意的额外痛点;
内存碎片-当事物来回移动到内存时,可用空间会被分割成越来越小的区块。这将使寻找可用空间在内存中加载新程序和数据变得更加困难;
安全性-如果程序A意外覆盖了程序B的内存怎么办?或者,更糟糕的是:如果它故意从另一个程序读取敏感数据,如密码或信用卡信息,该怎么办?
因此,对于20世纪60年代早期的硬件架构师来说,很明显,一种形式的自动内存管理可以显著简化编程并解决更关键的内存保护问题。最终,他们提出了今天所熟知的虚拟内存。
在虚拟内存中,程序不能直接访问物理RAM。相反,它与称为虚拟地址空间的虚幻地址空间交互。操作系统与处理器协同工作,以提供这样的虚拟地址空间,并迟早将其转换为物理地址空间。
每次内存访问都是通过虚拟地址执行的,该虚拟地址并不指向内存中的实际物理位置。程序总是读取或写入虚拟地址,并且它完全不知道底层硬件中正在发生什么。
在上图中,您可以看到虚拟到物理转换的实际示例,它揭示了虚拟内存的两个主要优势:
每个程序都有一个从0开始的虚拟地址空间-这大大简化了程序员的生活:不再需要手动跟踪内存偏移量;
虚拟内存始终是连续的,即使底层的物理对应内存不是这样的-操作系统将把可用的部分收集到一个统一的虚拟内存块中,这是一项艰巨的工作。
虚拟内存机制还解决了RAM有限的问题:每个进程都给人一种使用未定义的内存量(通常大于物理内存量)的印象。此外,虚拟内存保证了安全性:程序A不能在不触发操作系统错误的情况下读取或写入分配给程序B的虚拟内存。在下面的段落中,我们将看到所有这些魔术是如何实现的。
虚拟内存机制需要一个位置来存储虚拟地址和物理地址之间的映射。也就是说,给定一个虚拟地址X,系统必须能够找到相应的物理地址Y。但是,您不能将这样的信息保存为1:1关系:这将需要一个与整个RAM一样大的数据库!
现代虚拟内存实现通过将虚拟内存和物理内存解释为一长串固定大小的小块来解决此问题(以及许多其他问题)。虚拟内存的块称为页,物理内存的块称为帧。内存管理单元(MMU)是CPU中的一个硬件组件,它将页面和帧之间的映射信息存储在称为页表的特殊数据结构中。页表类似于数据库表,其中每行都包含一个页索引及其对应的帧索引。每个运行的程序在MMU中都有一个页表,如下图所示。
该信息足以让MMU执行虚拟到物理的转换。当程序读取或写入虚拟地址时,它会唤醒MMU,MMU进而获取页索引(1),并在程序的页表中搜索相应的帧。一旦找到帧,MMU就利用帧偏移量(2)找到确切的物理内存地址,并将其传递回程序。此时转换完成:程序在RAM中有一个物理地址,可以通过虚拟地址读取或写入。
虽然程序被提供了一个竞争、干净和整洁的虚拟地址空间,但是操作系统和硬件都被允许在后台用驻留在物理内存中的数据做疯狂的事情。
例如,操作系统经常延迟从硬盘驱动器加载程序部分,直到程序尝试使用它。某些代码将仅在初始化期间或发生特殊情况时运行。程序的页表可以用指向不存在或尚未分配的帧的条目来填充。上面的图像3描述了这种情况,其中最后两页没有映射到任何地方。
这样的技巧对应用程序是完全透明的,它一直在读写自己的虚拟地址空间,而不知道背景噪音。然而,程序迟早会想要访问一个没有映射到RAM的虚拟地址:怎么办?
当程序访问当前未映射到物理帧的页上的虚拟地址时,就会发生页错误(也称为页未命中)。更具体地说,当页面存在于程序的页表中,但指向物理内存中不存在或尚不可用的帧时,就会发生页面错误。
MMU检测到页面错误,并将消息重定向到操作系统,操作系统将尽其所能在物理存储器中查找用于映射的帧。大多数情况下,这是一个简单的操作,除非系统内存不足。
分页是另一个内存管理技巧:操作系统将一些页面移动到硬盘驱动器,以便在没有更多物理内存可用时为其他程序或数据腾出空间。有时它也被称为交换,尽管不是百分之百正确。交换就是将整个过程移动到磁盘。有些操作系统在需要时也会这样做。
分页给程序以无限可用RAM的错觉。操作系统乐观地允许虚拟内存地址空间大于物理内存地址空间,因为它知道在需要的情况下可以将数据移入和移出硬盘驱动器。某些系统(例如Windows)为此使用称为分页文件的特殊文件。其他的(例如Linux)有一个称为交换区的专用硬盘分区(不过,由于历史原因,现代Linux执行分页而不是交换)。
不幸的是,硬盘比主存储器慢得多。因此,当发生页面错误并且页面临时移动到硬盘驱动器时,操作系统必须从运行缓慢的介质中读取数据并将其移回内存,从而导致延迟。总而言之,较少的分页意味着系统的运行效率更高。
当系统在分页上花费的时间比运行应用程序的时间更多时,就会发生抖动,这是由持续的页面错误流触发的。如果您运行的程序太多,填满了整个RAM和/或硬盘上的分页区域未优化,则会出现这种极端情况。操作系统试图跟上大量的页面错误请求,不断地在硬盘和物理内存之间移动数据,使系统陷入停顿。可以通过增加RAM容量、减少正在运行的程序数量或通过调整交换文件的大小来避免抖动。
虚拟内存还为正在运行的应用程序提供安全性:您的浏览器可以窥探文本编辑器的虚拟内存,反之亦然,而不会触发错误。内存保护的主要目的是防止进程访问不属于它的内存。
内存保护机制通常由MMU及其管理的页表提供,而其他体系结构可能使用不同的硬件策略。当程序试图访问它不拥有的虚拟内存的一部分时,会触发无效页面错误。MMU和操作系统捕获信号并引发称为分段故障(在Unix上)或访问冲突(在Windows上)的故障条件。作为响应,操作系统通常会终止程序。
分段故障和访问冲突也经常被错误地引发。执行手动内存管理的编程语言使您能够留出一部分内存用于存储程序数据:操作系统将为您提供大量空闲内存(也称为。缓冲区)根据程序的需要进行读写。但是,没有什么可以阻止您在缓冲区边界之外读取或写入,从而访问不属于您的程序或根本不存在的内存。操作系统将检测到非法访问并发出通常的违规信号。
虚拟内存为许多其他有趣的主题铺平了道路。例如,与传统的读写文件方式相比,内存映射文件是一种强大的抽象。内存映射允许程序直接从硬盘驱动器访问文件,就像它已经完全加载到RAM中一样,而不是手动将数据复制到内存中以对其进行操作。必要时,虚拟内存机制将照常负责将数据从硬盘移动到RAM。内存映射文件简化了程序员的工作,通常会加快文件访问操作。更多信息请点击此处。
虚拟内存也使得对内存消耗进行推理变得更加困难。假设您的某个程序占用300兆字节的内存:它是虚拟的还是物理的?该空间的一部分是否已分页到磁盘?如果是这样的话,传呼操作是否足够快呢?此外,如果您想让系统保持良好状态,调优分页文件/交换区域也是重要的一步。操作系统提供了许多工具来测量和调整内存:更多信息请单击此处和此处。
计算机希望-记忆彼得·J·丹宁-在内存成为虚拟安卓权威之前-什么是虚拟内存?Kernel.org-内存管理操作系统:三个简单的部分-第18章:分页Philippe的Opmann-自下而上的分页计算机科学入门-第6章:虚拟内存John T.Bell博士-操作系统,虚拟内存堆栈溢出-现代操作系统是否使用分页和分段?StackOverflow-什么是抽打?为什么会发生这种情况呢?维基百科-内存地址维基百科-分页维基百科-地址空间维基百科-虚拟内存维基百科-虚拟地址空间维基百科-颠覆维基百科-分段故障ITPro Today-分页性能Aleph One-为了乐趣和利润粉碎堆栈。
非常好的虚拟内存101。一些基本面是值得了解的,尽管它们几十年来一直在幕后发生,并自我管理。