当前位置:   article > 正文

内存虚拟化、内存复用、大页内存作用及详解

大页内存

  脏数据:在内存中发生变化还没有写回磁盘的数据(写数据或者更改数据时都是先将数据写到内存中,并非直接对硬盘进行操作,待刷新周期后内存数据会刷新到硬盘中)当应用程序在写数据到硬盘时,先写入到内存缓存中,默认30S后会写入硬盘。
赃页Dirty 在/proc/meninfo中可查看到

#查看1s内脏数据
watch -n 1 ‘cat proc/meninfo | grep Dirty’ 

# 刷脏页
sync 
  • 1
  • 2
  • 3
  • 4
  • 5

  脏数据必须回收,否则会丢数据。

1. 内存虚拟化

  操作系统对内存的认识与管理需要达成以下两点:
    ● 内存都是从物理地址0开始的
    ● 内存都是连续的

  内存虚拟化需要解决两个问题:
    ● 从物理地址0开始的:物理地址0只有一个,无法同时满足所有客户机从0开始的要求
    ● 地址连续:虽然可以分配连续的物理地址,但是内存使用效率不高,缺乏灵活性。

  内存虚拟化的解决方案:
    ● 软件虚拟化:通过映射表记录虚拟内存和物理内存的映射关系,由VMM软件记录
    ● 把物理机的真实物理内存统一管理,包装成多个虚拟机的内存给若干个虚拟机使用。KVM通过内存虚拟化共享物理系统内存,动态分配给虚拟机。

在这里插入图片描述

  H:Host、G:Guest、V:virtual、P:Physical、A:Address
  KVM中,虚拟机的物理内存即为qemu-kvm进程所占用的内存空间
  影子页表
  由于宿主机MMU不能直接装载客户机的页表来进行内存访问,所以当客户机访问宿主机物理内存时,需要经过多次地址转换。也即首先根据客户机页表把客户机虚拟地址(GVA)转换成客户机物理地址(GPA),然后再通过客户机物理地址(GPA)到宿主机虚拟地址(HVA)之间的映射转换成宿主机虚拟地址,最后再根据宿主机页表把宿主机虚拟地址(HVA)转换成宿主机物理地址(HPA)。而通过影子页表,则可以实现客户机虚拟地址到宿主机物理地址的直接转换。
  硬件虚拟化
  靠硬件EPG模块记录虚拟内存到物理内存的映射关系
Intel的CPU提供了EPT(Extended page Tables 扩展页表)技术,直接在硬件上支持GVA →GPA →HPA的地址转换,从而降低内存虚拟化实现的复杂度,也进一步提升了内存虚拟化性能。
AMD提供的类似技术要NPT,即Nested Page Tables

2. 内存复用

2.1. 内存共享

  虚拟机之间共享同一物理内存空间,此时虚拟机仅对内存做只读操作。当虚拟机需要对内存进行写操作时,开辟另一内存空间,并修改映射。
  虚拟机内存数据相同的部分在物理内存上是共享的,共享部分只读、不可写,任何一个虚拟机想要修改共享部分,需要申请新的位置,将新数据写到新位置,然后修改映射关系。
在这里插入图片描述
  不开启内存复用,就没有这里讲的内存共享技术,那么VM1写一个1到内存,VM2也写一个1到内存,两个相同的内存占用内存2份存储空间

2.2. 内存置换

  虚拟机长时间未访问的内存内容被置换到存储(内存置换盘)中,并建立映射,当虚拟机再次访问该内存内容时再置换回来。
  例如如下:123数据访问频率很高,4是冷数据访问频率很低,冷数据会刷盘临时存在存储中,但是一旦访问4,就需要先加载到内存,所以如果访问4就会很慢
在这里插入图片描述

