为什么Linux(x86)的页面大小是4 KB,如何计算?

48

在x86架构上,Linux内核的默认内存页面大小为4KB,我想知道这是如何计算出来的,以及为什么选择这个大小?


5
未来读者:@HadiBrais 在这个旧问题上的最近回答值得仔细阅读。它详细解释了在设计386时,英特尔为什么可能选择了4kiB,并且更大和更小的页面大小之间的权衡取舍。 - Peter Cordes
6个回答

52

默认页面大小由CPU的内存管理单元(MMU)支持的大小确定。在32位保护模式下,x86支持两种页面:

  • 普通页面,4 KiB
  • 大页面,4 MiB

并非所有的x86处理器都支持大页面。需要具备页面大小扩展(PSE)功能的CPU。这排除了早期的奔腾处理器。几乎所有当前的x86 CPU都实现了它。

4 KiB也是其他架构中广泛使用的页面粒度。有人可能会认为这个大小来自于将32位虚拟地址划分为两个10位索引和剩余的12位给出4 KiB页面大小。


5
4M巨页面仅适用于32位模式下的x86。64位模式下的x86使用2M或1G巨页面,因为4级页表格式每级使用9位。在长模式下,非巨页面大小仍为4k,因此L1DTLB和L1D缓存仍然基本相同,还有其他原因。 详见https://dev59.com/qFYO5IYBdhLWcg3wBc95。 - Peter Cordes
@PeterCordes,感谢您的评论。我确实只讨论了32位模式,通常我用x86表示它。 - Hristo Iliev

35

介绍

第一个支持页面虚拟内存技术的英特尔处理器是Intel 80386。该处理器支持单个页面大小,4 KB。由于它是在1985年发布的,我们必须回到那个时期才能理解为什么英特尔选择了这个特定的页面大小。

Atlas是第一台支持页面大小为3 KB 的计算机,它对虚拟内存设计产生了深远影响并激发了相关研究。该系统设计于1958-1962年间。有趣的是,尽管80386是大约20年后设计的,并且计算机(以及它们运行的工作负载)在这段时间内发生了巨大变化,但它支持的页面大小与Atlas支持的页面大小相当接近!事实上,当时许多计算机使用的页面大小范围在0.5-5 KB之间。当时的研究人员实际上花费了大量精力研究虚拟内存系统(分页和分段)。

