理解虚拟地址、虚拟内存和分页

48

我学习了这些主题并阅读了许多文章和书籍,但它们都缺少一些补充信息,使我更加困惑。因此,在这里,我想解释一下我所知道的内容,并提出我的问题。希望这个主题对像我一样的许多人有用。如果需要,我也想学习我的知识是否正确以及进行纠正。

虚拟内存

有些文章说,“虚拟内存是硬盘上模拟物理内存的一些空间,这样我们就可以拥有比实际内存更多的内存。”。其他一些文章说:“虚拟内存是物理内存(RAM)与一个类似于物理内存的硬盘区域和页表的组合。”然而它们是不同的东西,我不明白为什么会有不同的解释。

让我们采用第二个解释,因为这也是维基百科描述虚拟内存的方式。这时,虚拟地址就有意义了,因为我们使用虚拟内存中的地址而不是直接使用物理内存。

顺便说一句,我的 Mac 说我有8GB物理内存和8GB虚拟内存。在这种情况下,虚拟内存是否包括物理内存,还是它是用作内存的硬盘空间的数量?我的程序可以使用16GB内存吗?

enter image description here

问题1:

Intel i5有36位地址总线,这意味着您可以寻址64GB内存。假设我在计算机上安装了4GB RAM。然而,我的程序可能不知道安装的内存大小,因为它将在许多不同大小的内存系统上使用。这就是虚拟内存变得方便的地方。它抽象掉了已安装内存的实际大小。

但是,当我的程序想要访问内存地址0xFFFFFFFFF时会发生什么?我只安装了4GB,也许一些内存空间在硬盘上。

我对这个问题有两个理论:

1. 由于页面表是由操作系统维护的,因此操作系统解码该地址并找出哪个页面,并在页面表中检查是否与其关联的物理地址(有效和无效标志),如果存在,则进入页面条目在虚拟地址中定义的偏移量指向的物理内存中并获取该值。否则,会发生页错误,操作系统会在次要存储器中寻找该页面,将其提取并将其放入内存中,然后更新页面表。

2. 它会抛出一种OutOfMemory类型的异常,指出我没有任何内存可以访问给定的地址。

第一个理论的缺点是当程序想使用64GB的内存时会发生什么?然后我们需要在硬盘上拥有60GB的内存空间,因为我们只有4GB。但是,在下面的截图中,MAC告诉我只有8GB的虚拟内存。

问题2:

如何将进程放入虚拟内存?我的意思是每个进程对于它们可用的0x0-0xFFFFFFFFF虚拟内存空间吗?还是只有一个虚拟内存地址空间,所有进程都放置在其中?

如果每个进程都假定它们拥有可用的所有内存,则内存看起来像下面这样:

enter image description here

如果只有一个虚拟内存概念,则它看起来像这样:

enter image description here

页面表

因此,页面表是位于物理地址和虚拟地址之间的数据结构。它是一个关联数组(或类似字典),其中对于每个页面(键),有一个与之关联的物理地址(值)。

操作系统使用MMU(内存管理单元)执行从虚拟地址到物理地址的转换。

enter image description here

问题3:

“有一个包含所有进程页面的大型页面表,还是每个进程都有自己的页面表?”

分页技术

分页技术是一种内存管理方法。内存管理单元将虚拟内存和物理内存划分为大小固定的块-页面。当你在内存和辅助存储器之间交换页面时,这种技术非常有用。例如,你的程序请求位于某个地址上的数据。但是,你的程序正在使用的地址是虚拟地址,并且内存管理单元使用页面表进行翻译。在此过程中,内存管理单元检查页面表是否存在所请求的地址,并且如果不存在,则从辅助存储器获取并更新页面表。

问题4:

假设一个进程请求从某个地址获取数据,该地址被转换为已经存在一些数据的物理地址。如何确定该数据不属于请求者进程并应被替换为辅助存储器中的数据?

例如,有一个脏位用于确定是否将该页面写回硬盘,但我不认为它能确定所属进程。

2个回答

