==目标==
在linux2.6.38之前,处理大容量内存工作集的高性能关键计算应用是运行在libhugetlbfs之上,必须依赖于
hugetlbfs,。透明Hugepage支持是一种替代手段,它使用大内存页,并且虚拟内存页的大小可以动态变化,没有hugetlbfs的缺点。
目前只适用于匿名内存映射,但未来可以扩展到tmpfs的papecache层。
大内存页的优点:
巨大的页面可以通过减少缺页提升性能(一次缺页分配大块的内存),还可以通过减少虚拟地址到物理地址的转换成本(减少转化次数),甚至可以避免地址转换。如果处理器必须转换一个虚拟地址,它必须通过多达四层页表。处理器保持一个“翻译后备缓冲器(TLB)的缓存的转换结果。TLB的容量很小,一般是128条指令转换、32条数据转换。非常容易被填满,从而必须进行大量的地址转换。例如:2m的页大小只需要一个tlb项,而4k的页大小就需要512个tlb项。通过内核地址使用大内存页,可以减少tlb的压力。
基于以上原因,使用大内存页的程序会运行的很快。
hugetlbfs 缺点:
(1)巨页必须在内核启动时刻进行设置,并且需要应用程序显示映射。
(2)更倾向于大的专有数据库管理系统
==设计==
- “优雅的回退”:不具有透明hugepage的内存管理系统可以会退到不使用透明hugepage,而使用常规页面和各自的规则pmd/pte映射
- ,如果因为内存碎片导致大内存页分配失败,这时系统应该优雅的使用常规页代替,并将其写入到相同的vma中,并且没有任何故障、重大延迟或用户态通知。
- 如果有些任务结束,越来越多的大页面变成可用(无论是立即在伙伴系统或通过虚拟内存),常规页分配的物理内存应该通过khugepaged自动迁往大内存页.
- 它并不需要保留内存,只要有可能就会使用大页面。
(唯一可能保留的kernelcore = 避免不可移动的页面片段去分裂所有内存,但这样一个调整不特定于透明hugepage支持,它是一个通用的适用于所有的动态内存高阶分配)
- 这个最初只支持提供匿名内存区,但将来会扩展到tmpfs和pagecache
与hugetlbfs通过将不可用内存用来作为cache或者可移动的(或者甚至无法移动实体)的保留内存方式相比,透明Hugepage可以使空闲内存利最大化。它不需要保留内存,以防止大内存页分配。这对用户态是透明的。大内存页允许分页和所有其他的先进的虚拟技术,并且对用户是透明的,不需要修改应用程序来使用这些技术。
应用可以更好的使用这些功能。例如可以避免有优化之前的malloc(4k)的mmap系统洪水。这种优化不是强制的,khugepaged会处理理那些不使用大内存的来处理大量内存的应用程序。
在某些情况下,系统范围使用大页面,会导致应用分配更多的内存资源。例如应用程序MMAP了一大块内存,但是只涉及1个字节,在这种情况下,2M页面代替4K页的分配没有任何好处,这就是为什么禁用全系统大页面,而只针对MADV_HUGEPAGE区使用的原因。
嵌入式系统仅仅在madvise区域使能大页面分配可以消除浪费内存的任何风险,并且程序运行的更快。
== sysfs==
透明Hugepage支持可以被完全禁用(多为调试目的)或只启用MADV_HUGEPAGE(以避免消耗更多的内存资源的风险)或启用系统
范围。
echo always >/sys/kernel/mm/transparent_hugepage/enabled
echo madvise >/sys/kernel/mm/transparent_hugepage/enabled
echo never >/sys/kernel/mm/transparent_hugepage/enabled
它也可以限制在创造大页面的内存碎片整理的影响,他们不是立即对madvise区空闲或从未尝试碎片整理内存而使用规则页面,除非是立即可用。显然,我们使用CPU来碎片整理内存,希望获得的更多的大内存页,而不是普通页面。这并不是保证,但它更可能的情况下分配为MADV_HUGEPAGE区。
echo always >/sys/kernel/mm/transparent_hugepage/defrag
echo madvise >/sys/kernel/mm/transparent_hugepage/defrag
echo never >/sys/kernel/mm/transparent_hugepage/defrag
khugepaged会自动启动时当transparent_hugepage/enabled设置为“always”或“madvise",它会自动关机,如果它被设置为“never”。
khugepaged通常在低频运行。
echo 0 >/sys/kernel/mm/transparent_hugepage/khugepaged/defrag
echo 1 >/sys/kernel/mm/transparent_hugepage/khugepaged/defrag
你还可以控制khugepaged每次扫描多少页。
/sys/kernel/mm/transparent_hugepage/khugepaged/pages_to_scan
扫描间隔(毫秒)
/sys/kernel/mm/transparent_hugepage/khugepaged/scan_sleep_millisecs
当分配大内存失败等待多少毫秒来压制下一个大内存页的分配尝试。
/sys/kernel/mm/transparent_hugepage/khugepaged/alloc_sleep_millisecs
可以看出,倒塌的内存页数量
/sys/kernel/mm/transparent_hugepage/khugepaged/pages_collapsed
每次扫描:
/sys/kernel/mm/transparent_hugepage/khugepaged/full_scans
==引导参数==
你可以改变透明Hugepage的默认支持。
通过内核命令行参数"transparent_hugepage=always" or
"transparent_hugepage=madvise" or "transparent_hugepage=never"。
==需要重新启动应用程序==
transparent_hugepage/enabled 只影响未来行为。因此,使它们有效,你需要重新启动任何
应用程序可以一直使用大页面。这也适用于注册khugepaged的内存区。
== get_user_pages和follow_page ==
get_user_pages和follow_page在hugepage上运行,将返回
像往常一样的头或尾页(正如hugetlbfs所做的)。
多数GUP用户只关心实际的物理页面地址和其I/O完成后的释放,所以他们将不会注意到的页面其实是巨页的。但如果驱动程序操作页面结构(如检查起始页、末尾页的page->mapping或相关的其他位),它应去检查头页。
注意:对GUP API没有新的约束,跟hugetlbfs具有相同的约束。所以任何在hugetlbfs中操作GUP的驱动可以在
透明hugepage支持映射中运行的很多。
如果你不能处理通过follow_page返回的混合页,FOLL_SPLIT位可以被指定为follow_page参数,这样它将会返回之前分离巨页。
==优化应用程序==
要保证内核立即映射到一个2M的页面在任何内存区域中,在mmap区域必须是hugepage对齐。 posix_memalign()可以提供这种保证。
== Hugetlbfs ==
您可以使用在配置透明巨页功能的内核中使用hugetlbfs。除了没有整体内存碎片外,其他没有区别。所有属于hugetlbfs的通用功能都保存并且不受影响。 libhugetlbfs也将像以前一样正常工作。
==优雅的回退==
查询pagetables的时候可以使用split_huge_page_pmd 检查pmd_offset返回的pmd。
如果没有查询pagetables并且碰上一个物理hugepage但你不能在你的代码处理它本身,可以使用split_huge_page分离它。
==了解hugepage代码的锁定==
我们要尽可能知道hugepage代码,使用split_huge_page()或split_huge_page_pmd()有代价的。
为了使查询页表时意识到 huge pmd,你需要做的是调用pmd_trans_huge 在pmd_offset返回后。您必须持有mmap_sem(读或写),确保huge pmd不能被khugepaged创建(khugepaged collapse_huge_page在获取mmap_sem和anon_vma lock在写模式下执行)。
如果pmd_trans_huge返回false,就走旧代码路径。相反,如果pmd_trans_huge返回true,你必须要获取 page_table_lock和重新执行pmd_trans_huge。page_table_lock将防止将huge pmd转换成常规pmd(split_huge_page可以并行执行检查页表)。如果第二次pmd_trans_huge返回flase,你应该丢弃page_table_lock和执行以前的代码路径。否则你应该执行pmd_trans_splitting。pmd_trans_splitting返回true,这意味着split_huge_page已经在分裂页面中。因此,如果pmd_trans_splitting返回true,丢弃page_table_lock和执行wait_split_huge_page,然后执行旧的代码路径。当wait_split_huge_page返回时候,pmd不再是巨页的。如果pmd_trans_splitting返回false,就可以进行处理hugepmd和hugepage。一旦完成,你可以释放page_table_lock。
== compound_lock,get_user_pages和put_page ==
在从页结构清除所有PG_head/tai位之前,split_huge_page函数必须从头页到尾页设置refcounts值。对huge pmd映射的refcounts很容易做到。get_page和put_page运行在尾页的时候(仅在尾页),它们必须找到其各自的头页,
然后减少头页和尾页的refcount。为了得到可靠的头页和在无竞争状态下减少refcount值,put_page必须串行获取每页锁compound_lock使用__split_huge_page_refcount。
compound_lock。