分类: LINUX
2010-09-07 19:13:23
|
级别: 初级 宏伟 唐 (), 硕士研究生, 中国科学院计算技术研究所 2009 年 9 月 10 日 为了减少 TLB Miss 对应用程序性能的影响,Linux 内核支持以 2MB 作为物理页面分页的基本单位。 Linux 内核采用基于 Hugetlb 伪文件系统的实现方式支持大页面,虽然较大地提升了应用程序的性能,但由于不能做到完全的透明性,对应用程序的移植带来了挑战。本文对 Linux 大页面机制的使用和实现进行了简要的介绍和分析。 随着计算需求规模的不断增大,应用程序对内存的需求也越来越大。为了实现虚拟内存管理机制,操作系统对内存实行分页管理。自内存“分页机制”提出之始,内存页面的默认大小便被设置为 4096 字节(4KB),虽然原则上内存页面大小是可配置的,但绝大多数的操作系统实现中仍然采用默认的 4KB 页面。 4KB 大小的页面在“分页机制”提出的时候是合理的,因为当时的内存大小不过几十兆字节,然而当物理内存容量增长到几 G 甚至几十 G 的时候,操作系统仍然以 4KB 大小为页面的基本单位,是否依然合理呢? 在 Linux 操作系统上运行内存需求量较大的应用程序时,由于其采用的默认页面大小为 4KB,因而将会产生较多 TLB Miss 和缺页中断,从而大大影响应用程序的性能。当操作系统以 2MB 甚至更大作为分页的单位时,将会大大减少 TLB Miss 和缺页中断的数量,显著提高应用程序的性能。这也正是 Linux 内核引入大页面支持的直接原因。好处是很明显的,假设应用程序需要 2MB 的内存,如果操作系统以 4KB 作为分页的单位,则需要 512 个页面,进而在 TLB 中需要 512 个表项,同时也需要 512 个页表项,操作系统需要经历至少 512 次 TLB Miss 和 512 次缺页中断才能将 2MB 应用程序空间全部映射到物理内存;然而,当操作系统采用 2MB 作为分页的基本单位时,只需要一次 TLB Miss 和一次缺页中断,就可以为 2MB 的应用程序空间建立虚实映射,并在运行过程中无需再经历 TLB Miss 和缺页中断(假设未发生 TLB 项替换和 Swap)。 为了能以最小的代价实现大页面支持,Linux 操作系统采用了基于 hugetlbfs 特殊文件系统 2M 字节大页面支持。这种采用特殊文件系统形式支持大页面的方式,使得应用程序可以根据需要灵活地选择虚存页面大小,而不会被强制使用 2MB 大页面。本文将针对 hugetlb 大页面的应用和内核实现两个方面进行简单的介绍,以期起到抛砖引玉的作用。
本文的例子摘自 Linux 内核源码中提供的有关说明文档 (Documentation/vm/hugetlbpage.txt) 。使用 hugetlbfs 之前,首先需要在编译内核 (make menuconfig) 时配置CONFIG_HUGETLB_PAGE和CONFIG_HUGETLBFS选项,这两个选项均可在 File systems 内核配置菜单中找到。 内核编译完成并成功启动内核之后,将 hugetlbfs 特殊文件系统挂载到根文件系统的某个目录上去,以使得 hugetlbfs 可以访问。命令如下: mount none /mnt/huge -t hugetlbfs 此后,只要是在 /mnt/huge/ 目录下创建的文件,将其映射到内存中时都会使用 2MB 作为分页的基本单位。值得一提的是,hugetlbfs 中的文件是不支持读 / 写系统调用 ( 如read()或write()等 ) 的,一般对它的访问都是以内存映射的形式进行的。为了更好地介绍大页面的应用,接下来将给出一个大页面应用的例子,该例子同样也是摘自于上述提到的内核文档,只是略有简化。
对于系统中大页面的统计信息可以在 Proc 特殊文件系统(/proc)中查到,如/proc/sys/vm/nr_hugepages给出了当前内核中配置的大页面的数目,也可以通过该文件配置大页面的数目,如:
调整系统中的大页面的数目为 20 。 例子中给出的大页面应用是简单的,而且如果仅仅是这样的应用,对应用程序来说也是没有任何用处的。在实际应用中,为了使用大页面,还需要将应用程序与库libhugetlb链接在一起。libhugetlb库对malloc()/free()等常用的内存相关的库函数进行了重载,以使得应用程序的数据可以放置在采用大页面的内存区域中,以提高内存性能。
在简要介绍了大页面的使用之后,本文接下来将重点介绍 hugetlbfs 在内核中的实现。本文的源代码分析是基于 2.6.18.8 版本的 Linux 内核进行的。涉及到的文件主要包括mm/hugetlb.c和include/linux/hugetlb.h以及fs/ hugetlbfs/inode.c三个文件。为了能够更好地理解 hugetlbfs 的工作原理,将按照上述程序示例中给出的流程,介绍 hugetlb 特殊文件系统的初始化过程、在 hugetlbfs 伪文件系统中创建文件的内核处理流程,以及将 hugetlb 文件映射到用户地址空间的内核实现过程。 Hugetlbfs 的初始化是通过函数
在示例代码中,首先调用 由于 hugetlbfs 是一个伪文件系统,在磁盘上没有相应的副本,因此在该文件系统中创建一个文件的过程也仅仅是分配虚拟文件系统( 在成功创建了 hugetlbfs 文件之后,就可以将其映射到应用进程的地址空间了,这是通过系统调用 函数 以 4KB 为基本分页单位的 64 位 Linux 操作系统来采用四级页表管理虚实映射。如图 1 所示。每个页表项占据 64 位(8Bytes),因此每个作为页表的物理页面可以存放 512 个页表项,从而最末级页表所映射的物理内存大小为 512*4KB = 2MB,依此类推,在上一级页表(PMD)中,每一个 PMD 表项可映射 2MB 的物理内存。当采用 2MB 作为分页的基本单位时,内核中则设置了三级页表,如图 2 所示。在三级页表中,最末一级页表为 PMD 表,同样地,每一个 PMD 表项指出了一个 2MB 的大页面,也即虚拟地址的低 21 位作为大页面的页内偏移,而高位则作为大页面的页面编号( 简单介绍了采用大页面映射的页表组织后,下面将描述进程在设置为大页面的虚存区域产生 Page Fault 时的缺页中断处理流程,如图 3 所示: 在进程访问到尚未建立虚实映射的大页面内存区域时,就会产生缺页中断,缺页中断的处理函数是大名鼎鼎的
Linux 基于 hugetlb 特殊文件系统的大页面支持为应用程序的灵活性和性能优化提供了方便。为了测试大页面对应用程序性能的影响,我们使用 Linpack 进行了一个简单的实验,实验结果表明,采用 hugetlb 大页面的情况下,Linpack 的性能相对于采用 4KB 页面时提升了 1 到 2 个百分点,这对于大规模的科学计算应用来说性能的提升是较为显著的。除了性能的显著提高外,简单的文件操作接口如 但是,从本质上讲 hugetlbfs 的实现方式仅仅是一个通过“打补丁”的手段来支持灵活的内存页面大小,这主要是受限于 Linux 内核“模块化”的特征,为了尽可能少地影响到其它内核模块,hugetlbfs 无疑是一个很明智的选择,同时也注定了其无法实现对应用程序的透明性。 随着芯片制造工艺的不断进步,物理内存的容量会越来越大,因此 Linux 内核的内存分页基本单位的增大是一个必然的趋势。但如何做到对传统应用程序的完全透明性和与其它内核模块的兼容性,是实现上的难点。笔者在写作本文之前曾试图通过修改 Linux 内核中定义页面大小的宏( 虚拟内存机制提出的动机就是解决内存容量限制的问题,而在今天内存容量已经渐渐地不再是一个限制,并且随着多核的发展,内存带宽明显地成为性能的瓶颈。在这样的发展趋势下,操作系统是否应该考虑新的内存管理方式?灵活的或者可配置的内存页面大小是否能够满足应用程序日益增长的内存需求?这些都是操作系统研发人员应该关注的问题。
|