【问题标题】:Are Arrays Contiguous? (Virtual vs Physical)数组是连续的吗? (虚拟与物理)
【发布时间】:2021-11-30 18:25:12
【问题描述】:

我读到数组在虚拟内存中是连续的,但可能不在物理内存中,我不明白。

假设我有一个大小为 4KB 的数组(一页 = 一帧大小),在虚拟内存中该数组为一页。

在虚拟内存中,每一页都被翻译成一帧,所以我们的数组仍然是连续的......

(在页表中,我们将页面转换为帧,而不是每个字节都转换为自己的帧...)


附带问题:(在回答这个问题时,请清楚地提及它是为了附带说明):

当在虚拟内存中分配一页大小的数组时,它必须是一页还是可以在虚拟内存中分成两个连续的页面(例如第一个的下半部分和第二个的上半部分)?在这种情况下,最坏的情况是上面的答案是 2,我错了吗?

【问题讨论】:

  • 您已经给出了一页的示例,因此在这种情况下,它也是物理内存的一页,因此默认情况下是连续的。但是如果数组不止一个页面,那么它将在虚拟内存中是连续的,但每个虚拟页面都可以映射到任何不连续的物理页面集。
  • 语句“数组在虚拟内存中是连续的,但可能不在物理内存中”适用于大型数组,即多页大小的数组。
  • @user3386109 也可以应用于小型数组。如果数组从一页的末尾附近开始,它可能会跨越到下一页。
  • 是的@Barmar,但引用中的词 "probably" 不适用于可能不会跨越页面边界的小数组.我认为 OP 已经断章取义了。
  • 在典型系统上,除非您正在编写操作系统,否则您的程序永远不会看到任何物理内存。您关心的所有内存都是虚拟的。

标签: arrays c memory memory-management virtual-memory


【解决方案1】:

我读到数组在虚拟内存中是连续的,但可能不在物理内存中,我不明白。

这句话遗漏了一些非常重要的东西。 数组大小

对于小型数组,该语句是错误的。对于“大/巨大”数组,该语句是正确的。

换句话说:一个数组被分割成多个非连续物理页的概率是数组大小的函数。

对于小数组,概率接近于零,但概率随着数组大小的增加而增加。当数组大小增加到超过系统页面大小时,概率越来越接近 1。但是需要多个页面的数组在物理内存中可能仍然是连续的。

关于你的问题:

如果数组大小等于您的系统页面大小,则该数组最多可以跨越两个物理页面。

【讨论】:

  • 当数组大小超过系统页面大小时,概率为 1。 并非如此。概率随着数组大小而增加,但1 非常悲观:典型的malloc 实现将虚拟内存页面映射为 64K、128K 甚至更大的块,并且许多操作系统尝试将页面映射到连续的物理内存中,因此概率仍远未达到 1,但取决于操作系统风格和版本、页面大小、分配器实现和配置、物理内存量、系统负载、正常运行时间......
  • @chqrlie 同意。我的措辞相当糟糕。进行了更新。谢谢。
【解决方案2】:

任何大于页面大小的(数组、结构、...)都必须拆分为多个页面;因此可能是“虚拟连续的,物理上不连续的”。

没有进一步的知识或限制;在其最小对齐(例如,uint32_t 数组的 4 个字节)和页面大小之间的任何内容(数组、结构、...)都有可能被拆分为多个页面;其中概率取决于其大小和对齐方式。例如,如果页面大小为 4096 字节,并且数组的最小对齐为 4 字节且大小为 4092 字节,那么 1024 中有 2 次机会最终会出现在单个页面上(并且有 99.8% 的机会会分成多个页面)。

大小等于其最小对齐的任何内容(变量、微型数组、微型结构......)都不会(不应该 - 参见注释 3)被拆分到多个页面中。

注 1:对于任何使用从堆分配的内存的东西,可以假定最小对齐是堆提供的(实现定义的)最小对齐,而不是对象本身的最小对齐。例如。对于uint16_t 的数组,最小对齐为 2 个字节;但是malloc() 将返回对齐更大的内存(可能是 16 个字节)

