在前两期,“时间管理大师”教会了大家,如何在创建虚拟机的时候进行CPU的超分配,把1个CPU的物理HT超分配出多个虚拟机的vCPU。
链接在此
虚拟化与云计算技术硬核内幕 (19) —— 时间管理大师(上)
虚拟化与云计算技术硬核内幕 (20) —— 时间管理大师(下)
我们也留下了一个问题……
方老师的学生小E发现,在手头的KVM集群中,可用CPU和可用RAM的配比是1:4,如双路5218R服务器上有80个HT,而RAM有384GB,被操作系统占用了一些以后,还有320GB是可以分配给虚拟机使用的。恰巧,在用于部署应用的虚拟机中,最常见的vCPU和RAM比例也是1:4。这样,每台宿主机的CPU和RAM刚好分配完。
(不知道什么是CPU和RAM的同学请自行查阅计算机基础书籍,不要再半夜问方老师)
那么,如果小E在KVM集群开启了CPU超分,超分比为1:2,也就是1个HT可以作为2个vCPU分配给虚拟机,那么,如果还希望分配的虚拟机每个vCPU配置4GB RAM,小E会发现,此时宿主机可以分配160个vCPU,但可分配的RAM却还是只有320GB,无法满足vCPU和RAM按照1:4的配比分配。
小E又找到方老师,却发现方老师忙于几个亿的项目,以及给人面试,暂时没有时间解答问题,只好自己回去查资料。
果然,小E发现在KVM中,内存实际上也是可以超分配的。
最早的内存超分配方式,叫做虚拟内存(Virtual Memory)。略有历史的电脑玩家会发现,在Windows95以后的系统中,如果开启了较多的Word,Excel,Powerpoint窗口,再运行《帝国时代2》,《星际争霸·母巢之战》等大型游戏,游戏启动所需要的时间会显著增加。在等待的这段时间里,电脑的硬盘灯会不停闪烁,同时硬盘(如果是西部数据的硬盘)会发出电锯一般的声响。
这是因为,Windows95为代表的32位操作系统让CPU工作在保护模式。我们在《花名与破冰》一篇中提到,在保护模式下,应用程序看到的是操作系统为其分配的内存“虚拟地址”,访问虚拟地址时,会映射到实际的物理内存。
但是,我们注意到,32位处理器的地址长度也是32位的,其地址空间为2的32次方,也就是4GB,而在Windows95时代,计算机的每MB内存价格约为50元,实际整机配置的物理内存大小在8MB-64MB之间。显然,在计算机中,实际物理内存的量是远少于虚拟内存空间的。即使在物理内存成本大大降低的今天,也不可能为计算机配置了与虚拟地址寻址空间大小完全一致的物理内存。
(这是一位土豪的计算机,Pentium Pro处理器 64MB RAM的整机,在1995-1996年的价格为1.5万元以上,方老师那会儿只有小霸王学习机可玩)
虚拟地址空间和物理地址之间的差额,就是所谓的“虚拟内存”。实际上,这些虚拟内存是利用硬盘上的空间来存储内容的,在Linux系统下,这些空间被称为swap分区,而在windows系统下,这些空间被C盘根目录下的pagefile.sys占据。
那么,操作系统是怎么样实现,在应用程序访问“虚拟内存”的时候,从硬盘读取所需内容的呢?
让我们回忆一下前面学过的内容——
操作系统将虚拟内存划分为页(page),并将每个页到物理地址的映射关系写到CR3指向的映射表中,CPU指令访问虚拟地址时,CPU内部的MMU会查询映射表,在映射表中找到物理地址,并在内存总线上发出该物理地址的寻址请求。
我们故意留白的内容是,如果在映射表中找不到对应的物理内存地址呢?
事实上,如果我们对物理内存进行了超分配,如计算机中只有64MB内存,而操作系统中运行的各个应用却申请了128MB内存的场景,另外64MB分配给操作系统的虚拟内存地址,在映射表中是找不到的。那么,当CPU执行的指令访问这样一个地址的时候,会发生什么呢?
小E翻开了厚厚的《金瓶梅》《Intel 64 and IA32 Architectures Software Developers Manual》。
原来,Intel的x86处理器的异常中,有一类叫做fault,当CPU指令访问了页表中没有映射的地址,会产生page fault(缺页异常)。
在Linux中,用户态进程的缺页异常,会让处理器保存现场,调用handle_mm_fault 函数,在这个函数中,如果访问的地址是一个正确的地址,则操作系统会去swap分区寻找这块地址的内容,将其调换到一页物理内存中,并在页表中将这页物理内存的物理地址映射给虚拟地址,便于提供给用户态程序使用。
那么,操作系统在物理内存消耗殆尽的情况下,去哪里找出这页物理内存来呢?原来,操作系统会通过一定的算法,把其他的物理内存内容暂时写到swap分区中,腾挪出内存页来,给page fault时调度使用。这个过程叫做page swap (页交换)。
我们在上期还提到,在Linux中,每个虚拟机实际上是一个QEMU进程,那么,如果操作系统欺骗QEMU,为QEMU分配的内存,实际上有一部分是缺页的,临时存放到硬盘里面。当QEMU对应的虚拟机需要访问这块内存的时候,Linux操作系统再从硬盘中调换内存page,使得虚拟机能够访问到存放在虚拟内存里面的数据。
利用操作系统这一特性,我们就可以给虚拟机分配超过物理内存总量的内存了。
同时,我们发现了两个关键点:
- 操作系统发现内存不够用的时候,可以通过虚拟内存技术,欺骗应用或虚拟机说内存够用,实际上,操作系统会把虚拟内存的内容,偷偷存在硬盘上。当应用或虚拟机要访问这段内存地址的时候,操作系统会在page fault处理程序中,从硬盘上把内容读到物理内存里面,同时把其他应用或虚拟机的内存内容挤出去存到硬盘上;
- 这种行为虽然看起来能分配比物理内存大得多的内存,但却会让发生内存交换的应用阻塞在page fault及磁盘IO上,从而降低执行效率;
那么,有没有不会降低执行效率,并且能够为虚拟机超量分配内存的办法呢?
这个问题小E打算下期再去问方老师。
(实际上我们在本期描述page fault的段落故意偷偷忽略了一个细节,想知道的同学请不要错过下期)