问题是,当进程使用内存地址时,它正在谈论虚拟地址,该地址可能与同一物理地址不同。这意味着两个进程可以引用相同的地址并且不会混合它们的数据,因为它将位于两个不同的物理位置。
下面我描述了典型计算机上虚拟地址如何转换为物理地址(这在其他体系结构上略有不同,但都是相同的想法)
了解 Intel x86 架构上的内存转换过程(3 级分页)
因此,一方面你有一个虚拟内存地址,并且你想获取一个物理内存地址(即:RAM 上的实际地址),工作流程主要是这样的:
虚拟地址 -> [分段单元] -> [分页单元] -> 物理地址
每个操作系统都可以定义分段单元和分页的工作方式。例如,Linux 使用平面分割模型,这意味着它被忽略,所以我们现在会做同样的事情。
现在,我们的虚拟地址通过称为分页单元的东西,并以某种方式转换为物理地址。这就是方法。
内存被分成一定大小的块,在 intel 上此大小可能是 4KB 或 4MB。
每个进程在内存中定义一组表,以便计算机知道应该如何转换内存地址。这些表以分层方式组织,实际上,您要访问的内存地址被分解在这些表的索引中。
我知道,这听起来令人困惑,但请继续听我说几句话。你可以通过这张图片来关注我的写作:
CPU内部有一个寄存器,叫做CR3它存储第一个表的基地址(我们将这个表称为页目录,它的每个条目称为页目录条目)。当一个进程正在执行时,它的CR3已加载(除其他外)。
所以,现在你想要访问,比如说,内存地址0x00C30404,
分页单元说“好吧,让我们获取页目录基址”,查看CR3寄存器并知道页目录基址在哪里,我们称这个地址为PDB(页目录基址)。
现在您想知道应该使用哪个目录条目。正如我之前所说,地址被分解为一堆索引。最高有效 10 位(位 22 到 31)对应于页目录的索引。在这种情况下,0x00C30404 is
0000 0000 1100 0011 0000 0100 0000 0100以二进制表示,其最高 10 位为:0000 0000 11这是0x3。这意味着我们要寻找第三页目录条目。
我们现在干什么?
请记住,这些表是分层的:每个表页面目录条目除其他外,还有下一个表的地址,称为页表。 (此表可能因每个页面目录条目).
现在,我们得到了另一个表。地址的接下来 10 位将告诉我们将访问该表的哪个索引(我们将其称为页表条目)。
00 0011 0000是接下来的 10 位,它们是数字:0x30。这意味着我们必须访问第 30 个页表项..
最后,这个页表项保存所需的偏移量页框(请记住,内存被划分为 4k 的块)。最后,我们地址的最低有效 12 位是该地址的内存偏移量页框,请注意页框是实际的物理内存地址。
这称为 3 级分页,在 64 位(或使用 PAE)上非常相似,但多了一层分页。
您可能认为仅仅为了获取变量而获取所有这些内存访问真是太糟糕了。这是事实。计算机中有一些机制可以避免所有这些步骤,其中之一就是TLB(Table Lookaside Buffer),它存储所有已完成翻译的缓存,因此可以轻松获取内存。
此外,这些结构的每个条目都有一些有关权限的属性,例如“此页面可写吗?” “此页面可执行吗?”。
所以,现在您了解了内存分页的工作原理,就很容易掌握 Linux 如何处理内存了:
- 每个进程都有自己的 CR3,当计划运行进程时,会加载 cr3 寄存器。
- 每个进程都有自己的虚拟空间(表可能未满,进程可以从例如 4kb 的单页开始)。
- 每个进程都映射了一些其他页面(操作系统内存),因此当它被中断时,中断处理程序会启动同一任务并处理该任务中的中断,从而执行所需的代码。
此类计划的一些动机
- 您不必同时拥有所有进程的内存,您可以将一些内存存储在磁盘中。
- 您可以安全地隔离每个进程内存,并为其授予一些权限。
- 一个进程可以使用 10MB 的 RAM,但它们不需要在物理内存中是连续的。