0.概述
本文总结了hugepage使用过程中的一些知识点,参考的内核版本是4.1.12
注:本文为原创,转载请注明出处
1.hugepage是什么,有什么用?
大页是相对传统4K小页而言的,一般来说常见的体系架构都会提供2种大页大小,比如常见的2M大页和1G大页。其实这两种大页size也分别对应PMD和PUD的一个页表项可以cover的物理内存大小。当然某些体系架构(如arm64)通过contiguous-tlb特性支持2种以上的大页。
大页的主要优点:使用大页可以减少页表项数量,从而减少TLB Miss的概率,提升系统访存性能。当然有利必有弊,使用大页降低了内存管理的粒度和灵活性,如果程序并不是对内存的使用量特别大,使用大页还可能造成内存的浪费。
2.如何使用hugepage
一般来说有三种方法:
- 使用mmap+MAP_HUGETLB直接匿名映射
- 使用shmget+SHM_HUGETLB属性分配
- mount hugetlbfs后,在hugetlbfs下创建文件,然后通过mmap进行映射
注:上述方法使用的前提是,系统中加载了hugetlbfs,且存在空闲大页。可以通过cat /proc/meminfo查看是否有空闲内存。另外本质上匿名大页也是有对应的大页文件系统中的匿名文件,其实并不是真正的匿名页。
HugePages_Total: 10
HugePages_Free: 10
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB
DirectMap4k: 92308 kB
DirectMap2M: 1861632 kB
3.如何使能大页功能
一般来说有两种方法
1)在bootline中设置大页参数,这种方式最稳妥,因为大页本质上是连续的4K小页组成的,所以在系统启动时预留大页成功率最高。有三个参数,看名字就很清楚了
default_hugepagesz=1G hugepagesz=1G hugepages=4
如果没有定义default_hugepagesz,则大页size默认是2M
#define HPAGE_SHIFT PMD_SHIFT
#define HPAGE_SIZE (_AC(1,UL) << HPAGE_SHIFT)
#define HPAGE_MASK (~(HPAGE_SIZE - 1))
#define HUGETLB_PAGE_ORDER (HPAGE_SHIFT - PAGE_SHIFT)
static int __init hugetlb_init(void)
{
...
if (!hugepages_supported())//不支持hugepage直接返回
return 0;
if (!size_to_hstate(default_hstate_size)) {//没有设置default_hugepagesz则默认2M
default_hstate_size = HPAGE_SIZE;
if (!size_to_hstate(default_hstate_size))
hugetlb_add_hstate(HUGETLB_PAGE_ORDER);
}
...
}
2)在系统启动后,通过echo 10 > /proc/sys/vm/nr_hugepages这种方式预留空闲大页
4.系统中是否可以同时存在多种大页类型
linux系统中是可以同时存在2种大页类型的,至于为什么是2种,个人理解是因为大页往往对应PMD和PUD两种级别的页表项所cover的物理内存大小。再大意义也不大了。可以通过下面的bootline设置两种大页类型
default_hugepagesz=1G hugepagesz=1G hugepages=4 hugepagesz=2M hugepages=4
内核启动的时候会解析bootline,然后把相应的信息存放在下面的结构体中
#define HUGE_MAX_HSTATE 2
struct hstate hstates[HUGE_MAX_HSTATE];
void __init hugetlb_add_hstate(unsigned order)
{
struct hstate *h;
unsigned long i;
if (size_to_hstate(PAGE_SIZE << order)) {
pr_warning("hugepagesz= specified twice, ignoring\n");
return;
}
BUG_ON(hugetlb_max_hstate >= HUGE_MAX_HSTATE);
BUG_ON(order == 0);
h = &hstates[hugetlb_max_hstate++];
...
snprintf(h->name, HSTATE_NAME_LEN, "hugepages-%lukB",
huge_page_size(h)/1024);
parsed_hstate = h; //解析hugepages参数时要使用这个变量
}
static __init int setup_hugepagesz(char *opt)
{
unsigned long ps = memparse(opt, &opt);
if (ps == PMD_SIZE) {//2M大页
hugetlb_add_hstate(PMD_SHIFT - PAGE_SHIFT);
} else if (ps == PUD_SIZE) {//1G大页
hugetlb_add_hstate(PUD_SHIFT - PAGE_SHIFT);
} else {
pr_err("hugepagesz: Unsupported page size %lu M\n", ps >> 20);
return 0;
}
return 1;
}
__setup("hugepagesz=", setup_hugepagesz);
5.如何使用指定的大页size
在使用mmap或者shmget获取大页时,如果直接使用MAP_HUGETLB或SHM_HUGETLB参数,则获取的大页size是2M
FILE:mmap.c
...
else if (flags & MAP_HUGETLB) {
struct user_struct *user = NULL;
struct hstate *hs;
hs = hstate_sizelog((flags >> MAP_HUGE_SHIFT) & SHM_HUGE_MASK);
if (!hs)
return -EINVAL;
...
FILE:hugetlb.h
static inline struct hstate *hstate_sizelog(int page_size_log)
{
if (!page_size_log)
return &default_hstate;
return size_to_hstate(1UL << page_size_log);
}
#define default_hstate (hstates[default_hstate_idx])
如果需要指定大页的size,则可以使用MAP或SHM flag的bit[31:26],这6个bit的取值范围是0~31,因此可以表示的页大小从0~2G,基本够用了。下面是SHM flag的代码,MAP flag与此类似
/* Bits [26:31] are reserved */
/*
* When SHM_HUGETLB is set bits [26:31] encode the log2 of the huge page size.
* This gives us 6 bits, which is enough until someone invents 128 bit address
* spaces.
*
* Assume these are all power of twos.
* When 0 use the default page size.
*/
#define SHM_HUGE_SHIFT 26
#define SHM_HUGE_MASK 0x3f
#define SHM_HUGE_2MB (21 << SHM_HUGE_SHIFT)
#define SHM_HUGE_1GB (30 << SHM_HUGE_SHIFT)
还有一种方法可以指定大页的size,那就是在mount时通过pagesize选项指定:
mount -t hugetlbfs -o pagesize=1G none /dev/hugepages1G
对应的代码如下所示:
FILE:fs/hugetlbfs/inode.c
case Opt_pagesize: {
unsigned long ps;
ps = memparse(args[0].from, &rest);
//根据输入的参数到hstates数组中查找匹配的数组项
pconfig->hstate = size_to_hstate(ps);
if (!pconfig->hstate) {
//找不到匹配的大小就报错
pr_err("Unsupported page size %lu MB\n",
ps >> 20);
return -EINVAL;
}
break;
}
//最终hstate信息被保存在hugetlbfs的sb结构中
struct hugetlbfs_sb_info *sbinfo;
sb->s_fs_info = sbinfo;
sbinfo->hstate = config.hstate;
//后续hugetlbfs中的文件都可以通过file结构体关联到hstate结构体
static inline struct hstate *hstate_inode(struct inode *i)
{
struct hugetlbfs_sb_info *hsb;
hsb = HUGETLBFS_SB(i->i_sb);
return hsb->hstate;
}
static inline struct hstate *hstate_file(struct file *f)
{
return hstate_inode(file_inode(f));
}
//从而mmap就知道使用什么size的大页了,具体可以参见hugetlbfs_file_mmap函数
6.hugepage的缺页处理
hugepage在缺页时与普通小页的缺页流程是有区别的,具体流程如下
do_page_fault->__do_page_fault->handle_mm_fault->__handle_mm_fault->hugetlb_fault->hugetlb_no_page->alloc_huge_page
可以看出在__handle_mm_fault对于大页缺页直接调用了hugetlb_fault。另外多说一点,alloc_huge_page函数的参数如下所示
static struct page *alloc_huge_page(struct vm_area_struct *vma,
unsigned long addr, int avoid_reserve)
//通过下面的代码可以通过vma或者hstate
struct hstate *h = hstate_vma(vma);
//本质上还是调用了hstate_vma函数
static inline struct hstate *hstate_vma(struct vm_area_struct *vma)
{
return hstate_file(vma->vm_file);
}
7.hugepage扩展
如果要同时支持2种以上的大页类型,比如2M,32M,1G,可以修改HUGE_MAX_HSTATE宏定义,同时也要修改
hugepage pte的底层操作接口,下面的例子是arm64 contiguous-tlb的patch,可供参考。
http://www.spinics.net/lists/…