一个重要的问题是:什么是最佳页面大小?在60年代和70年代,有大量的研究尝试研究和理解页面大小对应用程序性能的影响,并提出建议或提供指导如何选择页面大小。肯定有一些未发表的论文。据我所知,这包括英特尔的文件,其中说:“...因此,页面大小应为4 KB。”但影响或与页面大小相互作用的因素以及选择页面大小(或多个页面大小)的过程是众所周知的,这就是我将在这篇文章中基本讨论的内容。我还将特别解释为什么4 KB的页面大小是合理的。
页面大小问题
在分页方法中,物理内存被组织为一系列连续的内存区域,称为页面帧,它们具有相同的大小(这是分页的定义特征)。每个页面帧可以映射到虚拟地址空间的等大小块,称为虚拟页面。
假设一个页面由{{N}}个字节组成(这意味着按定义,页面帧的大小也是{{N}}字节),考虑一个由{{P}}页组成的虚拟地址空间(即页面编号为{0、1、2、...,{{P}}-1},总共的虚拟地址数为{{N}} * {{P}})。还假设物理地址空间由{{F}}个页面帧组成(即页面帧编号为{0、1、2、...,{{F}}-1},总物理地址数为{{N}} * {{F}})。
给定一个虚拟地址{{VA}},需要一种机制(一种映射设备)来确定它映射到的物理地址{{PA}},或者在没有映射的情况下引发页面错误。映射设备使用某处存储的数据结构(页面表)执行映射。对于每个已分配的虚拟页面,在该表中应有一个条目描述页面如何映射以及可能的其他属性(例如保护属性)。正如您将看到的那样,页面表项的设计与页面大小相互作用。我稍后会讨论Intel 80386的页面表项设计。
虚拟地址的大小为log2(N * P),物理地址的大小为log2(N * F)。VA的一些位表示页面内的偏移量,而其他位表示页面号,使用映射设备标识页面。
页面大小有多少种选择?可以是一个字节至多N * PN * F,以较小者为准。这是很多选择。
页面大小为2的幂次方是方便的。
虚拟地址VA等同于页面号和偏移量(PNOFF)。翻译过程应尽可能高效。对于程序员来说,页面内的字节在地址空间中连续是方便的。通过在单个地址上进行简单算术运算,即数据结构的基地址,可以计算出多字节数据结构内项目的地址。这可以通过使用虚拟地址的最低有效log2(N)位(向上取整)表示偏移量,其余位表示页面号来实现。
如果N不是2的幂,则偏移量和页码之间会共享一些位,具体取决于这些位的值。将N设置为2的幂次方可以避免这种复杂情况。因此,使用2的幂次方作为页面大小会更加简洁。所有支持分页的真实处理器都使用2的幂次方作为页面大小(尽管可寻址单位可能不是8位),这是有道理的。但说实话,现在技术已经发展到了令人难以置信的地步,N是否是2的幂次方可能对性能或其他感兴趣的度量标准没有任何可衡量的影响。事实上,在未来需要更大的页面大小时,可能会出现一些不是2的幂次方的页面大小更好的情况。但到目前为止,还没有发生过。我想表达的观点是,使页面大小不是2的幂次方的设计选项不应该自动被忽视。我认为这是未来虚拟内存系统研究的一个好机会。
无论如何,要记住的是,选择4KB页面是在80年代做出的,而页面大小的这种极小变化(从理论和实验上)被证明对性能影响很小。因此,2的幂次方页面大小的便利性胜过了其他。这将指数级减少要考虑的页面大小数量。但我们仍然有很多选择。
更喜欢较小的页面大小
由于映射设备在页面级别上运行,从操作系统的角度来看,分配单位是虚拟页面4。即使应用程序只需要分配1个字节,它仍然必须告诉操作系统为该1个字节分配一个完整的虚拟页面。这个问题被称为内部碎片化。每个应用程序(或甚至每个应用程序组件)都有自己的虚拟地址空间,从中以页面大小块分配内存。每个应用程序都应该尽可能地从同一页中分配尽可能多的对象,而不是为每个对象分配单独的页面。然而,由于页面属性在页面级别上工作,同一应用程序可能使用多个用户模式内存管理器(例如,在使用多个C/C++运行时时),并且应用程序难以与其他应用程序共享它们未使用的页面部分,因此内部碎片化会在系统中的许多页面中发生。使用较小的页面大小可以帮助减少浪费的物理(对于整个系统)和虚拟(每个进程)内存量。
此外,通常应用程序在其生命周期中经历许多阶段,在不同阶段具有不同的内存需求。如果页面大小为16 KB,但许多应用程序在许多阶段仅需要不到10 KB,则会有大量浪费的物理内存,这可能会导致可以通过支持较小的页面大小(例如8或4 KB)避免的内存不足情况。
较小的页面大小更适用于处理写时复制软页错误,因为页面越小,创建副本所需的时间就越短。对于极小的页面大小,这可能没有任何可测量的差异,具体取决于内存总线带宽。
70年代计算机可用的物理内存通常在几十KB或几百KB的范围内。设置数百KB或更大的页面大小是没有意义的。实际上,当时应用程序的工作集通常只有几个KB或几十个KB。因此,即使页面大小小至16 KB也不太实用,因为只有几个页面可能会消耗所有可用的物理内存。页面大小应与物理内存量相一致。当然,这个论点也适用于今天的系统(例如,设置128 GB页面是没有意义的)。
因此,在考虑70年代和80年代早期的工作集大小和物理内存可用性时,页面大小应该是20-214范围内的2的幂次方。现在,我们只有15个选项可供选择。
偏好较大的页面大小
我们也可以认为更大的页面大小更好。对于相同的工作集,较小的页面大小意味着每个应用程序需要更多的页面,这将需要页表条目来启用翻译。这基本上需要更大的页表,无论页表的结构如何(尽管确切的开销取决于页表条目本身的设计,我稍后会讨论)。想象一下,例如4字节的页面和几十KB的典型工作集。那么大部分物理内存实际上都被分配用于保存页面表而不是数据。将页面表换出到二级存储会导致单个内存引用的双重页面错误,这对性能来说是绝对糟糕的。这种设计显然是荒谬的。
基本上,页面大小不应该比可能的最小工作集大小(稍微)小。这立即排除了20-26大小的页面,留下了8个选项。70年代和80年代初研究页面大小的论文主要研究了这8个选项。
还有另一个原因使更大的页面大小具有优势。虚拟内存的好处之一是能够透明地使用辅助存储器以外的主存储器来保存易失性数据。但是,辅助存储设备是机械的,并且通过批量传输表现最佳。这实际上更多是一个指导方针,我们不应排除任何页面大小。有不同设计和特性的设备,最终,批量传输的性能优势会饱和到某个点。但是,在测量页面大小对性能的影响时,这肯定是需要考虑的因素。如果考虑的应用程序类型表现出很少的空间局部性,则较小的页面大小仍然是首选,因为将额外的字节复制到磁盘或从磁盘复制并不是完全免费的。
{{空间局部性推荐使用特定的页面大小。}}对于非常小的页面大小,高度可能会在短时间内使用页面中的所有字节。随着页面大小的增大,不太可能使用的字节数增加。但是,对于非常大的页面,无论地区如何,整个工作集都可以适合于少量页面。因此,为了最小化页面故障次数,页面大小应该太小或太大,而不是介于两者之间。但是,这因应用程序而异。新兴的编程范例(如面向对象编程和函数式编程)和技术(如多线程)可能会由于链接结构的广泛使用以及不同应用程序相互交互的方式而实际上减少空间局部性。较大的页面大小更可取,以减少页面故障的数量。但是,较小的页面大小可能更适合用于在多个应用程序之间共享的内存,以减少共享页面的内部碎片。实验证明,内存中可以保存的页面帧数与页面故障的数量相关,支持使用较小的页面大小。
{{当时已经很清楚需要TLB。}}英特尔在其专利中称其为“页面缓存”(不确定是否是英特尔首先使用该术语)。快速的TLB非常重要,因为地址转换处于执行指令的关键路径上。为了使它们尽可能快,它们应该被制作得很小,但是那样它们只能缓存少量的页表项。这促使使用更大的页面大小。
在寻找最佳页面大小时,发现并不存在一个最佳大小。当时已知需要支持多种页面大小。实际上,1965年设计的Multics系统支持64或1,024个字长为36位的单词大小的页面。在不同的场景中,页面大小在2的7次方到2的14次方之间被证明是最优的。英特尔必须已经观察到4KB页面对于80年代客户使用的应用程序具有最佳平均性能。对于今天的计算机和应用程序来说,页面大小的微小差异不再像70年代和80年代那样重要。
Intel 80386的页面目录项和页表项的设计在一份专利中详细讨论。这很重要,因为页面表项的大小和页面表的整体结构在许多关于页面大小的研究中都相互影响。为了保持回答简短,我不愿详细讨论这个问题。
未来的页面大小