注意 2:当事物是嵌套的(例如,数组在一个结构中的另一个结构中)时,上述所有内容仅适用于外部结构。例如。如果你在一个结构中有一个uint16_t 数组,而该数组恰好从结构中的偏移量 4094 开始;那么数组被跨页拆分的可能性会大大增加。

注意 3:可以使用指针显式破坏最小对齐(例如,使用 malloc() 分配 1024 字节,然后创建一个指向从分配区域内您想要的任何偏移量开始的数组的指针)。

注 4:如果某些内容(数组、结构、...)被拆分为多个页面;那么它有可能在物理上仍然是连续的。在最坏的情况下,这取决于物理内存的数量(例如,如果计算机有 1 GiB 的可用物理内存和 4096 字节的页面,那么在 262000 中大约有 1 个机会,2 个虚拟连续的页面将“意外地物理上连续”)。如果操作系统实现页面/缓存着色(请参阅https://en.wikipedia.org/wiki/Cache_coloring),它会通过页面/缓存“颜色”的数量来提高“物理连续”的概率(例如,如果计算机有 1 GiB 的可用物理内存和 4096 字节页面,并且操作系统使用 256 种页面/缓存颜色,那么在 1024 中大约有 1 个机会 2 个虚拟连续的页面将“意外地物理上连续”)。

注 5:大多数现代操作系统使用多种页面大小(例如 4 KiB 页面和 2 MiB 页面,也可能还有 1 GiB 页面)。如果您假设使用了最小的页面大小,这可能会导致难以猜测页面大小实际上是多少,或者会提高“意外物理上连续”的可能性。

注 6:对于某些 CPU(例如最近的 AMD/Zen),当且仅当页表条目兼容时,TLB 的行为就好像页面更大(例如,好像您使用的是 16 KiB 页面而不是 4 KiB 页面) (例如,如果 4 个页表条目描述了具有相同权限/属性的四个物理上连续的 4 KiB 页)。如果操作系统针对这些 CPU 进行了优化,结果类似于具有额外的页面大小(4 KiB、“16 KiB”、2 MiB 和可能 1 GiB)。

在大小为一页的虚拟内存中分配数组时,它必须是一页还是可以在虚拟内存中分成两个连续的页面(例如第一个的下半部分和第二个的上半部分)?

在堆内存中分配一页大小的数组时;最小对齐将是堆管理器/malloc() 提供的实现定义的最小对齐(例如,可能是 16 个字节)。然而;当分配的内存量“足够大”时,大多数现代堆管理器会切换到使用替代方案(例如mmap()VirtualAlloc() 或类似);所以(取决于实现和他们对“足够大”的定义)它可能是页面对齐的。

在原始虚拟内存中分配数组时(例如,使用mmap()VirtualAlloc() 或您自己类似的,而不使用堆而不使用malloc() 之类的东西);页面对齐是有保证的(主要是因为虚拟内存管理器不处理任何更小的东西)。

【讨论】:

    【解决方案3】:

    除非数组的开头恰好与一个内存页的开头对齐,否则它仍然可以占用两页;它可以在接近一页的末尾开始并在下一页结束。在堆栈上分配的数组可能不会被强制占用单个页面,因为堆栈帧只是在堆栈内存中按顺序分配,并且数组通常在每个堆栈帧内处于相同的偏移量。

    堆内存分配器 (malloc()) 可以 尝试确保小于页面的数组将完全分配在同一页面上,但我不确定这是否真的大多数分配器是如何实现的。这样做可能会增加内存碎片。

    【讨论】:

    • "它仍然可以占据多个页面" 为什么?最多 2 页(如果数组大小为 1 页)我是吗?
    • 2页是多页
    • 但是说“多页”并不准确,因为它不可能占用 3 个或更多...
    • 确实,我把答案改成了2。
    猜你喜欢
    • 2014-03-28
    • 1970-01-01
    • 2011-10-06
    • 2013-05-05
    • 1970-01-01
    • 1970-01-01
    • 2021-09-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多