[原] KVM 虚拟化原理探究(4)— 内存虚拟化
KVM 虚拟化原理探究(4)— 内存虚拟化
标签(空格分隔): KVM
内存虚拟化简介
前一章介绍了CPU虚拟化的内容,这一章介绍一下KVM的内存虚拟化原理。可以说内存是除了CPU外最重要的组件,Guest最终使用的还是宿主机的内存,所以内存虚拟化其实就是关于如何做Guest到宿主机物理内存之间的各种地址转换,如何转换会让转换效率更高呢,KVM经历了三代的内存虚拟化技术,大大加快了内存的访问速率。
传统的地址转换
在保护模式下,普通的应用进程使用的都是自己的虚拟地址空间,一个64位的机器上的每一个进程都可以访问0到2^64的地址范围,实际上内存并没有这么多,也不会给你这么多。对于进程而言,他拥有所有的内存,对内核而言,只分配了一小段内存给进程,待进程需要更多的进程的时候再分配给进程。
通常应用进程所使用的内存叫做虚拟地址,而内核所使用的是物理内存。内核负责为每个进程维护虚拟地址到物理内存的转换关系映射。
首先,逻辑地址需要转换为线性地址,然后由线性地址转换为物理地址。
逻辑地址 ==> 线性地址 ==> 物理地址
逻辑地址和线性地址之间通过简单的偏移来完成。
一个完整的逻辑地址 = [段选择符:段内偏移地址],查找GDT或者LDT(通过寄存器gdtr,ldtr)找到描述符,通过段选择符(selector)前13位在段描述符做index,找到Base地址,Base+offset就是线性地址。
为什么要这么做?据说是Intel为了保证兼容性。
逻辑地址到线性地址的转换在虚拟化中没有太多的需要介绍的,这一层不存在实际的虚拟化操作,和传统方式一样,最重要的是线性地址到物理地址这一层的转换。
传统的线性地址到物理地址的转换由CPU的页式内存管理,页式内存管理。
页式内存管理负责将线性地址转换到物理地址,一个线性地址被分五段描述,第一段为基地址,通过与当前CR3寄存器(CR3寄存器每个进程有一个,线程共享,当发生进程切换的时候,CR3被载入到对应的寄存器中,这也是各个进程的内存隔离的基础)做运算,得到页表的地址index,通过四次运算,最终得到一个大小为4K的页(有可能更大,比如设置了hugepages以后)。整个过程都是CPU完成,进程不需要参与其中,如果在查询中发现页已经存在,直接返回物理地址,如果页不存在,那么将产生一个缺页中断,内核负责处理缺页中断,并把页加载到页表中,中断返回后,CPU获取到页地址后继续进行运算。
KVM中的内存结构
由于qemu-kvm进程在宿主机上作为一个普通进程,那对于Guest而言,需要的转换过程就是这样。
Guest虚拟内存地址(GVA)
|
Guest线性地址
|
Guest物理地址(GPA)
| Guest
------------------
| HV
HV虚拟地址(HVA)
|
HV线性地址
|
HV物理地址(HPA)
What's the fu*k ?这么多...
别着急,Guest虚拟地址到HV线性地址之间的转换和HV虚拟地址到线性地址的转换过程可以省略,这样看起来就更清晰一点。
Guest虚拟内存地址(GVA)
|
Guest物理地址(GPA)
| Guest
------------------
| HV
HV虚拟地址(HVA)
|
HV物理地址(HPA)
前面也说到KVM通过不断的改进转换过程,让KVM的内存虚拟化更加的高效,我们从最初的软件虚拟化的方式介绍。
软件虚拟化方式实现
第一层转换,由GVA->GPA的转换和传统的转换关系一样,通过查找CR3然后进行页表查询,找到对应的GPA,GPA到HVA的关系由qemu-kvm负责维护,我们在第二章KVM启动过程的demo里面就有介绍到怎样给KVM映射内存,通过mmap的方式把HV的内存映射给Guest。
struct kvm_userspace_memory_region region = {
.slot = 0,
.guest_phys_addr = 0x1000,
.memory_size = 0x1000,
.userspace_addr = (uint64_t)mem,
};
可以看到,qemu-kvm的kvm_userspace_memory_region结构体描述了guest的物理地址起始位置和内存大小,然后描述了Guest的物理内存在HV的映射userspace_addr
,通过多个slot,可以把不连续的HV的虚拟地址空间映射给Guest的连续的物理地址空间。
软件模拟的虚拟化方式由qemu-kvm来负责维护GPA->HVA的转换,然后再经过一次HVA->HPA的方式,从过程上来看,这样的访问是很低效的,特别是在当GVA到GPA转换时候产生缺页中断,这时候产生一个异常Guest退出,HV捕获异常后计算出物理地址(分配新的内存给Guest),然后重新Entry。这个过程会可能导致频繁的Guest退出,且转换过程过长。于是KVM使用了一种叫做影子页表的技术。
影子页表的虚拟化方式
影子页表的出现,就是为了减少地址转换带来的开销,直接把GVA转换到HVP的技术。在软件虚拟化的内存转换中,GVA到GPA的转换通过查询CR3寄存器来完成,CR3保存了Guest中的页表基地址,然后载入MMU来做地址转换。
在加入了影子页表的技术后,当访问到CR3寄存器的时候(可能是由于Guest进程后导致的),KVM捕获到这个操作,CPU虚拟化章节 EXIT_REASON_CR_ACCESS,qemu-kvm通过载入特俗的CR3和影子页表来欺骗Guest这个就是真实的CR3,后面的操作就和传统的访问内存的方式一致,当需要访问物理内存的时候,只会经过一层的影子页表的转换。
影子页表由qemu-kvm进程维护,实际上就是一个Guest的页表到宿主机页表的映射,每一级的页表的hash值对应到qemu-kvm中影子页表的一个目录。在初次GVA->HPA的转换时候,影子页表没有建立,此时Guest产生缺页中断,和传统的转换过程一样,经过两次转换(VA->PA),然后影子页表记录GVA->GPA->HVA->HPA。这样产生GVA->GPA的直接关系,保存到影子页表中。
影子页表的引入,减少了GVA->HPA的转换过程,但是坏处在于qemu-kvm需要为Guest的每个进程维护一个影子页表,这将带来很大的内存开销,同时影子页表的建立是很耗时的,如果Guest进程过多,将导致频繁的影子页表的导入与导出,虽然用了cache技术,但是还是软件层面的,效率并不是最好,所以Intel和AMD在此基础上提供了硬件虚拟化技术。
EPT硬件加速的虚拟化方式
EPT(extended page table)可以看做一个硬件的影子页表,在Guest中通过增加EPT寄存器,当Guest产生了CR3和页表的访问的时候,由于对CR3中的页表地址的访问是GPA,当地址为空时候,也就是Page fault后,产生缺页异常,如果在软件模拟或者影子页表的虚拟化方式中,此时会有VM退出,qemu-kvm进程接管并获取到此异常。但是在EPT的虚拟化方式中,qemu-kvm忽略此异常,Guest并不退出,而是按照传统的缺页中断处理,在缺页中断处理的过程中会产生EXIT_REASON_EPT_VIOLATION,Guest退出,qemu-kvm捕获到异常后,分配物理地址并建立GVA->HPA的映射,并保存到EPT中,将EPT载入到MMU,下次转换时候直接查询根据CR3查询EPT表来完成GVA->HPA的转换。以后的转换都由硬件直接完成,大大提高了效率,且不需要为每个进程维护一套页表,减少了内存开销。
在笔者的测试中,Guest和HV的内存访问速率对比为3756MB/s对比4340MB/s。可以看到内存访问已经很接近宿主机的水平了。
总结
KVM内存的虚拟化就是一个将虚拟机的虚拟内存转换为宿主机物理内存的过程,Guest使用的依然是宿主机的物理内存,只是在这个过程中怎样减少转换带来的开销成为优化的主要点。
KVM经过软件模拟->影子页表->EPT的技术的进化,效率也越来越高。
[原] KVM 虚拟化原理探究(4)— 内存虚拟化的更多相关文章
- [原] KVM 虚拟化原理探究(5)— 网络IO虚拟化
KVM 虚拟化原理探究(5)- 网络IO虚拟化 标签(空格分隔): KVM IO 虚拟化简介 前面的文章介绍了KVM的启动过程,CPU虚拟化,内存虚拟化原理.作为一个完整的风诺依曼计算机系统,必然有输 ...
- [原] KVM 虚拟化原理探究(1)— overview
KVM 虚拟化原理探究- overview 标签(空格分隔): KVM 写在前面的话 本文不介绍kvm和qemu的基本安装操作,希望读者具有一定的KVM实践经验.同时希望借此系列博客,能够对KVM底层 ...
- [原] KVM 虚拟化原理探究 —— 目录
KVM 虚拟化原理探究 -- 目录 标签(空格分隔): KVM KVM 虚拟化原理探究(1)- overview KVM 虚拟化原理探究(2)- QEMU启动过程 KVM 虚拟化原理探究(3)- CP ...
- [原] KVM 虚拟化原理探究(6)— 块设备IO虚拟化
KVM 虚拟化原理探究(6)- 块设备IO虚拟化 标签(空格分隔): KVM [toc] 块设备IO虚拟化简介 上一篇文章讲到了网络IO虚拟化,作为另外一个重要的虚拟化资源,块设备IO的虚拟化也是同样 ...
- [原] KVM 虚拟化原理探究(3)— CPU 虚拟化
KVM 虚拟化原理探究(3)- CPU 虚拟化 标签(空格分隔): KVM [TOC] CPU 虚拟化简介 上一篇文章笼统的介绍了一个虚拟机的诞生过程,从demo中也可以看到,运行一个虚拟机再也不需要 ...
- [原] KVM 虚拟化原理探究(2)— QEMU启动过程
KVM 虚拟化原理探究- QEMU启动过程 标签(空格分隔): KVM [TOC] 虚拟机启动过程 第一步,获取到kvm句柄 kvmfd = open("/dev/kvm", O_ ...
- KVM 介绍(2):CPU 和内存虚拟化
学习 KVM 的系列文章: (1)介绍和安装 (2)CPU 和 内存虚拟化 (3)I/O QEMU 全虚拟化和准虚拟化(Para-virtulizaiton) (4)I/O PCI/PCIe设备直接分 ...
- KVM 内存虚拟化
内存虚拟化的概念 除了 CPU 虚拟化,另一个关键是内存虚拟化,通过内存虚拟化共享物理系统内存,动态分配给虚拟机.虚拟机的内存虚拟化很象现在的操作系统支持的虚拟内存方式,应用程序看到邻近的内存 ...
- 2017.4.28 KVM 内存虚拟化及其实现
概述 KVM(Kernel Virtual Machine) , 作为开源的内核虚拟机,越来越受到 IBM,Redhat,HP,Intel 等各大公司的大力支持,基于 KVM 的开源虚拟化生态系统也日 ...
随机推荐
- C#与C++的发展历程第三 - C#5.0异步编程巅峰
系列文章目录 1. C#与C++的发展历程第一 - 由C#3.0起 2. C#与C++的发展历程第二 - C#4.0再接再厉 3. C#与C++的发展历程第三 - C#5.0异步编程的巅峰 C#5.0 ...
- 【大型网站技术实践】初级篇:借助LVS+Keepalived实现负载均衡
一.负载均衡:必不可少的基础手段 1.1 找更多的牛来拉车吧 当前大多数的互联网系统都使用了服务器集群技术,集群即将相同服务部署在多台服务器上构成一个集群整体对外提供服务,这些集群可以是Web应用服务 ...
- node中的cmd规范
你应该熟悉nodejs模块中的exports对象,你可以用它创建你的模块.例如:(假设这是rocker.js文件) exports.name = function() { console.log('M ...
- CSS Position 定位属性
本篇文章主要介绍元素的Position属性,此属性可以设置元素在页面的定位方式. 目录 1. 介绍 position:介绍position的值以及辅助属性. 2. position 定位方式:介绍po ...
- JavaScript Array对象
介绍Js的Array 数组对象. 目录 1. 介绍:介绍 Array 数组对象的说明.定义方式以及属性. 2. 实例方法:介绍 Array 对象的实例方法:concat.every.filter.fo ...
- 【原】AFNetworking源码阅读(二)
[原]AFNetworking源码阅读(二) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇中我们在iOS Example代码中提到了AFHTTPSessionMa ...
- Javascript实用方法二
承接上一篇, Object keys object的keys方法能够获取一个给定对象的所有键(key/属性名)并以数组的形式返回.这个方法可以用于键的筛选.匹配等. var basket = { st ...
- spring注解源码分析--how does autowired works?
1. 背景 注解可以减少代码的开发量,spring提供了丰富的注解功能.我们可能会被问到,spring的注解到底是什么触发的呢?今天以spring最常使用的一个注解autowired来跟踪代码,进行d ...
- C#开发微信门户及应用(39)--使用微信JSSDK实现签到的功能
随着微信开逐步开放更多JSSDK的接口,我们可以利用自定义网页的方式来调用更多微信的接口,实现我们更加丰富的界面功能和效果,例如我们可以在页面中调用各种手机的硬件来获取信息,如摄像头拍照,GPS信息. ...
- 【深入Java虚拟机】之四:类加载机制
类加载过程 类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载.验证.准备.解析.初始化.使用和卸载七个阶段.它们开始的顺序如下图所示: 其中类加载的过程包括了加载.验 ...