2.3. 内存气泡

  Hypervisor通过内存气泡将较为空闲的虚拟机内存释放给内存使用率较高的虚拟机,从而提升内存利用率。
  某个虚拟机因为APP应用程序的开启申请了60G内存(申请到了创建虚拟机的设定值),然后又关闭了一些应用,因此就需要释放内存(但是只有开启了内存复用才会释放内存),释放出来的内存就可以给其他虚拟机使用。如果不开启内存复用就不会有内存气泡,那么就会导致虚拟机申请来的内存就是自己的,即使应用程序释放了虚拟机也不会释放出来。
在这里插入图片描述

  内存刷盘
  OS操作系统跟CPU说需要将B改为A,而B正好在10000扇区,然后操作系统又告诉CPU写一个A,正好A得写在扇区1,过一会操作系统又告诉CPU写一个C,正好C得写到扇区9000,又要在扇区8000写一个D,没有内存的话,那么指针就跟无头苍蝇似的在盘之间来回转,性能就会非常的差,所以有了内存后,数据写入到内存,然后对数据进行整合后再刷盘。

3. 大页内存

3.1. 大页内存原理

  x86(包括x86-32和x86-64)架构的CPU默认使用4KB大小的内存页面,但是它们也支持较大的内存页,如x86_64系统就支持2MB大小的大页(huge page)。Linux2.6及以上的内核都支持huge page。如果在系统中使用了huge page,则内存页的数量会减少,从而需要更少的页表(page table),节约了页表所占用的内存数量,并且所需的地址转换也减少了,TLB缓存失效的次数就减少了,从而提高了内存访问的性能。另外,由于地址转换(虚拟内存和物理内存地址的转换)所需的信息一般保存在CPU的缓存中,huge page的使用让地址转换信息减少,从而减少了CPU缓存的使用,减轻了CPU缓存的压力,让CPU缓存能更多地用于应用程序的数据缓存,也能够在整体上提升系统的性能。
  CPU是通过寻址来访问内存的。32位CPU的寻址宽度是0~0xFFFFFFFF ,计算后得到的大小是4G,也就是说可支持的物理内存最大是4G。但在实践过程中,碰到了这样的问题:程序需要使用4G内存,而可用物理内存小于4G,导致程序不得不降低内存占用。为了解决此类问题,现代CPU引入了MMU(Memory Management Unit 内存管理单元)。MMU的核心思想是利用虚拟地址替代物理地址,即CPU寻址时使用虚址,由MMU负责将虚址映射为物理地址。MMU的引入,解决了对物理内存的限制。这时对程序来说,就像自己在使用4G内存一样。

由于CPU只能使用物理内存地址,所以需要将虚拟内存地址转换为物理内存地址才能被CPU使用,这个转换过程由MMU(MemoryManagement Unit,内存管理单元)来完成。

  内存分页(Paging)是在使用MMU的基础上,提出的一种内存管理机制。它将虚拟地址和物理地址按固定大小(4K)分割成页(page)和页帧(page frame)。这种机制,从数据结构上,保证了访问内存的高效,并使OS能支持非连续性的内存分配。在上文中提到,虚拟地址与物理地址需要通过映射,才能使CPU正常工作。而映射就需要存储映射表。在现代CPU架构中,映射关系通常被存储在物理内存上一个被称之为页表(page table)的地方。页表是被存储在内存中的。我们知道CPU通过总线访问内存,肯定慢于直接访问寄存器的。为了进一步优化性能,现代CPU架构引入了TLB(Translation Lookaside Buffer),用来缓存一部分经常访问的页表内容。
