我经常对操作系统中的虚拟化概念感到困惑。将RAM视为物理内存,为什么我们需要虚拟内存来执行进程?
当从外部硬盘驱动器中获取进程(程序)并放入主内存(物理内存)以进行执行时,虚拟内存位于哪里。
谁负责虚拟内存,虚拟内存的大小是多少?
假设RAM大小为4GB(即2^32-1地址空间),虚拟内存的大小是多少?
我经常对操作系统中的虚拟化概念感到困惑。将RAM视为物理内存,为什么我们需要虚拟内存来执行进程?
当从外部硬盘驱动器中获取进程(程序)并放入主内存(物理内存)以进行执行时,虚拟内存位于哪里。
谁负责虚拟内存,虚拟内存的大小是多少?
假设RAM大小为4GB(即2^32-1地址空间),虚拟内存的大小是多少?
缺点:
这不具有可扩展性。理论上,当应用程序执行某些重度任务时,可能需要大量内存。因此,为了确保它永远不会耗尽内存,分配给它的内存区域必须始终大于或等于所需内存量。如果一款软件的最大理论内存使用量为2 GB
(因此需要从RAM分配2 GB
内存),而安装在只有1 GB
内存的机器上,该怎么办?软件是否应该在启动时中止,并表示可用RAM小于2 GB
?还是应该继续运行,当所需内存超过2 GB
时,立即中止并以“内存不足”的消息退出?
无法防止内存混乱。有数百万种软件,即使每个软件只被分配1 kB
内存,所需总内存也将超过16 GB
,这比大多数设备提供的内存还要多。那么,如何分配不会侵犯彼此区域的内存槽?首先,没有集中的软件市场可以调节新软件发布时必须从“尚未占用的区域”分配多少内存,其次,即使有,也不可能做到这一点,因为软件数量实际上是无限的(因此需要无限的内存来容纳所有软件),任何设备上可用的总RAM都不足以容纳所需的一小部分,因此必然会导致一个软件的内存边界侵犯另一个软件的内存边界。那么当Photoshop被分配内存位置1
到1023
,而VLC被分配1000
到1676
时会发生什么?如果Photoshop在位置1008
存储了一些数据,然后VLC用自己的数据覆盖了它,稍后Photoshop访问它,认为它是先前存储在那里的相同数据,你可以想象,会发生糟糕的事情。
很明显,正如你所看到的,这个想法相当天真。
说设备刚刚开机,操作系统刚刚启动,现在没有其他进程运行(忽略操作系统,它也是一个进程!),你决定启动VLC。所以VLC从最低字节地址的RAM中分配了一部分。好的。现在当视频正在播放时,您需要启动浏览器查看某个网页。然后您需要启动记事本来记录一些文本。然后启动Eclipse进行编码...很快您的4 GB内存全部用完,RAM看起来像这样: 问题1:现在你无法启动其他进程,因为所有的内存都被使用完了。因此,编写程序时必须考虑可用的最大内存(实际上可用的内存可能还要少,因为其他软件也会同时运行!)。换句话说,在你那破旧的1GB电脑上,你不能运行高内存消耗的应用程序。可能的解决方案2:让我们尝试另一个方案 - 让操作系统大部分管理内存。软件只需在需要任何内存时向操作系统请求,操作系统将相应地进行调整。例如,如果新进程请求内存,操作系统将确保从可能的最低字节地址(如前所述,RAM可以想象为字节数组,因此对于4 GB RAM,字节的地址范围为0到2 ^ 32-1)分配内存,否则,如果是正在运行的进程请求内存,则将从该进程仍然驻留的最后一个内存位置分配。由于软件将发出地址而不考虑实际存储数据的内存地址是什么,因此操作系统必须维护一个映射,每个软件对应一个映射,将软件发出的地址映射到实际物理地址(注意:这是我们称之为“虚拟内存”概念的两个原因之一。软件不关心它们的数据存储在哪里的真实内存地址,它们只是即时输出地址,操作系统会找到合适的位置并在以后查找它们,如果需要的话)。
Opera 流畅地适应在底部。现在你的 RAM 看起来像这样:
很好。一切看起来都很好。然而,剩余的空间不多,现在你需要再次启动Chrome,这是一个已知的内存占用大户!它需要大量的内存才能启动,而你几乎没有留下任何空间......除非......你现在注意到一些最初占用大量空间的进程现在不需要太多空间了。也许你已经停止了在VLC中的视频播放,因此它仍然占用一些空间,但不像运行高分辨率视频时那样需要太多空间。同样的情况也适用于Notepad和Photos。你的RAM现在看起来是这样的:
空洞
问题又来了!回到起点!之前,空洞是由于进程终止导致的,现在是因为进程需要的空间比以前少了!你又面临同样的问题,空洞
合并后产生的空间比所需空间更多,但它们分散在各处,在单独使用时没有太大用处。因此,你必须再次移动这些进程,这是一个昂贵的操作,并且非常频繁,因为进程在其生命周期内经常缩小。
问题3:进程在其生命周期内可能会缩小,留下未使用的空间,如果需要使用这些空间,则需要进行昂贵的操作,即移动许多进程。这被称为
内部碎片
。
好的,现在,您的操作系统做了必要的事情,移动了进程并启动了Chrome,一段时间后,您的RAM看起来像这样:
很好。现在假设您再次在VLC中观看Avatar。它的内存需求将会增加!但是...没有空间供其增长,因为记事本紧贴在其底部。所以,所有进程都必须向下移动,直到VLC找到足够的空间!好的。现在假设照片正在用于从外部硬盘加载一些照片。访问硬盘将您从缓存和RAM的领域带到比数量级慢得多的磁盘领域。痛苦地、不可撤销地、超然地慢。这是一个I/O操作,这意味着它不是CPU绑定(它恰恰相反),这意味着它现在不需要占用RAM。然而,它仍然固执地占用着RAM。如果您想同时启动Firefox,您就不能这样做,因为可用的内存不多,而如果Photos在其I/O绑定活动期间被移出内存,它将释放大量内存,接着是(昂贵的)压缩,接着是Firefox适合其中。问题4:如果进程需要增长,则这将是一项非常昂贵的操作。
因此,正如我们所看到的,即使采用虚拟内存的方法,我们仍然有很多问题。问题5:I/O绑定作业一直占用RAM,导致RAM被低效利用,而这些RAM在此期间可以被CPU绑定作业使用。
左侧是进程的虚拟地址空间。假设虚拟地址空间需要40个内存单元。如果物理地址空间(右侧)也有40个内存单元,那么将所有左侧位置映射到右侧位置就成为可能,我们会非常高兴。但不幸的是,物理内存不仅可用内存单位更少(此处为24),而且还必须在多个进程之间共享!好吧,让我们看看我们如何应对。
当进程开始时,会发出一个内存访问请求,请求的位置为35
。这里页面大小为8
(每个页面
包含8
个位置,整个虚拟地址空间中共有40
个位置,因此共有5
个页面)。所以该位置属于第4
页(35/8
)。在该页面
中,该位置的偏移量为3
(35%8
)。因此,该位置可以用元组(pageIndex, offset)
=(4,3)
来指定。这只是开始,所以进程的任何部分都还没有存储在实际物理内存中。因此,当前的页表
为空,该页表维护了左侧页面到右侧实际页面(称为帧
)的映射。因此,操作系统放弃CPU,让设备驱动程序访问磁盘并获取该进程的页面4
(基本上是从磁盘上的程序中获取的内存块,其地址范围为32
至39
)。当它到达时,操作系统将页面分配到RAM的某个位置,比如第一个帧,该进程的页表
注意到页面4
映射到RAM中的帧0
。现在数据终于存在于物理内存中了。操作系统再次查询元组(4,3)
的页表
,这次,页表表示页面4
已经映射到RAM中的帧0
。因此,操作系统直接转到RAM中的第0
帧,在该帧中访问偏移量为3
的数据(花点时间来理解一下。整个页面
从磁盘上获取后,被移动到了帧
中。因此,页面中单个内存位置的偏移量在帧中也是相同的,因为在页面
/帧
内,内存单元仍然位于相对的同一位置!),并返回数据!因为数据在第一次查询时没有在内存中找到,而必须从磁盘中获取并加载到内存中,所以它构成了一个缺失。28
,它被转换为(3,4)
。目前页表
只有一个条目,将页面4
映射到帧0
。因此这又是一次缺页,进程放弃CPU,设备驱动程序从磁盘获取页面,进程再次控制CPU,并更新其页表
。现在假设页面3
映射到RAM中的帧1
。因此(3,4)
变成了(1,4)
,并返回RAM中该位置的数据。很好。以这种方式,假设下一个内存访问请求的位置是8
,它被转换为(1,0)
。页面1
还没有在内存中,重复相同的过程,页面
被分配到RAM中的帧2
。现在RAM-进程映射看起来像上面的图片。此时,原本只有24个内存单元可用的RAM已经填满。假设该进程的下一个内存访问请求来自地址30
。它映射到(3,6)
,页表
显示页面3
在RAM中,并且将其映射到帧1
。太好了!因此数据从RAM位置(1,6)
获取并返回。这构成了一个命中,因为所需的数据可以直接从RAM中获取,因此速度非常快。同样地,下几个访问请求,比如位置11
、32
、26
、27
都是命中,即进程请求的数据可以直接在RAM中找到,无需查找其他地方。3
的内存访问请求。它转化为(0,3)
,而该进程的页表
目前有3个条目,对应于页面1
、3
和4
,表示该页面不在内存中。与之前的情况类似,它从磁盘中获取,但不同的是,RAM已经填满了!那么现在该怎么办呢?这就是虚拟内存的美妙之处——从RAM中驱逐一个帧!(有各种因素来决定要驱逐哪个帧。它可以基于LRU
,其中最近最少访问的帧是要被驱逐的。也可能是基于先进先出
的原则,其中分配时间最长的帧被驱逐等等。)因此,某些帧被驱逐。假设我们随机选择了帧1。然而,那个帧
映射到某个页面
!(目前,它由页表映射到我们唯一的一个进程的第3页)。因此,必须告诉该进程这个悲惨的消息,即一个帧
,不幸地属于你,将从RAM中驱逐出去,为其他页面
腾出空间。该进程必须确保更新其页表
以获取此信息,即删除该页面-帧对的条目,以便下次请求该页面
时,它会正确地告诉该进程,该页面
不再在内存中,并且必须从磁盘中获取。好的。因此,帧1
被驱逐,页面0
被带入并放置在RAM中,并且页面3
的条目被删除,并被页面0
替换,映射到相同的帧1
。因此,我们的映射现在如下所示(请注意右侧第二个帧
中的颜色变化):
看到刚才发生了什么了吗?该进程需要增长,需要比可用的RAM更多的空间,但与我们之前的情况不同,那时每个RAM中的进程都必须移动以适应增长的进程,这里只需进行一个页面替换即可!这是由于进程的内存不再需要连续,可以分块驻留在不同的位置,操作系统会维护它们的信息,并在需要时适当地查询它们。请注意:您可能会想,嗯,如果大部分时间都是未命中,数据必须不断从磁盘加载到内存中怎么办?是的,在理论上是可能的,但大多数编译器都是按照引用局部性的方式设计的,即如果使用某些内存位置的数据,则下一个所需的数据将位于非常接近的某个地方,可能来自同一页,刚刚加载到内存中的页面。因此,下一个未命中将在相当长的一段时间后发生,大多数即将到来的内存需求将由刚刚带入的页面或最近使用的页面满足。完全相同的原则也允许我们驱逐最近最少使用的页面,逻辑是很久没有使用的东西也不太可能再被使用。然而,并非总是如此,在特殊情况下,性能可能会受到影响。稍后再详细了解。页面
替换,而不需要移动任何其他进程。
对于问题2,请花点时间理解,这种情况已经完全被移除了!现在不需要移动一个进程来适应一个新的进程,因为整个进程永远不需要一次性全部适应,只有某些页面需要临时适应,这是通过从RAM中驱逐帧
来实现的。所有操作都是以页面
为单位进行的,因此现在没有空洞
的概念,因此也没有任何移动的问题!也许由于这个新需求,需要移动10个页面
,但还有成千上万个页面
没有受到影响。而之前,所有进程(每一位)都必须被移动!
问题2的解决方案:为了容纳一个新的进程,只需要根据需要清除其他进程中最近未使用的部分的数据,并以固定大小的单位称为
页面
进行操作。因此,这个系统不存在空洞
或外部碎片
的可能性。
现在,当进程需要执行一些I/O操作时,它可以轻松地放弃CPU!操作系统只需从RAM中驱逐其所有页面
(可能将其存储在某个缓存中),而新进程同时占用RAM。当I/O操作完成后,操作系统只需将这些页面
恢复到RAM中(当然是通过替换其他进程的页面
,可能是替换原始进程的进程,也可能是需要执行I/O操作的进程之一,并因此可以放弃内存!)
问题5的解决方案:当进程执行I/O操作时,它可以轻松地放弃RAM使用权,这可以被其他进程利用。这导致了RAM的合理利用。
当然,现在没有进程直接访问RAM。每个进程都访问一个虚拟内存位置,该位置映射到一个物理RAM地址,并由该进程的页表
维护。映射由操作系统支持,操作系统让进程知道哪个框架为空,以便为进程安装新页面。由于该内存分配由操作系统本身监督,因此它可以轻松确保没有进程通过仅从RAM中分配空框架或通过侵入RAM中另一个进程的内容而侵犯另一个进程的内容,通信以更新其页表
。
分页
(除其他技术外),结合虚拟内存,支持了当今运行在操作系统上的软件!这使得软件开发人员不必担心用户设备上有多少可用内存,在哪里存储数据,如何防止其他进程破坏他们软件的数据等等。但是,它当然不是绝对可靠的。存在以下缺陷:
分页
最终通过使用磁盘作为辅助备份来给用户提供无限内存的幻觉。从辅助存储器检索数据以适应内存(称为页面交换
,在RAM中找不到所需页面的事件称为页面错误
)是一种IO操作,因此很昂贵。这会减慢进程速度。连续进行多次此类页面交换后,进程变得非常缓慢。你是否曾经看到过你的软件正常运行,突然变得非常缓慢,几乎挂起或只能重启?可能是太多的页面交换导致其变慢(称为抖动
)。为什么我们需要虚拟内存来执行进程? - 正如答案详细解释的那样,为了给软件一种设备/操作系统具有无限内存的假象,使得任何大小的软件都可以运行,而不必担心内存分配或其他进程损坏其数据,即使在并行运行时也是如此。这是一个概念,通过各种技术实现,其中之一,如本文所述,是分页。它也可能是分段。
当从外部硬盘驱动器将进程(程序)带到主内存(物理内存)进行执行时,这个虚拟内存位于哪里? - 虚拟内存本身并不存在,它是一个抽象概念,总是存在的。当软件/进程/程序启动时,会为其创建一个新的页表,并包含从该进程输出的地址到实际RAM中的物理地址的映射。由于进程输出的地址不是真实地址,在某种意义上,它们实际上就是虚拟内存。
谁负责虚拟内存,虚拟内存的大小是多少? - 由操作系统和软件共同负责。想象一下你代码中的一个函数(最终编译并制成可执行文件),其中包含一个局部变量 - 一个int i
。当代码执行时,i
在函数堆栈中获得一个内存地址。该函数本身作为一个对象存储在其他地方。这些地址是编译器生成的(编译你的代码成为可执行文件的编译器)- 虚拟地址。当执行时,i
必须至少在该函数的实际物理地址中驻留一段时间(除非它是静态变量!),因此操作系统将编译器生成的 i
的虚拟地址映射到实际物理地址,以便在该函数中的某些代码需要 i
的值时,该进程可以查询 OS 获取该虚拟地址,然后 OS 可以查询存储的物理地址以获取存储的值,并返回它。
假设 RAM 的大小为 4GB(即 2^32-1 个地址空间),虚拟内存的大小是多少? - RAM 的大小与虚拟内存的大小无关,它取决于操作系统。例如,在 32 位 Windows 上,它是 16 TB
,在 64 位 Windows 上,它是 256 TB
。当然,它还受磁盘大小的限制,因为那里备份了内存。虚拟内存在IT技术中是一个抽象概念,它为程序员提供了有无限内存的幻觉。
虚拟内存映射与实际物理地址相对应。操作系统通过页表等数据结构来创建和处理这些映射。虚拟内存映射总是在页表或某些类似的数据结构中(如果是其他虚拟内存实现方式,我们可能不应该称之为“页表”)。页表也位于物理内存中 - 通常位于内核保留空间中,因此用户程序无法覆盖。
虚拟内存通常比物理内存大 - 如果虚拟内存和物理内存大小相同,则几乎没有必要进行虚拟内存映射。
通常只有程序的需要部分驻留在内存中 - 这是一个称为“分页”的主题。虚拟内存和分页密切相关,但并非完全相同的话题。还有其他虚拟内存实现,例如分段。
我可能猜错了,但我敢打赌你难以理解的问题与特定的虚拟内存实现有关,很可能是分页。没有一种完全正确的分页方法 - 有许多实现,您的教科书描述的方法可能与出现在Linux / Windows等真实操作系统中的方法略有不同 - 可能存在细微差异。
我可以在分页上闲扯一千段落......但我认为最好针对特定的主题进行不同的问题。
VIRT -- 虚拟大小 (kb) 任务正在使用的虚拟内存总量。它包括所有代码、数据和共享库以及已经被交换出去但没有被使用的页面。
SWAP -- 交换大小 (kb) 非驻留的内存,但存在于一个任务中。这是被交换出去但可能包括其他非驻留内存的内存。此列通过从虚拟内存中减去物理内存来计算。
物理内存:物理内存指计算机中的RAM或主存储器。物理内存是一种易失性内存,因此需要持续供电以保留数据。
虚拟内存是一种逻辑内存。换句话说,它是由操作系统执行的内存管理技术。虚拟内存允许程序员使用比实际可用的物理内存更多的内存来运行程序。如果物理内存为4GB,虚拟内存为16GB,则程序员可以使用这16GB虚拟内存来执行程序。使用虚拟内存,他可以执行需要大于物理内存的复杂程序。
物理内存和虚拟内存之间的主要区别在于,物理内存是指连接到主板的实际RAM,而虚拟内存是一种内存管理技术,允许用户执行大于实际物理内存的程序。
因此,任何程序通常需要从内存(物理地址)中存储/读取数据。 CPU 生成称为逻辑地址空间的地址空间,这是一组可用于访问内存中程序的地址,因为逻辑地址空间对应于实际地址。
逻辑地址空间中的地址称为逻辑地址或虚拟地址,而与它们相对应的一组地址在物理内存中被称为物理地址空间,每个地址称为物理地址,即内存中的实际地址。
注意: 内存管理单元MMU负责进行地址绑定。
来源:“操作系统概念”作者:阿伯拉罕·西尔伯沙茨(Abraham Silberschatz) (第8章内存管理策略)