虚拟内存和物理内存有什么区别?
我经常与操作系统中的虚拟化概念混淆。 考虑到RAM作为物理内存,为什么我们需要虚拟内存来执行进程?
当外部硬盘的进程(程序)被带到主内存(物理内存)执行时,这个虚拟内存在哪里?
谁负责虚拟内存以及虚拟内存的大小?
假设如果RAM的大小是4GB(即2 ^ 32-1地址空间),那么虚拟内存的大小是多少?
除此之外,虚拟内存是一种抽象,为程序员提供了在系统上拥有无限可用内存的错觉。
虚拟内存映射被制作成对应于实际的物理地址。 操作系统创建并处理这些映射 - 利用页表和其他数据结构来维护映射。 虚拟内存映射总是在页表或一些类似的数据结构中找到(对于虚拟内存的其他实现,我们可能不应该称之为“页表”)。 页表也在物理内存中 - 通常在用户程序无法写入的内核保留空间中。
虚拟内存通常比物理内存大 - 如果虚拟内存和物理内存大小相同,虚拟内存映射没有太多的理由。
通常只有程序所需的部分驻留在内存中,这是一个称为“分页”的主题。 虚拟内存和分页紧密相关,但不是同一个主题。 还有其他虚拟内存的实现,例如分割。
我可能会在这里假设错误,但我敢打赌,你发现很难绕开你的头脑的东西必须与特定的虚拟内存实现,最有可能分页。 没有办法做分页 - 有很多实现,你的教科书描述的实现可能与在Linux / Windows等实际操作系统中出现的实现不一样 - 可能存在细微的差异。
我可以说一千个关于分页的段落......但我认为最好留给专门针对该主题的另一个问题。
我无耻地从顶部的man page复制摘录
VIRT - 虚拟映像(kb)该任务使用的虚拟内存总量。 它包括所有代码,数据和共享库以及已被换出的页面和已被映射但未被使用的页面。
SWAP - 交换大小(kb)不驻留但存在于任务中的内存。 这是已换出的内存,但可能包含其他非常驻内存。 此列是通过从虚拟内存中减去物理内存来计算的
软件在一个非常简单的前提下在操作系统上运行 - 它们需要内存。 设备OS以RAM的形式提供它。 所需的内存量可能会有所不同 - 有些软件需要大量内存,有些则需要微不足道的内存。 大多数(如果不是全部的话)用户同时在OS上运行多个应用程序,并且考虑到内存昂贵(并且设备大小有限),可用内存量总是有限的。 因此,鉴于所有软件都需要一定数量的RAM,并且所有软件都可以同时运行,所以OS需要处理两件事:
现在主要的问题归结为内存如何管理。 究竟什么决定了内存中哪些属于给定软件的数据?
可能的解决方案1 :让各个软件明确指定它们将在设备中使用的内存地址。 假设Photoshop声明它总是使用范围从0
到1023
存储器地址(设想存储器是一个线性字节数组,因此第一个字节位于位置0
,第1024
个字节位于位置1023
) - 即占用1 GB
内存。 同样, VLC声明它将占用1244
到1876
内存范围等
优点:
缺点:
这不会缩放。 理论上,应用程序在执行真正重要的任务时可能需要大量内存。 因此为了确保它永远不会耗尽内存,分配给它的内存区域必须总是大于或等于该内存量。 如果一台软件的最大理论内存使用量是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会认为它是以前存储的数据相同的数据? 你可以想象,坏事会发生。
很明显,正如你所看到的,这个想法很幼稚。
可能的解决方案2 :让我们尝试另一种方案 - 其中操作系统将执行大部分内存管理。 只要软件需要任何内存,软件只会请求操作系统,操作系统将相应地调整。 说操作系统确保每当一个新的进程请求内存时,它将从可能的最低字节地址分配内存(如前所述,RAM可以想象成线性字节阵列,因此对于4 GB
RAM,地址范围对于从0
到2^32-1
一个字节),如果进程正在启动,否则如果它是一个正在运行的请求内存的进程,它将从该进程仍然驻留的最后一个内存位置分配。 由于软件将发射地址而不考虑存储数据的实际内存地址,所以操作系统必须保持软件映射到实际物理地址的每个软件的映射(注意:这就是我们将这个概念称为Virtual Memory
的两个原因之一:软件不关心存储数据的真实存储器地址,它们只是随时吐出地址,操作系统找到合适的地方来适应它,如果需要,稍后找到它)。
假设设备已经打开,OS刚刚启动,现在没有其他进程在运行(忽略操作系统,这也是一个进程!),并且您决定启动VLC 。 所以VLC从最低字节地址分配一部分RAM。 好。 现在,当视频正在运行时,您需要启动浏览器才能查看某个网页。 然后你需要启动记事本来涂抹一些文字。 然后Eclipse做一些编码..很快你的4 GB
内存全部用完,RAM看起来像这样:
问题1:现在你不能启动任何其他进程,因为所有的RAM都用完了。 因此必须编写程序,记住可用的最大内存(实际上更少可用,因为其他软件也将并行运行!)。 换句话说,你不能在你的摇摇晃晃的1 GB
PC上运行一个耗费大量内存的应用程序。
好的,现在你决定不再需要保持Eclipse和Chrome的开放,关闭它们以释放一些内存。 这些进程在RAM中占用的空间被OS收回,现在看起来像这样:
假设这两个空间释放了700 MB
空间 - ( 400
+ 300
)MB。 现在您需要启动Opera ,这将占用450 MB
空间。 那么,总共有450 MB
空间可用,但是......它不是连续的,它被分成单独的块,其中没有一个足够大以容纳450 MB
。 所以,你碰到一个好主意,让我们把下面的所有过程移动到尽可能高的地方,这将在底部的一个块中留下700 MB
空白空间。 这就是所谓的compaction
。 伟大的,除了...所有的进程正在运行。 移动它们将意味着移动它们所有内容的地址(请记住,操作系统维护软件将内存吐出到实际内存地址的映射,想象一下软件已经将数据123
吐出了一个45
的地址,操作系统已经存储了它在2012
年的位置,并在地图上创建了一个条目,映射45
到2012
。如果软件现在移到内存中,原来2012
位置将不再是2012
,而是位于新位置,操作系统必须更新该映射相应地映射45
到新地址,以便软件在查询存储位置45
时可以得到期望的数据( 123
)。就软件而言,它知道的是,地址45
包含数据123
!)! 设想一个引用局部变量i
。 当它再次被访问时,它的地址已经改变,并且不能再找到它了。 对于所有的函数,对象,变量都是一样的,基本上每个东西都有一个地址,移动一个进程将意味着改变它们全部的地址。 这导致我们:
问题2:你不能移动一个进程。 该进程中所有变量,函数和对象的值在编译期间被编译器吐出的硬编码值,该进程取决于它们在其生命周期中位于相同位置,并且改变它们是昂贵的。 结果,流程在退出时会留下巨大的“ holes
”。 这称为External Fragmentation
。
精细。 不知怎的,以某种奇迹般的方式,你确实设法推动了这个过程。 现在底部有700 MB
的可用空间:
歌剧顺利融入底部。 现在你的RAM看起来像这样:
好。 一切都很好看。 但是,剩下的空间并不多,现在您需要再次启动Chrome ,这是一种已知的记忆猪! 它需要大量的内存才能启动,而且几乎没有任何剩余的空间......除了......您现在注意到,一些最初占用大空间的进程现在不需要太多空间。 可能你已经停止了VLC中的视频,因此它仍然占据一些空间,但运行高分辨率视频时并不需要它。 类似的记事本和照片 。 你的RAM现在看起来像这样:
Holes
,再次! 回到原点! 除此之外,由于进程终止而出现空洞,现在这是由于进程需要比以前更少的空间! 而且你又遇到了同样的问题,这些holes
合并在一起会产生比所需要的更多的空间,但是它们散布在周围,而不是孤立地使用太多。 因此,您必须再次移动这些流程,这是一项昂贵的操作,而且是非常频繁的流程,因为流程在整个生命周期内的大小往往会缩小。
问题3:在整个生命周期中,过程可能会减小尺寸,留下未使用的空间,如果需要使用,将需要昂贵的移动许多过程的操作。 这被称为Internal Fragmentation
。
很好,现在,你的操作系统完成所需的任务,移动进程并启动Chrome ,一段时间后,你的RAM如下所示:
凉。 现在假设你再次在VLC中继续观看阿凡达。 它的内存需求将会激增! 但是......没有空间让它成长,因为记事本蜷缩在它的底部。 所以,再次,所有的过程必须低于VLC ,直到VLC找到足够的空间!
问题4:如果流程需要增长,这将是一项非常昂贵的操作
精细。 现在假设照片被用来从外部硬盘加载一些照片。 访问硬盘可以让你从缓存和RAM的领域到磁盘的速度,这个速度按照数量级递减。 痛苦地,不可逆转地,超越地慢。 这是一个I / O操作,这意味着它不受CPU限制(它恰恰相反),这意味着它现在不需要占用RAM。 但是,它仍然顽固占用RAM。 如果你想同时启动Firefox ,你不能,因为没有太多的内存可用,而如果照片在其I / O绑定活动期间内存不足,它将释放大量内存,然后是(昂贵的)压缩,然后是Firefox装入。
问题5:I / O绑定作业继续占用RAM,导致RAM的利用率不足,同时CPU绑定作业可能使用RAM。
所以,正如我们所看到的,即使使用虚拟内存方法,我们也有很多问题。
有两种方法来解决这些问题 - paging
和segmentation
。 让我们讨论paging
。 在这种方法中,进程的虚拟地址空间以块为单位映射到物理内存 - 称为pages
。 一个典型的page
大小是4 kB
。 这个映射是由一个叫做page table
的东西维护的,给定一个虚拟地址,现在我们要做的就是找出该地址属于哪个page
,然后从page table
中找到page
在实际物理内存中的相应位置(称为frame
),并且假定page
虚拟地址的偏移量与page
以及frame
相同,则通过将该偏移量添加到page table
返回的地址来找出实际地址。 例如:
左边是进程的虚拟地址空间。 假设虚拟地址空间需要40个单元的内存。 如果物理地址空间(右侧)也有40个内存单元,则可以将所有位置从左侧映射到右侧的位置,我们会非常高兴。 但是,如果运气不好,不仅物理内存可用(此处为24个)内存单元可用,它还必须在多个进程之间共享! 好吧,让我们看看我们如何处理它。
当进程开始时,例如对位置35
进行内存访问请求。 这里页面大小为8
(每个page
包含8
位置, 40
位置的整个虚拟地址空间因此包含5
页面)。 所以这个位置属于第页。 4
( 35/8
)。 在此page
,此位置的偏移量为5
( 35%8
)。 所以这个位置可以由元组(pageIndex, offset)
= (4,3)
。 这只是开始,所以没有任何过程的一部分存储在实际的物理内存中。 因此,保持左侧页面到右侧实际页面(它们被称为frames
)的映射的page table
当前是空的。 所以操作系统放弃CPU,让设备驱动程序访问磁盘并获取页面号。 4
(这个过程基本上是从磁盘上程序的内存块,其地址范围从32
到39
)。 当它到达时,OS在RAM中的某个地方分配页面,比如第一帧本身,并且此过程的page table
需要注意,页面4
映射到RAM中的第0
帧。 现在数据终于出现在物理内存中。 OS再次查询页面表中的元组(4,3)
,并且这次,页面表示页面4
已经映射到RAM中的帧0
。 因此,操作系统只是进入RAM中的第0
帧,访问该帧中偏移量为3
的数据(花点时间理解这一点,整个从磁盘取出的page
移动到frame
,因此无论偏移量一个页面中的单个内存位置是,它在框架中也是一样的,因为在page
/ frame
,内存单元仍然驻留在相同的位置!),并返回数据! 由于数据在第一次查询时并未在内存中找到,而是必须从磁盘中取出才能加载到内存中,所以它构成了一个未命中 。
精细。 现在假设,位置28
的存储器访问被创建。 它归结为(3,4)
。 Page table
在只有一个条目,将第4
页映射到第0
帧。 所以这又是一次错过 ,这个过程放弃了CPU,设备驱动程序从磁盘获取页面,进程再次重新获得对CPU的控制,并且其page table
被更新。 现在说第3
页被映射到RAM中的第1
帧。 因此(3,4)
变成(1,4)
,并且RAM中该位置处的数据被返回。 好。 以这种方式,假设下一个存储器访问是针对位置8
,其转换为(1,0)
。 页面1
还没有在内存中,重复相同的过程, page
被分配到RAM中的page
2
帧。 现在RAM进程映射看起来像上面的图片。 此时,只有24个可用内存单元的RAM被填满。 假设此过程的下一个内存访问请求来自地址30
。 它映射到(3,6)
,并且page table
表示页面3
在RAM中,并且映射到帧1
。 好极了! 所以数据从RAM位置(1,6)
获取并返回。 这构成了一个冲击 ,因为所需的数据可以直接从RAM获得,因此速度非常快。 同样,未来几年的访问请求,说的位置11
, 32
, 26
, 27
都是命中 ,由进程请求,即数据被直接发现在RAM中,而无需到别处。
现在假设位置3
的内存访问请求来了。 它转化为(0,3)
以及page table
的过程中,目前有3项,换页1
, 3
和4
说,这个页面是不是在内存中。 像以前的情况一样,它是从磁盘中获取的,但是,与以前的情况不同,RAM已经被填满了! 那么现在该怎么做? 这就是虚拟内存的美妙之处,内存中的一帧被驱逐! (各种因素决定了哪一个帧将被驱逐,它可能是基于LRU
的,其中一个进程最近最少访问的帧将被驱逐,它可能是first-cum-first-evicted
基础,其中分配的帧最长时间之前,被驱逐等)。因此,一些框架被驱逐。 说第1帧(只是随机选择它)。 但是,该frame
映射到某个page
! (目前,它被映射到我们从第4
页开始的唯一一个进程的页表)。 所以这个过程必须被告知这个悲惨的消息,一个不幸属于你的frame
将被从RAM中驱逐出去,为另一个pages
腾出空间。 该过程必须确保它使用该信息更新其page table
,即删除该页框架二进制的条目,以便下一次对该page
发出请求时,它正确地告诉过程该page
是不再在内存中,并且必须从磁盘获取。 好。 因此,第1
帧被逐出,第0
页被引入并放置在RAM中,并且第4
页的条目被移除,并被第0
页映射替换为同一帧1
。 所以现在我们的映射看起来像这样(注意右边第二frame
的颜色变化):
看到发生了什么? 这个过程需要增长,它需要比可用RAM更多的空间,但与我们之前的场景不同,RAM中的每个进程都必须移动以适应不断增长的进程,而这只是一个page
替换! 这是因为一个进程的内存不再需要是连续的,它可以驻留在不同的地方,操作系统保存关于它们的位置的信息,并且当需要时,它们被适当地查询。 注意:你可能会想,呃,如果大部分时间都是未miss
,数据必须不断从磁盘加载到内存中呢? 是的,从理论上讲,这是可能的,但是大多数编译器都是按照locality of reference
方式设计locality of reference
,即如果使用来自某个存储位置的数据,则需要的下一个数据将位于非常接近的位置,可能来自同一page
,刚刚加载到内存中的page
。 因此,下一次错过会在相当一段时间后发生,大多数即将到来的内存需求将由刚刚带入的页面或最近使用的内存中的页面满足。 完全相同的原则也允许我们驱逐最近最少使用的page
,并且一段时间内没有使用的逻辑也不太可能在一段时间内被使用。 然而,情况并非总是如此,在特殊情况下,是的,表现可能会受损。 更多关于它以后。
问题4的解决方案:如果面临空间问题,流程现在可以轻松增长,只需要做一个简单的page
替换,而无需移动任何其他流程。
问题1的解决方案:进程可以访问无限制的内存。 当需要更多的内存时,磁盘被用作备份,所需的新数据从磁盘加载到内存中,并且最近最少使用的数据frame
(或page
)被移动到磁盘。 这可以无限地进行下去,而且由于磁盘空间便宜且几乎没有限制,因此会产生无限的内存错觉。 Virtual Memory
这个名字的另一个原因是,它给了你一种真正不可用的内存错觉!
凉。 早些时候,我们面临的一个问题是,尽管过程缩小了,但其他过程难以回收空间(因为这需要高成本的压缩)。 现在很容易,当一个进程变得更小时,很多pages
不再使用,所以当其他进程需要更多的内存时,一个简单的基于LRU
的驱逐会自动将这些用量较少的pages
从RAM中移走,并将其替换为来自其他进程的新页面(当然更新所有这些进程的page tables
以及现在需要更少空间的原始进程),所有这些都不需要任何昂贵的压缩操作!
问题3的解决方案:每当进程减小时,其RAM中的frames
将被较少使用,因此基于LRU
的简单驱逐可将这些页面逐出并用新进程所需的pages
替换它们,从而避免不需要compaction
Internal Fragmentation
compaction
。
至于问题2,花一点时间来了解这一点,该场景本身已被彻底删除! 没有必要移动一个进程来适应新的进程,因为现在整个进程不需要立刻适应,只有特定的页面需要适应临时特性,这是通过从RAM中删除frames
来实现的。 一切都以pages
为单位发生,因此现在没有hole
概念,因此不存在任何移动的问题! 由于这一新要求,可能需要移动10 pages
,有数千pages
原封未动。 而早些时候,所有进程(每一个进程)都必须移动!
问题2的解决方案:为了适应新的过程,只需要根据需要逐出其他过程中最近使用过的部分的数据,并且这种情况发生在固定大小的称为pages
单元中。 因此这个系统不存在hole
或External Fragmentation
可能性。
现在当进程需要做一些I / O操作时,它可以轻松放弃CPU! 操作系统只是从RAM中逐出它的所有pages
(也许将其存储在某个缓存中),同时新进程占用RAM。 当I / O操作完成后,操作系统会简单地将这些pages
恢复到RAM中(当然,通过替换其他进程中的pages
,可能来自替换原始进程的页面,或者可能来自某些本身需要执行的操作现在I / O,因此可以放弃记忆!)
问题5的解决方案:当进程正在进行I / O操作时,它可以轻松放弃RAM使用,这可以被其他进程使用。 这导致RAM的正确使用。
当然,现在还没有进程直接访问RAM。 每个进程都访问一个虚拟内存位置,该位置映射到物理RAM地址并由该进程的page-table
维护。 映射是OS支持的,操作系统让流程知道哪个框架是空的,这样就可以在那里安装一个过程的新页面。 由于这种内存分配是由操作系统本身监督的,所以它可以很容易地确保没有进程通过仅分配来自RAM的空帧或者侵占RAM中另一进程的内容来侵占另一进程的内容,与进程通信更新它的page-table
。
对原始问题的解决方案:由于整个分配由操作系统本身管理,并且每个进程都在自己的沙盒虚拟地址空间中运行,所以进程访问另一个进程的内容是不可能的。
因此, paging
(以及其他技术)与虚拟内存一起,是当今运行在OS-es上的软件的强大之处! 这使软件开发人员不必担心用户设备上有多少内存可用,数据存储在何处,如何防止其他进程破坏他们的软件数据等。但是,这当然不是完整的。 有缺陷:
Paging
最终会给用户带来无限内存的幻觉。 从辅助存储中检索数据以适应内存(称为page swap
,以及在RAM中找不到所需页面的事件被称为page fault
)是昂贵的,因为它是IO操作。 这减缓了这个过程。 几次这样的页面交换连续发生,并且该过程变得非常缓慢。 曾见过你的软件运行良好,并且突然变得很慢,几乎挂起,或者让你没有选择重启它? 可能太多的页面交换正在发生,使其变慢(称为thrashing
)。 所以回到OP,
为什么我们需要虚拟内存来执行进程? - 正如答案最后解释的那样,为软件给出设备/操作系统具有无限内存的错觉,以便任何软件(无论大小)都可以运行,而不必担心内存分配或其他过程破坏其数据,即使在并行运行。 这是一个概念,通过各种技术在实践中实施,其中之一,如此处所述,是Paging 。 它也可能是分段 。
当外部硬盘驱动器的进程(程序)被带到主内存(物理内存)执行时,这个虚拟内存在哪里? - 虚拟内存本身并不存在,它是抽象的,总是存在的,当软件/进程/程序被引导时,为它创建一个新的页表,并且它包含从那个地址吐出来的映射处理到RAM中的实际物理地址。 由于进程吐出的地址不是真实地址,从某种意义上说,它们实际上就是你可以说the virtual memory
。
谁负责虚拟内存以及虚拟内存的大小? - 它是由操作系统和软件共同处理的。 想象一下你的代码中的一个函数(它最终编译并生成了产生进程的可执行文件),它包含一个局部变量 - 一个int i
。 当代码执行时, i
在函数的堆栈中获得一个内存地址。 该函数本身存储为其他地方的对象。 这些地址是编译器生成的(将你的代码编译成可执行文件的编译器) - 虚拟地址。 在执行时, i
必须至少驻留在实际物理地址的某个地方(至少它是一个静态变量!),因此OS将编译器生成的虚拟地址i
映射到实际的物理地址,以便每当该函数,某些代码需要i
的值,该进程可以查询该虚拟地址的操作系统,然后OS可以查询存储的值的物理地址,并将其返回。
假设如果RAM的大小是4GB(即2 ^ 32-1地址空间),那么虚拟内存的大小是多少? - RAM的大小与虚拟内存的大小无关,取决于操作系统。 例如,在32位Windows上,它是16 TB
,在64位Windows上是256 TB
。 当然,它也受到磁盘大小的限制,因为这是备份内存的地方。
上一篇: What are the differences between virtual memory and physical memory?