在这里插入图片描述

  在 Linux 中,物理内存是以页为单位来管理的。默认页的大小为 4KB。 1MB 的内存能划分为 256 页,1GB 则等同于 256000 页。4KB大小的页面在 “分页机制” 提出的时候是合理的,因为当时的内存大小不过几十兆字节。然而当前计算机的物理内存容量已经增长到GB甚至TB 级别了,操作系统仍然以4KB大小为页面的基本单位的话,会导致CPU中MMU的页面空间不足以存放所有的地址条目,则会造成内存的浪费。
  虚拟地址转换物理地址的过程:打开MMU后,CPU访问的都是虚拟地址,当CPU访问一个虚拟地址的时候,会通过CPU内部的mmu来查询物理地址,MMU首先通过虚拟地址在tlb中查找,如果找到相应表项,直接获得物理地址;如果TLB没有找到,就会通过虚拟地址从页表基地址寄存器保存的页表基地址开始查询多级页表,最终查询到找到相应表项,会将表项缓存到TLB中,然后从表项中获得物理地址。
在这里插入图片描述

页表保存的是虚拟内存地址与物理内存地址的映射关系,MMU从页表中找到虚拟内存地址所映射的物理内存地址,然后把物理内存地址提交给CPU

  TLB是有限的,这点毫无疑问。当超出TLB的存储极限时,就会发生TLB miss。之后,OS就会命令CPU去访问内存上的页表。如果频繁的出现TLB miss,程序的性能会下降地很快。为了让TLB可以存储更多的页地址映射关系,我们的做法是调大内存分页大小。
  同时在Linux操作系统上运行内存需求量较大的应用程序时,采用的默认的4KB页面,将会产生较多TLB丢失(Translation Lookaside Buffer,用来缓存一部分经常访问的页表内容)和缺页中断,从而大大影响应用程序的性能。当操作系统以2MB甚至更大作为分页的单位时,将会大大减少页表丢失和缺页中断的数量,显著提高应用程序的性能。
  为了解决上述问题,自 Linux Kernel 2.6 起,引入了 Huge pages(巨型页)的概念,目的是通过使用大页内存来取代传统的4KB内存页面, 以适应越来越大的内存空间。Huge pages 有 2MB 和1GB两种规格,2MB大小(默认)适合用于GB级别的内存,而1GB大小适合用于TB级别的内存。

其实大页内存(HugePages)简单来说就是通过增大操作系统页的大小来减小页表,从而避免快表缺失。

案例说明:
  假设某台Host上有一共256GB内存,且我们配置大页内存大小为1GB,一共设置160个分页,那么这台Host上就有了160GB的大页内存,并剩余下96GB的非大页内存(256G-160G)。且这个Host上没有部署任何虚拟机。然后我们要按VM1到VM4的顺序拉起4个虚拟机,每个虚拟机规格定义中,分别如下:
  ● VM1:使用170GB大页内存
  ● VM2:使用130GB大页内存
  ● VM3:使用100GB非大页内存
  ● VM4:使用16GB非大页内存

  那么创建的结果如下:
  ● VM1创建会失败。因为系统虽然总共有256GB内存,但是大页内存只有160GB,小于VM1需求的170GB大页内存。所以会因为大页内存不足而导致创建失败。
  ● VM2创建可以成功。创建完成后,系统剩余30GB大页内存可用,96GB非大页内存可用。
  ● VM3创建会失败。此时系统剩余的总内存资源是126GB,但是非大页内存只有96GB,小于VM3需求的100GB非大页内存。所以会因为非大页内存不足而导致创建失败。
  ● VM4可以创建成功。创建完成后,系统剩余30GB大页内存可用,80GB非大页内存可用。
一般要求只保留FusionSphere OpenStack+UVP系统自身内存开销为非大页内存,剩余内存全部配置为大页内存。

3.2. 大页内存配置

  大页面配置需要连续的内存空间,因此在开机时就分配是最可靠的设置方式。配置大页面的参数有:

  • hugepages :在内核中定义了开机启动时就分配的永久大页面的数量。默认为 0,即不分配。只有当系统有足够的连续可用页时,分配才会成功。由该参数保留的页不能用于其他用途。

  • hugepagesz: 在内核中定义了开机启动时分配的大页面的大小。可选值为 2MB 和 1GB 。默认是 2MB 。

  • default_hugepagesz:在内核中定义了开机启动时分配的大页面的默认大小。

  查看 Linux 操作系统是否启动了大页内存,如果 HugePages_Total 为 0,意味着 Linux 没有设置或没有启用 Huge pages。

