一、大页内存与系统分页管理
大页内存的原理,涉及到操作系统的虚拟地址到物理地址的转换过程,操作系统为了能同时运行多个进程,会为每个进程提供一个虚拟的进程空间,在32位操作系统上,进程空间大小为4G,64位系统为2^64,进程访问的是虚拟内存地址,会通过页表转换成物理内存地址,即是操作系统的分页存储管理;转换过程涉及到一个硬件模块 - MMU 内存管理单元,MMU 即是 CPU 芯片中的一个硬件模块,负责虚拟内存地址管理转换、内存保护、中央处理器高速缓存的控制等;分页存储管理将进程的虚拟地址空间,分成若干个页,并为各页加以编号,相应地,物理内存空间也分成若干个块,同样加以编号;系统为了保证进程能在内存中找到虚拟页对应的实际物理块,为每个进程维护一个映像表,即页表;由于页表是存放在内存中的,这使 CPU 在每存取一个数据时,都要两次访问内存,第一次时访问内存中的页表,从中找到指定页的物理块号,再将块号与页内偏移拼接,以形成物理地址,第二次访问内存时,才是从第一次所得地址中获得所需数据;因此,采用这种方式将使计算机的处理速度降低近 1/2;为了提高地址转换速度,系统在 MMU 中增加一个具有并行查找能力的特殊高速缓存,用以存放当前访问的那些页表项,即快表(TLB),由于成本的关系,快表不可能做得很大,通常只存放 16~512 个页表项。
小页内存的问题:
对中小程序来说运行非常好,快表的命中率非常高,所以不会带来多少性能损失,但是当程序耗费的内存很大,而且快表命中率不高时,就会有性能问题;现代计算机系统,都支持非常大的虚拟地址空间(2^32~2^64),这导致页表变得非常庞大,而且还要求空间是连续的;由于程序的内存很大,如果程序的访存局部性不好,会导致快表一直缺失,每次都要访问页表;而且由于页表项非常多,而快表只能缓存几百页,即使程序的访存性能很好,在大内存情况下,快表缺失的概率也很大;另一方面为了解决空间连续问题,引入二级或者三级页表,但更加影响性能,因为快表缺失时,访问页表的次数由两次变为三次、四次或更多;(实际上默认用 4k 页时,Linux 采取 4 级页表来管理地址转换,4 级页表从高到低,依次为 pgd、pud、pmd、pte,最后的 pte 指向真正的 4kb 划分的物理页)。
标准大页与透明大页 THP:
标准大页管理,是预分配的方式;透明大页管理,是动态分配的方式。
大页优点:
1、减少页表大小,每个 HugePage 对应的是连续的 2MB 物理内存,这样 12GB 的物理内存只需要 48KB 的页表,与原来 4k 页的 24MB 相比减少很多;
2、HugePage 内存只能锁定在物理内存中,不能被交换到交换区,避免了交换引起的性能影响;
3、由于页表数量的减少,使得 TLB 命中率大大提高;
4、HugePage 的页表在各进程之间可以共享,也降低了 PageTable 的大小。
大页缺点:
1、标准大页要预先分配;
2、不够灵活,需要重启主机生效;
3、程序使用内存小,却申请了大页内存,会造成内存浪费,因为内存分配最小单位是页。
注意事项:
linux x86_64 系统只支持 2Mi 和 1Gi 两种大页。
二、kvm 虚机使用内存大页
kvm 虚机使用大页,指的是为虚机的 qemu 等进程分配使用宿主机的大页,提高 qemu 虚机进程性能,这会消耗宿主机的大页资源。
由于大页是针对系统的,虚机进程所消耗的宿主机的大页资源,由宿主机系统分配,而虚机内部应用程序消耗的大页,由虚机内部的系统去分配,因此宿主机的大页和虚机内系统的大页,是相互隔离的,不存在依赖,虚机内系统的大页不会消耗宿主机的大页,即使虚机进程没有分配大页资源,虚机内部系统也仍然可以分配大页给应用程序。
虚机配置大页,可以提高虚机的性能,虚机系统内配置大页,可以提高虚机内应用的性能,实践上可以尝试都开启。
注意事项:1、当在虚机内系统配置分配大页时,由于大页资源会占用部分虚机内存,而虚机又需要保证足够内存给一些系统进程,因此虚机需要有足够大的内存,才能确保内部大页生效。2、当虚机进程申请超过宿主机可用大页资源时,虚机仍然可以运行,但不会分配到大页。
三、k8s 使用内存大页
k8s 的 pod 及控制器,使用大页的方式,是先申请 node 上的大页资源,之后可以把大页资源提供给 pod 中的应用程序使用,如果应用程序使用时需要挂载大页资源,则需要同时在 pod 或控制器中添加 volumes 和 volumeMounts;
与虚机使用大页的区别是,虚机是 qemu 等虚机进程使用大页;而 pod 是先申请大页资源,再提供给 pod 中的应用使用。
namespace 可以配置大页限额,和 cpu 内存这些计算资源一样,通过 ResourceQuota 配置,用 hugepages-<size> 标记控制每个命名空间下的巨页使用量。
k8s 启用大页流程
1、node 宿主机预先分配好标准大页,禁用透明大页。
确认升级宿主 node 节点的内核到 4.18 或以上版本,根据以下 k8s 80452 issue,使用 3.10 内核时,从修改 2M 到 1G 大页时,会导致 k8s node kubelet 无法恢复 ready 状态,使用 4.18 内核则验证正常。
2、重启 node 或重启 kubelet 后,k8s 可以检测到大页资源 hugepages-2Mi 和 hugepages-1Gi。
3、pod 或控制器使用大页资源
在请求大页资源时,需要同时指定请求内存或 CPU 资源,否则创建会报错;
limits.memory、requests.memory 大小不需要和大页资源相同,没有依赖关系,属于不同的计算资源;
应用需要挂载大页时,可以为 pod 或控制器中添加大页挂载目录,只有一种大页挂载时用 medium: HugePages,同时有两种大页挂载时分别用 medium: HugePages-2Mi、medium: HugePages-1Gi;
如果 pod 申请大页资源超过 node 可用资源时,会进入 pending。
command: - sleep - inf volumeMounts: - mountPath : /hugepages name: hugepage resources: limits: hugepages-1Gi: 2Gi memory: 500Mi requests: memory: 200Mi volumes: - name : hugepage emptyDir: medium: HugePages |
k8s 同时使用 2Mi 和 1Gi 两种大页资源
开启两种大页和 HugePageStorageMediumSize 特性有关,该特性可通过在 kubelet 和 kube-apiserver 中开启,详细信息可通过 k8s 官方文档查阅该特性。
在 1.18 k8s 上,该特性处于 alpha 状态,暂不建议稳定使用。
四、kubevirt 创建大页虚机
虚机 virt-launcher pod 申请大页资源(可通过在 pod 中 /sys/fs/cgroup/hugetlb/ 目录查询大页资源),挂载在 pod 中 /dev/hugepages 目录,再分配给虚机进程使用。
另外目前在官方没有查到为虚机不同 numa 启用不同大小的大页资源的方式。
创建说明如下:
1、根据官方说明,node 节点内核小于 4.14 时,vmi 需要增加 annotation kubevirt.io/memfd: "false",否则报错:unsupported configuration: hugepages is not supported with memfd memory source。
2、内存参数增加 hugepages: pageSize: 1Gi。
由于目前业务虚机设置了 memory.guests = requests.memory,因此根据官方文档,还有以下限制:
3、memory.guests 不能超过 node 节点 allocatable 的大页总大小;
4、memory.guests 需要是单个大页大小的倍数,即能被整除;
5、memory.guests 需大于单个大页大小;
... runStrategy: Always template: metadata: annotations: kubevirt.io/memfd : "false" labels: ... memory: guest: 1Gi hugepages: pageSize: 1Gi resources: limits: cpu: "1" memory: 2Gi requests: cpu: 333m memory: 1Gi |