Why'sTheDesign是计算机领域编程决策的系列文章。在本系列的每篇文章中,我们都会提出一个具体的问题,并从不同的角度讨论这种设计的优缺点及其对具体实现的影响。如果大家有什么想了解的问题,可以在文章下方留言。我们都知道Linux是以页为单位管理内存的。无论是将数据从磁盘加载到内存,还是将内存中的数据写回磁盘,操作系统都会以页为单位进行操作,即使我们只是将数据写入磁盘也是如此。要写入一个字节的数据,我们还需要将整个页面中的所有数据刷新到磁盘。Linux同时支持普通大小的内存页和巨大的内存页(HugePage)[^1]。大多数处理器上内存页的默认大小是4KB,虽然有些处理器使用8KB、16KB或64KB作为默认页大小,但4KB页仍然是操作系统默认内存页配置的主流;除了正常的内存页大小,不同的处理器还包含不同大小的大页,我们可以在x86处理器上使用2MB内存页。4KB内存页其实是个历史问题。1980年代确定的4KB一直保留到今天。虽然今天的硬件比过去丰富了很多,但是我们仍然使用过去主流的内存页大小。如下图,装过机的人应该对这里的内存条很熟悉了:图1-RandomAccessMemory如今,4KB内存页大小可能不是最佳选择,8KB或16KB可能是更好的选择,但这是过去在某些情况下做出的权衡。在这篇文章中,我们不要太执着于4KB的数量。我们应该更多地关注决定这个结果的几个因素,这样当我们遇到类似的场景时,就可以从这些方面来考虑最佳选择。我们在这篇文章中将介绍以下两个影响内存页面大小的因素,它们是:页面大小过小会带来较大的页表条目以提高搜索速度和TLB(Translationlookasidebuffer)在寻址时的额外开销;pagesize大会浪费内存空间,造成内存碎片,降低内存利用率;上个世纪在设计内存页大小时充分考虑了以上两个因素,最终选择4KB内存页作为最常见的操作系统。页面大小,我们接下来会详细描述上面对操作系统性能的影响。页表条目我们在为什么Linux需要虚拟内存一文中介绍了Linux中的虚拟内存。每个进程看到的是一个独立的虚拟内存空间。虚拟内存空间只是一个逻辑概念。进程仍然需要访问虚拟内存。物理内存对应内存,从虚拟内存到物理内存的转换需要使用到每个进程持有一个页表。为了在64位操作系统中存储128TiB虚拟内存的映射数据,Linux在2.6.10引入了四层页表辅助虚拟地址转换[^2],五层页表结构在4.11[^3]中,未来可能会引入更多层的页表结构来支持64位虚拟地址。图2——四级页表结构在上图所示的四级页表结构中,操作系统会使用最低12位作为页的偏移量,其余36位会分成四组在上层的Index中表示当前层级,所有的虚拟地址都可以使用上面提到的多层页表找到对应的物理地址[^4]。因为操作系统的虚拟地址空间大小是固定的,整个虚拟地址空间被平均划分为N个大小相同的内存页,所以内存页的大小最终会决定页面的层次结构和细节每个过程中的表条目。虚拟页大小越小,单个进程中的页表项和虚拟页就越多。因为当前虚拟页大小为4096字节,所以虚拟地址末尾的12位可以表示虚拟页中的地址。如果虚拟页的大小减小到512字节,那么原来的四层页表结构或者五层页表结构就会变成五六层,这样不仅会增加内存访问的开销,而且同时增加每个进程中页表项占用的内存大小。碎片因为内存映射设备工作在内存页面级别,所以操作系统将内存分配的最小单位视为虚拟页面。即使用户程序只申请了1字节的内存,操作系统也会为它申请一个虚拟页,如下图,如果内存页的大小是24KB,那么申请1字节的内存就会浪费~99.9939%的空间。图3——大内存的碎片随着内存页大小的增加,内存的碎片会越来越严重。小内存页会减少内存空间中的内存碎片,提高内存利用率。在上个世纪,内存资源并不像今天这样丰富。在大多数情况下,内存并不是限制程序运行的资源。大多数在线服务需要更多的CPU,而不是更多的内存。然而,内存在上个世纪其实是一种稀缺资源,所以提高稀缺资源的利用率是我们不得不考虑的事情:图4-内存价格在80年代和90年代,内存条只有512KB或2MB,价格是也贵的离谱,但是几千兆的内存在今天已经很普遍了[^8],所以虽然内存利用率仍然很重要,但是在内存价格大幅下降的今天,内存碎片已经不是要解决的关键问题了向上。除了内存利用率,更大的内存页也会增加内存复制的额外开销,因为Linux上的copy-on-write机制,当多个进程共享同一块内存时,当其中一个进程修改共享的虚拟内存时虚拟内存会触发内存页的复制。这时候操作系统的内存页越小,copy-on-write带来的额外开销就越小。总结正如我们上面提到的,4KB内存页是上个世纪决定的默认设置。从今天的角度来看,这很可能是错误的选择。arm64、ia64等架构已经可以支持8KB、16KB等内存页,随着内存的价格越来越低,而系统的内存越来越大,更大的内存可能是操作系统更好的选择。回顾一下决定内存页的两个页面大小的要素:页面大小过小会带来较大的页表条目以提高搜索速度和寻址时TLB(Translationlookasidebuffer)的额外开销,但也会减少程序中的内存碎片并提高内存利用率;pagesize太大会浪费内存空间,造成内存碎片,降低内存利用率,但可以减少进程中的页表项和TLB寻址时间;这种类似的场景在我们设计系统的时候用的也比较多。举个不恰当的例子,当我们要在集群上部署服务时,每个节点上的资源是有限的。单个服务占用的资源可能会影响集群的资源利用率或系统的额外资源。高架。如果我们在集群中部署32个占用1个CPU的服务,那么我们可以充分利用集群中的资源,但是这样大量的实例会带来很大的额外开销;如果我们在集群服务中部署4个占用8个CPU的服务,这些服务的额外开销很小,但可能会在节点中留下很多空隙。最后,让我们看看一些未解决的相关问题。有兴趣的读者可以仔细思考以下问题:Linux中的扇区、块、页之间有什么区别和联系?Linux中的块大小是如何确定的?常见尺寸有哪些?本文转载自微信公众号“毫无逻辑”,可关注下方二维码。转载本文请联系公众号。