几个月前,英特尔被授予了一项专利,该专利提出了一个默认页面大小为64 KB的系统,但同时支持4 KB页面(和其他IA-32e页面大小)以实现向后兼容性。我引用专利中的话:

由于支持将64 KB页面映射到4 KB子页面,VA64直接支持所有当前定义的4 KB页面操作,包括每个4 KB页面的独立保护位和任意4 KB对齐地址映射。即使操作系统内核以64 KB为单位分配内存,VA64也支持在4 KB边界上进行OS内核页面管理。由于支持大页面,VA64支持现有分页系统(例如英特尔公司的IA-32e分页系统)支持的虚拟地址范围的所有页面划分。因此,VA64支持与4 KB页面Windows®操作系统内核一起工作的应用程序和硬件设备,同时在可以使用64 KB页面时充分利用64 KB页面。
VA64的功能可以逐步被操作系统内核采用,而不需要在第一代VA64-capable操作系统内核中支持它们所有。例如,VA64-capable操作系统内核可以先将所有页面映射到当前大小(例如,英特尔公司的IA-32e分页系统中的4 KB / 2 GB / 1 TB),但更改为新的页表格式。在更改了页表格式之后,操作系统内核可以修改为以64 KB为单位映射虚拟内存,并更改为在其空闲列表中存储64 KB页面。然后,操作系统内核可以在对齐和保护允许的情况下开始使用64 KB页面,并添加对其他VA64功能的支持。

