众所周知,Linux 会以页面为单位对内存进行管理。不论是将磁盘中的数据加载到内存中,还是将内存中的数据写回磁盘中,操作系统都会以页面为单位进行操作,这也意味着如果我们只向磁盘中写入一个字节的数据,操作系统也需要将整个页面中的全部数据刷入磁盘中。
值得注意的是,在 Linux 中,我们既可以使用正常大小的内存页进行操作,也可以使用大内存页 (Huge Page),尽管绝大多数处理器上的内存页默认大小为 4KB,但是部分处理器会使用 8KB、16KB 或者 64KB 作为默认的页面大小。除了正常的内存页大小之外,不同的处理器还支持不同的大页面大小。例如,在 x86 处理器上,我们可以使用 2MB 大小的内存页。
4KB 内存页已经成为一个历史遗留问题,这一页面大小从上个世纪 80 年代开始被广泛采用,而至今仍然保留下来。虽然现在的硬件比过去更加丰富,但是我们仍然延续着过去流传下来的 4KB 内存页大小。通常情况下,我们可以在安装内存时清晰地看到内存条的规格,如下图所示:
图 1 – 随机存取内存
在今天,4KB 的内存页大小可能不是最佳的选择,8KB 或者 16KB 说不定是更好的选择,但是这是过去在特定场景下做出的权衡。我们在这篇文章中不要过于纠结于 4KB 这个数字,应该更重视决定这个结果的几个因素,这样当我们在遇到类似场景时才可以从这些方面考虑当下最佳的选择,我们在这篇文章中会介绍以下两个影响内存页大小的因素,它们分别是:
过小的页面大小会带来较大的页表项增加寻址时 TLB(Translation lookaside buffer)的查找速度和额外开销;
过大的页面大小会浪费内存空间,造成内存碎片,降低内存的利用率;
上个世纪在设计内存页大小时充分考虑了上述的两个因素,最终选择了 4KB 的内存页作为操作系统最常见的页大小,我们接下来将详细介绍以上它们对操作系统性能的影响。
页表项
我们在 为什么 Linux 需要虚拟内存 一文中曾经介绍过 Linux 中的虚拟内存,每个进程能够看到的都是独立的虚拟内存空间,虚拟内存空间只是逻辑上的概念,进程仍然需要访问虚拟内存对应的物理内存,从虚拟内存到物理内存的转换就需要使用每个进程持有页表。
为了存储 64 位操作系统中 128 TiB 虚拟内存的映射数据,Linux 在 2.6.10 中引入了四层的页表辅助虚拟地址的转换[,在 4.11 中引入了五层的页表结构,在未来还可能会引入更多层的页表结构以支持 64 位的虚拟地址。
图 2 – 四层页表结构
在如上图所示的四层页表结构中,操作系统会使用最低的 12 位作为页面的偏移量,剩下的 36 位会分四组分别表示当前层级在上一层中的索引,所有的虚拟地址都可以用上述的多层页表查找到对应的物理地址。
因为操作系统的虚拟地址空间大小都是一定的,整片虚拟地址空间被均匀分成了 N 个大小相同的内存页,所以内存页的大小最终会决定每个进程中页表项的层级结构和具体数量,虚拟页的大小越小,单个进程中的页表项和虚拟页也就越多。
PagesCount=VirtualMemory ÷ PageSize
因为目前的虚拟页大小为 4096 字节,所以虚拟地址末尾的 12 位可以表示虚拟页中的地址,如果虚拟页的大小降到了 512 字节,那么原本的四层页表结构或者五层页表结构会变成五层或者六层,这不仅会增加内存访问的额外开销,还会增加每个进程中页表项占用的内存大小。
碎片化
因为内存映射设备会在内存页的层面工作,所以操作系统认为内存分配的最小单元就是虚拟页。哪怕用户程序只是申请了 1 字节的内存,操作系统也会为它申请一个虚拟页,如下图所示,如果内存页的大小为 24KB,那么申请 1 字节的内存会浪费 ~99.9939% 的空间。
图 3 – 大内存的碎片化
随着内存页大小的增加,内存的碎片化情况会越来越严重,小的内存页会减少内存空间中的内存碎片,提高内存的利用率。上个世纪的内存资源还没有像今天这么丰富,在大多数情况下,内存都不是限制程序运行的资源,多数的在线服务都需要更多的CPU,而不是更多的内存。不过在上个世纪内存其实也是稀缺资源,所以提高稀缺资源的利用率是我们不得不考虑的事情:
图 4 – 内存的价格
上个世纪八九十年代的内存条只有 512KB 或者 2MB,价格也贵得离谱,但是几 GB 的内存在今天却非常常见,所以虽然内存的利用率仍然十分重要,但是在内存的价格大幅降低的今天,碎片化的内存不再是需要解决的关键问题了。
除了内存的利用率之外,较大的内存页也会增加内存拷贝时的额外开销,因为 Linux 上的写时拷贝机制,在多个进程共享同一块内存时,当其中的一个进程修改了共享的虚拟内存会触发内存页的拷贝,这时操作系统的内存页越小,写时拷贝带来的额外开销也就越小。
总结
就像我们在上面提到的,4KB 的内存页是上个世纪决定的默认设置,从今天的角度来看,这很可能已经是错误的选择了,arm64、ia64 等架构已经可以支持 8KB、16KB 等大小的内存页,随着内存的价格变得越来越低、系统的内存变得越来越大,更大的内存可能是操作系统更好的选择,我们重新回顾一下两个决定内存页大小的要素:
-
过小的页面大小会带来较大的页表项增加寻址时 TLB(Translation lookaside buffer)的查找速度和额外开销,但是也会减少程序中的内存碎片,提高内存的利用率; -
过大的页面大小会浪费内存空间,造成内存碎片,降低内存的利用率,但是可以较少进程中的页表项以及 TLB 的寻址时间;
到最后,我们还是来看一些比较开放的相关问题,有兴趣的读者可以仔细思考一下下面的问题:
Linux 中的扇区、块和页都有什么区别和联系?
Linux 中的块大小是如何决定的?常见的大小有哪些?
以上就是良许教程网为各位朋友分享的Linu系统相关内容。想要了解更多Linux相关知识记得关注公众号“良许Linux”,或扫描下方二维码进行关注,更多干货等着你 !