目录

一、巨型页概述

二、ARM64处理器支持巨型页

三、标准巨型页

四、透明巨型页


一、巨型页概述

        当运行内存需求量较大的应用程序时,如果使用长度为4KB的页,将会产生较多的TLB 未命中和缺页异常,严重影响应用程序的性能。如果使用长度为2MB甚至更大的巨型页,可以 大幅减少TLB未命中和缺页异常的数量,大幅提高应用程序的性能。这才是内核引入巨型页 (Huge Page)的真正原因。

巨型页首先需要处理器能够支持,然后需要内核支持,内核有两种实现方式:

• 使用hugetlbfs伪文件系统实现巨型页;

• 透明巨型页;

二、ARM64处理器支持巨型页

处理器对巨型页的支持 ARM64处理器支持巨型页的方式有两种:

• 通过块描述符支持巨型页;

• 通过页/块描述符的连续位支持巨型页。

        如果页长度为4KB,那么使用连续四级转换表,1级转换表的块描述符不能使用连续位;2级转换表的块描述符支持16个连续块,即支持(16*2MB=32MB)巨型页;3级转换表的描述符支持16个巨型页,即支持16*4KB=64KB巨型页。

        如果页长度是16KB,那么使用4级转换表,2级转换表的块描述符支持32个连续块,即支持32*32MB=1GB,巨型页,3级转换表的页描述符支持128个连续页,即支持128*16KB=2MB巨型页。

        如果页长度支持64KB,那么使用3级转换表,2级转换表的描述符不能使用连续位,3级转换表的页描述符支持32个连续页,即支持32*64KB=2MB巨型页


三、标准巨型页

通过文件“cat /proc/sys/nr_hugepages”指定巨型页池中永久巨型页的数量。

root@ubuntu:~# cat /proc/sys/vm/nr_hugepages
0

通过文件”cat /proc/sys/vm/nr_overcommit_hugepages“指定巨型页池中临时巨 型页的数量,当永久巨型页用完的时候,可以从页分配器申请临时巨型页。

root@ubuntu:~# cat /proc/sys/vm/nr_overcommit_hugepages
0

nr_hugepages是巨型页池的最小长度, (nr_hugepages+nr_overcommit_hugepages)是巨型页池的最大长度,这两个参数默认值 都是0,至少要设置一个,否则分配巨型页会失败。

创建匿名页映射

#define MAP_LENGTH 10 * 1024 * 1024

address= mmap(NULL, MAP_LENGTH , PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_HUGETLB , -1, 0);

查看巨型页信息

root@ubuntu:~# cat /proc/meminfo

HugePages_Total:       0
HugePages_Free:        0
HugePages_Rsvd:        0
HugePages_Surp:        0
Hugepagesize:       2048 kB

巨型页池

        内核使用巨型页池管理巨型页。有的处理器架构支持多种巨型页长度,每种巨型页长度对应一个巨型页池,也有一个默认的巨型页长度,默认只创建巨型页长度是默认长度的巨型页池。例如ARM64架构在页长度为4KB的时候支持巨型页长度是1GB,32MB,2MB和64KB。默认的巨型页长度是2MB,默认只创建巨型页长度是2MB的巨型页池。

巨型页池的数据结构体hstate

struct hstate {
	int next_nid_to_alloc;//分配永久巨型页并添加到巨型页池中的时候,在允许的内存节点集合中,轮流从每个内存节点分配永久
	//巨型页,这个成员用来记录下次从哪个内存节点分配永久巨型页
	int next_nid_to_free;
	unsigned int order;//巨型页的长度,阶数
	unsigned long mask;//巨型页的掩码,将虚拟地址和掩码按位与,得到巨型页页号
	unsigned long max_huge_pages;//最大巨型页的数量
	unsigned long nr_huge_pages;//巨型页的数量
	unsigned long free_huge_pages;//空闲巨型页的数量
	unsigned long resv_huge_pages;//预留巨型页的数量
	unsigned long surplus_huge_pages;//临时巨型页的数量
	unsigned long nr_overcommit_huge_pages;//临时巨型页的最大数量
	struct list_head hugepage_activelist;//分配出去的巨型页链起来
	struct list_head hugepage_freelists[MAX_NUMNODES];//每一个内存节点一个空闲巨型页链表

	unsigned int nr_huge_pages_node[MAX_NUMNODES];//每个内存节点中巨型页的数量
	unsigned int free_huge_pages_node[MAX_NUMNODES];//每个内存节点中 空闲页的数量
	unsigned int surplus_huge_pages_node[MAX_NUMNODES];//每个内存节点中 临时巨型页数量
#ifdef CONFIG_CGROUP_HUGETLB
	/* cgroup control files */
	struct cftype cgroup_files_dfl[5];
	struct cftype cgroup_files_legacy[5];
#endif
	char name[HSTATE_NAME_LEN];//巨型页池名
};

巨型页池中的巨型页分为两种

永久巨型页:保留的,不能有其他用途,被预先分配到巨型页池中,释放时,归还到巨型页池。

临时巨型页:多余的巨型页,永久巨型页用完,可以从页分配器分配临时巨型页,释放时,被释放到页分配器。当设备长时间运行后,内存可能碎片化,分配临时巨型页可能失败。

预先分配永久巨型页

预先分配指定数量的永久巨型页到巨型页池中有两种方法