grep -i HugePages_Total /proc/meminfo
HugePages_Total:       0
  • 1
  • 2

  查看是否挂载了 hugetlbfs:

mount | grep hugetlbfs
hugetlbfs on /dev/hugepages type hugetlbfs (rw,relatime)
  • 1
  • 2

  如果没有挂载则手动挂载:

mkdir /mnt/huge_1GB
mount -t hugetlbfs nodev /mnt/huge_1GB

vim /etc/fstab
nodev /mnt/huge_1GB hugetlbfs pagesize=1GB 0 0
  • 1
  • 2
  • 3
  • 4
  • 5

  修改 grub2,例如为系统配置10 个1GB的大页面:

vim /etc/grub2.cfg
# 定位到 linux16 /vmlinuz-3.10.0-327.el7.x86_64 在行末追加
default_hugepagesz=1G hugepagesz=1G hugepages=10
  • 1
  • 2
  • 3

  如果系统不支持的的配置页大小,那么系统启动时会报错:
在这里插入图片描述
  配置大页面后,系统在开机启动时会首选尝试在内存中找到并预留连续的大小为 hugepages * hugepagesz 的内存空间。如果内存空间不满足,则启动会报错 Kernel Panic, Out of Memory 等错误。
  重启系统,查看更详细的大页内存信息

cat /proc/meminfo | grep -i Huge
AnonHugePages:   1433600 kB # 匿名 HugePages 数量
HugePages_Total: 0 # 分配的大页面数量
HugePages_Free: 0 # 没有被使用过的大页面数量
HugePages_Rsvd: 0 # 已经被分配预留但是还没有使用的大页面数目,应该尽量保持 HugePages_Free - HugePages_Rsvd = 0
HugePages_Surp: 0 # surplus 的缩写,表示大页内存池中大于 /proc/sys/vm/nr_hugepages 中值的大页面数量
Hugepagesize: 1048576 kB # 每个大页面的 Size,与 HugePages_Total 的相乘得到大页面池的总容量
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

  如果大页面的 Size 一致,则可以通过 /proc/meminfo 中的 Hugepagesize 和 HugePages_Total 计算出大页面所占内存空间的大小。这部分空间会被算到已用的内存空间里,即使还未真正被使用。因此,用户可能观察到下面现象:使用 free 命令查看已用内存很大,但 top 或者 ps 中看到 %mem 的使用总量加起来却很少。
  如果上述输出看见 Hugepagesize 已经设置成 1GB,但 HugePages_Total 还是为 0,那么需要修改内核参数设定大页面的数量

sysctl -w vm.nr_hugepages=10

或者:
echo 'vm.nr_hugepages = 10' > /etc/sysctl.conf 
sysctl -p
  • 1
  • 2
  • 3
  • 4
  • 5

  一般情况下,配置的大页面可能主要供特定的应用程序或服务使用,其他进程是无法共享这部分空间的(如 Oracle SGA)。 请根据系统物理内存和应用需求来设置合适的大小,避免大页面使用的浪费;以及造成其他进程因竞争剩余可用内存而出现内存溢出的错误,进而导致系统崩溃的现象。默认的,当存在大页面时,会在应用进程或者内核进程申请大页内存的时候,优先为它们分配大页面,大页面不足以分配时,才会分配传统的 4KB 页面。查看哪个程序在使用大页内存:

grep -e AnonHugePages /proc/*/smaps | awk '{if(2>4)print0}' | awk -F "/" '{print0;system("ps−fp"3)}'
  • 1

3.3. 透明大页THP

  Transparent Huge pages(THP,透明大页) 自 RHEL 6 开始引入。由于传统的 Huge pages 很难手动的管理,对于程序而言,可能需要修改很多的代码才能有效的使用。THP 的引入就是为了便于系统管理员和开发人员使用大页内存。THP 是一个抽象层,能够自动创建、管理和使用传统大页。操作系统将大页内存看作是一种系统资源,在 THP 开启的情况下,其他的进程也可以申请和释放大页内存。
  Huge pages和Transparent Huge pages在大页内存的使用方式上存在区别,前者是预分配的方式,而后者则是动态分配的方式,显然后者更适合程序使用。需要注意的是,THP 虽然方便,但在某些场景种仍然会建议我们关闭,这个需要结合实际应用场景慎重考虑。

  手动关闭THP:

echo never > /sys/kernel/mm/transparent_hugepage/enabled 
echo never > /sys/kernel/mm/transparent_hugepage/defrag

cat /sys/kernel/mm/transparent_hugepage/enabled
[always] madvise never

# - [always] 表示启用了 THP
# - [never] 表示禁用了 THP
# - [madvise] 表示只在 MADV_HUGEPAGE 标志的 VMA 中使用 THP
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

  永久关闭THP:

vim /etc/grub2.cfg

# 在 cmdline 追加:
transparent_hugepage=never
  • 1
  • 2
  • 3
  • 4

  大页面对内存的影响:
  为大页面分配的内存空间会被计算到已用内存空间中,即使它们还未真正被使用。因此,你可能观察到下面现象:使用 free 命令查看已用内存很大,但 top 或者 ps 指令中看到 %MEM 的使用总量加起来却很少。
例如:总内存为 32G,并且分配了 12G 大页面的 free 如下:

free -g total used free shared  buff/cache   available
Mem: 31 16 14 0 0 14
Swap: 3 0 3
  • 1
  • 2
  • 3

可以使用命令 top 输出, Shift+m 按内存使用量进行排序
可以使用命令 ps -eo uid,pid,rss,trs,pmem,stat,cmd,查看进程的内存使用量

  这种情况就导致了一个问题,如果盲目的去提高大页内存空间的占比,就很可能会出现胖的胖死,饿的饿死的问题。导致大页内存空间的浪费,因为普通程序是未必能够使用大页内存的。

  大页内存的性能问题:
  在页式虚拟存储器中,会在虚拟存储空间和物理主存空间都分割为一个个固定大小的页,为线程分配内存是也是以页为单位。比如:页的大小为 4K,那么 4GB 存储空间就需要 4GB/4KB=1M 条记录,即有 100 多万个 4KB 的页。我们可以相待,如果页太小了,那么就会产生大量的页表条目,降低了查询速度的同时还浪费了存放页面的主存空间;但如果页太大了,又会容易造成浪费,原因就跟段式存储管理方式一般。所以 Linux 操作系统默认的页大小就是 4KB,可以通过指令查看:

getconf PAGE_SIZE
4096
  • 1
  • 2

  但在某些对性能要求非常苛刻的场景中,页面会被设置得非常的大,比如:1GB、甚至几十 GB,这些页被称之为 “大页”(Huge Page)。大页能够提升性能的主要原因有以下几点:

  • 减少页表条目,加快检索速度。
  • 提升TLB快表的命中率,TLB一般拥有16 ~ 128个条目之间,也就是说当大页为1GB的时候,TLB能够对应16GB ~ 128GB之间的存储空间。

  值得注意的是,首先使用大页的同时一般会禁止主存-辅存页面交换,原因跟段式存储管理方式一样,大容量交换会让辅存读写成为 CPU 处理的瓶颈。再一个就是大页也会使得页内地址检索的速度变慢,所以并非是页面的容量越大越好,而是需要对应用程序进行大量的测试取得页面容量与性能的曲线峰值才对。

以上纯属个人的理解,如有不同理解,评论区大家可以一起讨论哈~

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小惠珠哦/article/detail/998955
推荐阅读
相关标签
  

闽ICP备14008679号