页面是Linux管理内存的基本单位,一般为4KB。如果程序运行时,需要大量的内存,就会产生非常多的TLB未命中和缺页异常,4KB的尺寸显然称为程序的瓶颈。如果直接修改系统默认页面大小,那么系统中其他程序运行时,很可能又会造成内存浪费。所以,Linux引入了巨型页,这种巨型页允许管理远大于4k的大页面,默认是2M,相当于512个普通页面。简而言之,通过启用大页面,系统可以处理更少的页面表,因此访问/维护它们的开销也更少!
巨型页的应用集中在对内存需求大的领域,比如数据库、虚拟机等系统中。
回顾下页面寻址,详细内容可参见 《页表》 。如下图
为了加快速度,采用了分段机制映射,如下图,而在2级转换目录之后,可以直接指向2MB的巨型页。在1级转换目录之后更是可以指向1GB的超大巨型页。
巨型页的实现需要CPU和内核的支持,在2中讲了通过块描述符来支持巨型页的方式,就是说一个块描述符就可以指向一个巨型页,不过粒度太粗了。这里讲一下另一种实现方式,通过页/块描述符的连续位组成一个集合,而用这个集合表示一个巨型页。也就是说,进程申请了一个巨型页,那么内核会申请n页连续的物业内存,当然这些也会被缓存到TLB中。具体说,每个页表项中都有一个连续标志位,当MMU(内存管理单元),访问其中任意一个页表项时,就会把N个页表项合并,并且填充到TLB中。这种方式就可以支持更多尺寸的页表(注意1级页表的块描述符不能使用连续位,也就是说页面4K时,不支持大于1GB的巨型页)
举两个例子
如果2级转换表中的块描述符支持16个连续块(一块2M),那么它就可以支持16*2MB=32MB的巨型页。
如果32级转换表中的页描述符支持16个连续页(一页4K),那么它就可以支持16*4KB=64KB的巨型页。
每个巨型页的长度并不总是相同尺寸的。比如x86 CPU具有支持4K和2M页面,而ia64支持4K,8K,64K, 256K等。这些都是可以同时存在的。而内核通过巨型页池(数组)来管理这些巨型页。相关源码如下
mm/hugetlb.c
int hugepages_treat_as_movable;// 巨型页池的数量
int hugetlb_max_hstate __read_mostly;
unsigned int default_hstate_idx;// 巨型页池的索引
struct hstate hstates[HUGE_MAX_HSTATE];//巨型页池数组
从内存管理的角度来看,巨型页分两种
内核中有一个hugetlb的模块负责巨型页的管理,而hugetlbfs基于hugetlb,在加载时会向内核注册hugetlbfd文件系统,结果允许保存到hugetlbfs_vfs中。
/proc/sys/vm/nr_overcommit_hugepages 临时巨型页的数量
/proc/sys/nr_hugepages 巨型页池中永久巨型页的数量
grep Huge /proc/meminfo
其中,
配置有多种方式,可以修改启动项重新启动机器,也可以修改内核参数。这里以后者为例。
root@VM-0-16-ubuntu:/home/ubuntu# sudo echo 10 > /proc/sys/vm/nr_hugepages
root@VM-0-16-ubuntu:/home/ubuntu# grep Huge /proc/meminfo
AnonHugePages: 0 kB
ShmemHugePages: 0 kB
HugePages_Total: 10
HugePages_Free: 10
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB
root@VM-0-16-ubuntu:/home/ubuntu# sudo echo 100 > /proc/sys/vm/nr_hugepages
root@VM-0-16-ubuntu:/home/ubuntu# grep Huge /proc/meminfo
AnonHugePages: 0 kB
ShmemHugePages: 0 kB
HugePages_Total: 100
HugePages_Free: 100
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB
添加配置项目 vm.nr_hugepages=128
root@VM-0-16-ubuntu:/home/ubuntu# echo "vm.nr_hugepages=128 ">> /etc/sysctl.conf
root@VM-0-16-ubuntu:/home/ubuntu# sysctl -p
kernel.sysrq = 1
net.ipv6.conf.all.disable_ipv6 = 0
net.ipv6.conf.default.disable_ipv6 = 0
net.ipv6.conf.lo.disable_ipv6 = 0
kernel.printk = 5
vm.nr_hugepages = 128
root@VM-0-16-ubuntu:/home/ubuntu# grep Huge /proc/meminfo
AnonHugePages: 0 kB
ShmemHugePages: 0 kB
HugePages_Total: 128
HugePages_Free: 128
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB
root@VM-0-16-ubuntu:/home/ubuntu# cat /sys/kernel/mm/transparent_hugepage/enabled
always [madvise] never
启用就是 [always] madvise never
不启用时 always madvise [never]
注意方括号的位置
参考
[0] http://oenhan.com/linux-kernel-khugepaged
[1] https://de5uebd9xnqr0.cloudfront.net/services/what-is-huge-pages-in-linux/
[2] https://ke.qq.com/webcourse/3294666/103425320