21

在我回答你的问题之前(希望我能够回答),以下是一些简要说明:

说明

问题在于,“虚拟内存”有两种含义。低级程序员使用的技术术语“虚拟内存”与向消费者解释的“虚拟内存”(几乎)没有任何关系。

在技术意义上,“虚拟内存”是一种内存管理系统,每个进程都有自己的虚拟地址空间,该地址空间中的内存地址通过具有硬件支持的操作系统内核映射到物理内存地址(使用诸如TLB、多级页表、页面故障和遍历等术语)。这就是您感兴趣的 VM 概念(下面描述)。

在非技术意义上,“虚拟内存”是用作 RAM 替代的磁盘空间(使用诸如交换、后备存储等术语)。这是您不太感兴趣的 VM 概念,但似乎您已经看到了一些主要涉及此概念或混淆了两个概念的材料。

问题1

当我的程序想访问内存地址0xFFFFFFFFF时会发生什么?我只有4GB

在这种情况下,您的“理论1”更接近。

虚拟机(VM)将程序看到和处理的地址——虚拟地址——与物理地址分离开来。你的4GiB内存可能位于0x0到0xFFFFFFFF(8个F)的物理地址上,但地址0xFFFFFFFFF(9个F)在用户空间(规范化布局中)的虚拟地址中。假设0xFFFFFFFFF在进程分配的块中,CPU和内核(协同工作)将把页面地址0xFFFFFF000(假设使用4k页面,则我们切掉低12位)翻译成一个实际的物理页面,该页面可以具有(几乎)任何物理基地址。假设该页面的物理地址为0xeac000(当内核给出虚拟页面0xFFFFFF000时建立的关系),那么在虚拟地址0xFFFFFFFFF处的字节位于物理地址0x00eacfff。

当你取出0xFFFFFFFFF(假设是4k页面),内核“请求”CPU访问该虚拟地址,CPU截掉较低的12位,在dTLB中查找页面(转换旁路缓存是虚拟到物理页面映射缓存;数据缓存和指令缓存至少有一个)。如果命中,则CPU构造真正的物理地址并获取值。如果TLB未命中,则CPU引发页错误,导致内核查询(或“走”)页表以确定正确的物理页,并将该值“返回”给CPU,CPU将其缓存在dTLB中(很可能会立即被重新使用)。然后内核再次请求CPU该地址,这一次,它将成功而不会触发走操作。

每个进程都有自己完全独立的虚拟内存空间,这几乎是虚拟内存存在的全部意义。

在早期,TLB在任何情况下都不是“进程感知”的。每次上下文切换都意味着TLB必须完全清除。现在,TLB条目具有短的“进程上下文”(PCID?)字段并支持选择性刷新,因此您可以有点 / 有点地认为它将PID哈希后与虚拟页面地址相加,因此TLB更加“进程感知”,并且仅在PCID与另一个进程发生碰撞时才需要刷新这些条目(两个进程映射到相同的PCID)。

每个进程都有自己的页表。

这当然是与操作系统有关的,我的理解是,Linux有一个多级页面表集,在其中条目(PTEs)带有PID标记,而不是每个进程都有单独的页面表。我认为基本原因是许多虚拟到物理映射是n:1而不是1:1,因为如果它们全部都是1:1,则会很大程度上破坏VM的主要目的:想想包含类似libc库指令的共享只读页面,或者在fork之后父子进程之间共享的写时复制数据页面。将这些条目在每个进程的单独页面表中复制是比在创建/退出进程时向公共页面表添加/删除进程特定条目更低效的。

磁盘作用的介入

一旦您拥有VM系统,就几乎可以轻松地添加从磁盘检索页面的能力,并对PTE实施“老化”,以便最近很少使用的页面可以放置在磁盘上。虽然这在内存受限系统上是一个重要的功能,但在理解VM系统实际工作方式方面几乎没有任何相关性。


