hugepage总结

0.概述

本文总结了hugepage使用过程中的一些知识点,参考的内核版本是4.1.12
注:本文为原创,转载请注明出处

1.hugepage是什么,有什么用?

大页是相对传统4K小页而言的,一般来说常见的体系架构都会提供2种大页大小,比如常见的2M大页和1G大页。其实这两种大页size也分别对应PMD和PUD的一个页表项可以cover的物理内存大小。当然某些体系架构(如arm64)通过contiguous-tlb特性支持2种以上的大页。
大页的主要优点:使用大页可以减少页表项数量,从而减少TLB Miss的概率,提升系统访存性能。当然有利必有弊,使用大页降低了内存管理的粒度和灵活性,如果程序并不是对内存的使用量特别大,使用大页还可能造成内存的浪费。

2.如何使用hugepage

一般来说有三种方法:

  1. 使用mmap+MAP_HUGETLB直接匿名映射
  2. 使用shmget+SHM_HUGETLB属性分配
  3. 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/…

    原文作者:scola666
    原文地址: https://segmentfault.com/a/1190000010436559
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