1)引导内核参数 “hugepages=N”,初始阶段内存没有碎片,分配比较可靠。

        特定长度巨型页hugepagesz=<size>[kKmMgG]

        hugepages=N 处理函数hugetlb_nrpages_setup

static int __init hugetlb_nrpages_setup(char *s)
{


    //如果hugepagesz是非法的,直接返回
	if (!parsed_valid_hugepagesz) {
		pr_warn("hugepages = %s preceded by "
			"an unsupported hugepagesz, ignoring\n", s);
		parsed_valid_hugepagesz = true;
		return 1;
	}
	/*
	 * !hugetlb_max_hstate means we haven't parsed a hugepagesz= parameter yet,
	 * so this hugepages= parameter goes to the "default hstate".
	 */
    //如果前面没有内核参数hugepagesz=,那么hugepages指定默认巨型页池的永久巨型页数量
	else if (!hugetlb_max_hstate)
		mhp = &default_hstate_max_huge_pages;
	else//如果有指定长度,那么参数指定该巨型页对应的巨型页池的永久巨型页的数量。
		mhp = &parsed_hstate->max_huge_pages;

	if (hugetlb_max_hstate && parsed_hstate->order >= MAX_ORDER)
		hugetlb_hstate_alloc_pages(parsed_hstate);

}

static void __init hugetlb_hstate_alloc_pages(struct hstate *h)
{
	unsigned long i;
	nodemask_t *node_alloc_noretry;


	for (i = 0; i < h->max_huge_pages; ++i) {
    //如果巨型页长度超过页分配器支持的最大阶数,那么从引导内存分配器分配巨型页
		if (hstate_is_gigantic(h)) {
			if (!alloc_bootmem_huge_page(h))
				break;
		} else if (!alloc_pool_huge_page(h,//如果巨型页长度小于或等于页分配器支持的最大阶数,那么从页分配器分配巨型页。
					 &node_states[N_MEMORY],
					 node_alloc_noretry))
			break;
		cond_resched();
	}

}

2)通过文件/proc/sys/vm/nr_hugepages 指定默认长度永久巨型页的数量。

处理函数hugetlb_sysctl_handler,调用如下hugetlb_sysctl_handler->hugetlb_sysctl_handler_common->__nr_hugepages_store_common->set_max_huge_pages 

最终通过set_max_huge_pages 来增加或减少巨型页。


四、透明巨型页

对进程是透明的,如果虚拟内存区域足够大,并且允许使用巨型页,那么内核在分配内存的时候首先选择巨型页,如果巨型页分配失败,回退分配普通页。

透明巨型页的配置

  1. CONFIG_TRANSPARENT_HUGEPAGE:支持透明巨型页;
  2. CONFIG_TRANSPARENT_HUGEPAGE_ALWAYS:总是使用透明巨型页;
  3. CONFIG_TRANSPARENT_HUGEPAGE_MADVISE:只有在进程使用madvise(MADV_HUGEPAGE)指定的虚拟地址范围内使用透明巨型页
  4. CONFIG_TRANSPARENT_HUGE_PAGECACHE:文件系统的缓存页使用透明巨型页。

开机引导参数设置透明巨型页的使用

  • transparent_hugepage=always
  • transparent_hugepage=madvise
  • transparent_hugepage=never

运行过程中设置透明巨型页

//总是使用
echo always > /sys/kernel/mm/transparent_hugepage/enabled 
//只有进程使用madvise(MADV_HUGEPAGE)指定的虚拟地址范围内使用透明巨型页
echo madvise > /sys/kernel/mm/transparent_hugepage/enabled 
//禁止使用透明巨型页
echo never > /sys/kernel/mm/transparent_hugepage/enabled 
root@ubuntu:~# ls -l /sys/kernel/mm/transparent_hugepage/
total 0
-rw-r--r-- 1 root root 4096 Jul 24 05:46 defrag
-rw-r--r-- 1 root root 4096 Jul 24 05:47 enabled
drwxr-xr-x 2 root root    0 Jul 24 05:47 khugepaged
-rw-r--r-- 1 root root 4096 Jul 24 05:47 use_zero_page

透明页分配失败,可以采取以下措施设置defrag 消除内存碎片,直接回收,异步回收,madvise,madvise+异步回收,不采取措施;



root@ubuntu:~# ls -l /sys/kernel/mm/transparent_hugepage/khugepaged/
total 0
-rw-r--r-- 1 root root 4096 Jul 24 05:47 alloc_sleep_millisecs
-rw-r--r-- 1 root root 4096 Jul 24 05:47 defrag
-r--r--r-- 1 root root 4096 Jul 24 05:47 full_scans
-rw-r--r-- 1 root root 4096 Jul 24 05:47 max_ptes_none
-r--r--r-- 1 root root 4096 Jul 24 05:47 pages_collapsed
-rw-r--r-- 1 root root 4096 Jul 24 05:47 pages_to_scan
-rw-r--r-- 1 root root 4096 Jul 24 05:47 scan_sleep_millisecs

pages_to_scan配置每次扫描多少页,默认值是一个巨型页包含的普通页数量的8倍

scan_sleep_millisecs配置两次扫描的时间间隔,单位是毫秒,默认值是10

系统调用madvise针对透明巨型页提供了两个linux私有的建议值

1)  MADV_HUGEPAGE 表示指定的虚拟地址范围允许使用透明巨型页

2)MADV_NOHUGEPAGE表示指定的虚拟地址范围不要合并成巨型页


《linux内核深度解析 (余华兵) 》

Logo

更多推荐