除了专利中所写的内容,我对VA64分页系统一无所知。网上没有任何相关信息。我猜我们很快就会知道更多。

参考文献

Denning, P.J. (1970). 虚拟内存. ACM计算机调查杂志 第2卷第3期,153-189。

Gelenbe, E., Tiberio, P., & Boekhorst, J. C. A. (1973). 需求分页系统中的页面大小. Acta Informatica,第3卷第1期,1-23。

Alanko, T. O., & Verkamo, A. I. (1983). 分段、分页和虚拟内存中的最佳页面大小。性能评估,第3卷第1期,13-33。

Corbató,F. J.和Vyssotsky,V. A。(1965)。Multics系统的介绍和概述。在1965年11月30日至12月1日秋季联合计算机会议第一部分的论文集中(第185-196页)。


脚注

(1) 实际上一个虚拟页的大小可以是页面帧大小的倍数,但我们不深入讨论这个。

(2) 我们可以概括公式,使用术语“字”来指代最小可寻址内存单元而非字节,但这在这里并不重要。

(3) 根据编程语言的不同,也许不是程序员,而是编译器、链接器、汇编器以及与二进制代码相关的工具。

(4) 设计支持同时使用分页和非分页的系统当然是可能的,从而潜在地支持多个分配单元,但我们不深入讨论这个。


2
小页面的另一个好处就是单页的缺页成本/延迟较低,无论是从磁盘中调入页还是处理写时复制软缺页的时间。对于交互使用(以及其他用例),多次短暂的暂停比偶发的较长暂停更容易接受(如果它们足够短)。 - Peter Cordes
@PeterCordes 是的,用于处理写时复制软页故障。同样也是用于处理来自磁盘的页面故障,但仅在空间局部性较低的情况下。基本上,一次传输从磁盘复制整个轨道比分两次传输复制轨道快,但比只复制半个轨道慢一点。因此,将页面大小缩小(等于半个轨道而不是整个轨道)是有意义的,但仅当空间局部性很小的时候才是这样。否则,最好一次性复制整个轨道。我会在答案中澄清这一点。 - Hadi Brais

26

32位体系结构中4KB普通页面大小的设计实际上非常有趣 :)

我想补充一个额外的答案来说明为什么这是合理的。

x86使用“2级页表”将虚拟内存地址转换为物理内存地址。

因此,假设页面目录和页面表都包含2x个条目,并且页面大小为2y字节。为了充分利用232个地址,我们有:

2x × 2x × 2y = 232 ⇒ 2x + y = 32

每个页面目录/表中的条目占用4个字节(32位),因此:

2y/4 = 2x ⇒ y - 2 = x

因此y=12,页面大小以字节为单位将是2y = 212 = 4KB。


那么“1级页表”呢?这很有趣,因为在逻辑上我们可以使用单个页表进行地址查找。

