linux内核源码分析之巨型页
当运行内存需求量较大的应用程序时,如果使用长度为4KB的页,将会产生较多的TLB未命中和缺页异常,严重影响应用程序的性能。如果使用长度为2MB甚至更大的巨型页,可以大幅减少TLB未命中和缺页异常的数量,大幅提高应用程序的性能。这才是内核引入巨型页(HugePage)的真正原因。巨型页首先需要处理器能够支持,然后需要内核支持,内核有两种实现方式•使用hugetlbfs伪文件系统实现巨型页;•透明巨型
目录
一、巨型页概述
当运行内存需求量较大的应用程序时,如果使用长度为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 来增加或减少巨型页。
四、透明巨型页
对进程是透明的,如果虚拟内存区域足够大,并且允许使用巨型页,那么内核在分配内存的时候首先选择巨型页,如果巨型页分配失败,回退分配普通页。
透明巨型页的配置
- CONFIG_TRANSPARENT_HUGEPAGE:支持透明巨型页;
- CONFIG_TRANSPARENT_HUGEPAGE_ALWAYS:总是使用透明巨型页;
- CONFIG_TRANSPARENT_HUGEPAGE_MADVISE:只有在进程使用madvise(MADV_HUGEPAGE)指定的虚拟地址范围内使用透明巨型页
- 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内核深度解析 (余华兵) 》
更多推荐
所有评论(0)