17
有些人将“虚拟内存”一词视为“页面文件”的同义词,因为页面文件代表您分配的不是“真实”内存(即RAM)的部分。但大多数人认为“虚拟内存”是操作系统提供给程序的整个抽象层,它结合了RAM和页面文件。
我不确定Mac OS更倾向于这些定义中的哪一个,但似乎不太可能您的计算机没有任何分页内存分配,所以我猜它可能会为您的8GB实际RAM添加8GB的分页内存,总共可用16GB(虚拟)内存。
请记住,由于操作系统管理内存分配和释放请求,因此它可以自由地做几乎任何想做的事情。我理解大多数操作系统都为每个进程设置了不同的内存分配表,因此它们可以将相同的虚拟内存地址分配给多个程序,但是这些内存地址将映射到不同的实际内存块。因此,64位操作系统可以为多个32位程序分配最大数量的32位地址-它们并不都限于相同的32位内存地址。
但是,还有一些限制:操作系统可以设置页面文件允许增长的大小限制。因此,除非您已经明确告诉操作系统这样做,否则它可能不会拥有64GB的总虚拟内存。即使如此,它也无法将所有64 GB分配给每个程序,因此在操作系统将虚拟地址分配给您的程序之前,您很可能会遇到“Out of Memory”错误。 (实际上,我不会感到惊讶,如果了解到“0xFFFFFFFFF”实际上是一个保留的错误代码位置,类似于“0x0”。)但由于您的程序知道的地址与真实内存地址没有关联,所以存在这样的可能性,即您最终被分配一个程序认为是“0xFFFFFFFFF”的内存地址,即使操作系统没有使用那么多内存。

每个进程都有自己的私有内存表,操作系统会有效地防止程序访问未被分配到该表中的内存地址。此外,还有共享内存的存在,两个需要使用相同信息的进程可以创建一个共享内存区域,该内存空间中的地址可由两者访问。操作系统本身也需要跟踪总体内存的可用性、哪些地址空间已被使用、已经将哪些虚拟内存块分配给RAM或页面文件中的哪些位置。

因此,假设某一进程被分配了地址为0x00000002的内存,当它尝试从此内存地址读取值时,操作系统可能会意识到实际上它对应的是真正的内存地址0x00000F23,这就是实际取出CPU寄存器中的值的内存地址。或者,它可能会意识到它已经将包含该地址的页移到了磁盘上,这种情况下,操作系统会找到一块空余的内存,首先将该页数据从磁盘加载到该内存中。(同样,这个内存地址与程序请求的原始内存地址没有任何关联。)

如果没有任何空闲内存来拿出该页,则操作系统必须首先将一些数据移出内存并存到页面文件中。它会尝试智能地确定哪些内存在不久的将来最不可能被使用。但有时候,刚刚被换到磁盘上的内存常常在不久后就被请求了,以替换程序即将请求的下一个内存块。这种“抖动”是导致内存不足的计算机变得非常非常慢的原因,因为磁盘访问比内存访问慢几个数量级。


这是一个非常好的解释!谢谢。一个快速的问题,假设物理内存中没有足够的空间进行新的内存分配,在这种情况下,应该将一些页面交换回辅助存储器。在此过程中,被交换的页面可能属于其他进程(我猜测)。那么操作系统会找到该进程的页表,并将这些页面的条目更新为无效,以便为其他进程进行新的分配吗? - Tarik
@Tarik:是的,每当数据被交换时,相应的页表都会得到更新。当其他进程请求该数据时,它将被交换回内存(很可能与之前不同的位置),并且页表将再次得到更新。 - StriplingWarrior
1
你的具体示例(0x2到0xF23)表明VM映射每个字节地址,而不是处理页面。显然,使用两个64位(实际上只有48位,但仍然)地址来映射每个字节,而不是每4kB(或1MiB)页面使用两个36位页面地址,这将是荒谬的。我也非常确定VM禁止/保留了这么低的地址。 - Emmet
@Emmet:是的,谢谢你指出这一点。我有点忽视了整个页面/偏移量方面,我认为你的答案更好地解释了这一点。 - StriplingWarrior

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接