假设页面目录包含2x个条目,每个条目将地址映射到相应的页面,页面大小为2y字节。

同样,为了充分利用232个地址,我们需要:

2x × 2y = 232 ⇒ x + y = 32

和:

2y/4 = 2x ⇒ y - 2 = x

我们得到y=17,页面大小是2y = 217 = 128KB。


我们也可以说,在“2级页表”版本中,页面目录和页面表可能具有不同的大小。但这意味着我们将使用更大的页面目录,它将占用超过一个内存页面。可惜的是,每当生成新的用户进程时,为了其自己的页面目录,操作系统必须分配连续的页面,这不是设计上的优雅。


1
正常的术语是“二级页表”,而不是“二遍通行证”。例如,x86-64使用四级页表(仍然使用4k常规页面,但大页面为2M或1G,而不是4M)。 - Peter Cordes
1
你的一级页表部分有一个不必要的假设:页面表本身不必以1个页面大小存在。可以使用更小的页面(和一个更巨大的平面页表)。一级页表的问题在于页表大小:只映射了少量内存的进程可能具有仅包含几个底层页表的稀疏树。TLB根本不是问题;每个TLB都包含来自页面表各级的完整转换,因此较大的页面意味着更少的页面位,这意味着更小的CAM。而且,较少的TLB条目覆盖更多的内存。 - Peter Cordes
1
@PeterCordes,展示的数学很好,但这并不是页面大小为4 KB的原因。推导更像是反过来的。 - Hadi Brais
@PeterCordes:我根据你的建议,用“2级页表”术语编辑了答案,并删除了答案中关于TLB的提及。(很久以前回答过这个问题,我的知识有很多过时了) - sleepsort
@HadiBrais:是的,我同意这个回答并没有真正回答提问者的问题。更多地展示了一种选择的优雅之处,从另一个角度来看。 - sleepsort

12

这取决于处理器架构

在许多架构上,默认页面大小为4 KB。通过切换到巨型页面模式,它通常可以增加(有时非常大,例如 AMD64 的 1 GB)。这允许页面表更小,从而可能导致性能改进。


巨页的最大好处是相同数量的TLB条目可以覆盖更多的内存,允许更大的工作集而不会出现TLB缺失。 (但它不是一个单独的模式:第二或第三级页表条目可以是巨页,而不是指向另一级页目录的指针。这就是为什么x86-64上的巨页大小为2M和1G,因为每个页表级别都比4k页面的12位地址位再多9位地址位。而旧式的2级32位页表具有4M巨页,2^(12+10)) - Peter Cordes

0

0

我添加了这个答案/评论,因为sleepsort的计算非常有趣,我必须将其添加到我的网页上(当然要注明来源)。

关于如何以编程方式计算页面大小的(可能的)答案可以在这里找到。正如sleepsort所提到的那样,它的计算方式非常有趣。我对x86_64也做了同样的尝试,但结果并不是我所期望的。

然而,在进一步阅读x86_64内存管理后,我发现对于64位英特尔处理器,其中16位未被使用,留下48位供我们计算。

9位用于内存树分支,
每个分支另外9位,
这里再加上另外9位用于分支,
最后还有9位用于最后一个分支的叶子节点。

所以每个内存页中的每个地址有12位,即48-36=12位。 212=4096,正如sleepsort之前提到的。

我只是想知道这个架构经过了多少次传递,是3次还是4次(如果有人能解释一下)

根据sleepsort的计算:

2x * 2x * 2x * 2x * 2y = 248

4x + y = 48

这次每个地址有8字节(8字节*8位/字节=64位)

2y / 23 = 2x

y - 3 = x

填入上一个方程式:

4(y - 3) + y = 48
4y - 12 + y = 48
5y = 60
y = 12

因为2y表示页面大小,所以大小= 212 = 4096字节

让我想起了“Linux中的巨大页面”这个问题?


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