数组是否连续?(虚拟内存 vs 物理内存)

4

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

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

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

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


附加问题:(回答此问题时,请明确说明这是附注)

在虚拟内存中分配大小为一页的数组是否必须是一页,或者可以将其分成两个连续的页面(例如第一个页面的底部和第二个页面的顶部)? 在这种情况下,最坏的答案就是2,我错了吗?


1
您已经给出了一个页面的示例,因此在这种情况下,它也是默认连续的物理内存的一页。但如果数组超过一个页面,则在虚拟内存中它将是连续的,但每个虚拟页面可以映射到任何不连续的物理页面集合。 - kaylum
1
它也适用于小数组。如果数组开始接近一页的末尾,它可能会跨越到下一页。 - Barmar
是的@Barmar,但该引语中的“probably”一词并不适用于可能不会跨越页面边界的小数组。我认为OP将引语断章取义了。 - user3386109
当然可以。如果它在连续的虚拟内存中,怎么可能超过2页呢? - Barmar
2
在典型的系统中,除非你正在编写操作系统,否则你的程序永远看不到任何物理内存。你关心的所有内存都是虚拟的。 - n. m.
显示剩余2条评论
3个回答

4

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

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


它仍然可以占用多个页面。为什么?最多2页(如果数组大小为1页)。我错了吗? - Algo
2页是多页。 - Barmar
但是说“多个页面”并不准确,因为它无法处理3个或更多个页面... - Algo
的确,我把答案改成了2。 - Barmar

3
我读到数组在虚拟内存中是连续的,但在物理内存中可能不是连续的,我看不懂这个说法。这个说法忽略了非常重要的一点,即数组大小。对于小数组而言,这个说法是错误的;对于“大/巨大”数组而言,这个说法是正确的。换句话说,数组被分割到多个非连续物理页面的概率是数组大小的函数。对于小数组,这个概率接近于零,但随着数组大小的增加,这个概率会增加。当数组大小增加到系统页面大小以上时,这个概率会越来越接近1。但需要多页才能容纳的数组仍可能在物理内存中是连续的。至于你的附加问题:当数组大小等于系统页面大小时,该数组最多可以跨越两个物理页面。

1
当数组大小超过系统页面大小时,概率为1。这并不完全正确。随着数组大小的增加,概率会增加,但1是非常悲观的:典型的malloc实现以64K、128K甚至更大的块映射虚拟内存页,并且许多操作系统尝试在可能的情况下将页面映射到连续的物理内存中,因此概率仍然远未达到1,而是取决于操作系统版本和类型、页面大小、分配器实现和配置、物理内存量、系统负载、运行时间等因素。 - chqrlie
@chqrlie 我同意。我的用词有些不当。已经更新了。谢谢。 - Support Ukraine

1

任何大于页面大小的内容(数组、结构体等)都必须分割成多个页面,因此可能是“虚拟连续的、物理不连续的”。

如果没有更多的知识或限制,任何在其最小对齐方式(例如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的机会两个虚拟连续页面会“意外地物理连续”)。如果操作系统实现了页面/缓存着色(请参见https://en.wikipedia.org/wiki/Cache_coloring),则可以通过页面/缓存“颜色”的数量来提高“意外地物理连续”的概率(例如,如果计算机有1 GiB可用物理内存和4096字节的页面,并且操作系统使用256个页面/缓存颜色,则大约有1024分之1的机会两个虚拟连续页面会“意外地物理连续”)。
注意5:大多数现代操作系统使用多个页面大小(例如4 KiB页面和2 MiB页面,也可能是1 GiB页面)。这可能使得猜测页面大小变得困难,或者如果假定使用最小的页面大小,则可以提高“意外地物理连续”的概率。
注意6:对于某些CPU(例如最近的AMD / Zen),仅当页表项兼容时(例如,如果4个页表项描述具有相同权限/属性的四个物理连续的4 KiB页面),TLB的行为会像页面更大一样(例如,好像您正在使用16 KiB页面而不是4 KiB页面)。如果操作系统针对这些CPU进行了优化,则结果类似于具有额外页面大小(4 KiB,“16 KiB”,2 MiB甚至1 GiB)。
当在虚拟内存中分配大小为一页的数组时,它必须是一个页面,还是可以分成两个连续的虚拟内存页面(例如第一个页面的底部和第二个页面的顶部)?
当在堆内存中分配大小为一页的数组时,最小对齐是由堆管理器/ malloc() 提供的实现定义的最小对齐(例如,可能是16字节)。然而,大多数现代堆管理器在分配的内存量足够大时切换到使用替代方案(例如mmap()VirtualAlloc()或类似方法),因此(取决于实现和他们对“足够大”的定义)它可能被页面对齐。
在使用原始虚拟内存(例如使用或等自己分配数组,而不是使用堆栈和类似的东西)时,页面对齐是有保证的(主要是因为虚拟内存管理器不处理更小的东西)。

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