qemu-kvn 内存虚拟化---ept

qemu-kvm内存虚拟化

内存虚拟化实际就是进行地址转换从客户机虚拟地址-->客户机物理地址-->宿主机的物理地址,转换实现有两种硬件内存虚拟化和软件影子页表方式, 下面主要分析基于intel ept硬件内存虚拟化实现,此实现主要做两件事情

1.开启ept功能2.构造转换页表。注意该页表构造采用动态方式(常说懒惰方式),就是不到完不得以情况不创建。此页表创建实现就是采用ept violation捕获,一步一步创建起来的,对人觉得十分费劲,但是机器喜欢做费劲事情。

我们还得从vcpu_enter_guest这个函数,可见此函数重要性,虚拟机每一次运行,都必须载入ept页表

static int vcpu_enter_guest(struct kvm_vcpu *vcpu)

{

r = kvm_mmu_reload(vcpu);

}

如果cr3内容无效,分配物理页作为ept页表根,此物理页地址作为cr3寄存器内容,也就是ept根目录,所有页表查询和转换基于cr3转换的,有效话不必分配了,直接使用。

int kvm_mmu_load(struct kvm_vcpu *vcpu)

{

int r;

r = mmu_topup_memory_caches(vcpu);

if (r)

goto out;

spin_lock(&vcpu->kvm->mmu_lock);

kvm_mmu_free_some_pages(vcpu);

spin_unlock(&vcpu->kvm->mmu_lock);

r = mmu_alloc_roots(vcpu);

spin_lock(&vcpu->kvm->mmu_lock);

mmu_sync_roots(vcpu);

spin_unlock(&vcpu->kvm->mmu_lock);

if (r)

goto out;

/* set_cr3() should ensure TLB has been flushed */

kvm_x86_ops->set_cr3(vcpu, vcpu->arch.mmu.root_hpa);

out:

return r;

}

获取EXIT_QUALIFICATION内容,了解ept violation退出的原因,原因有读,写等引起。

static int handle_ept_violation(struct kvm_vcpu *vcpu)

{

unsigned long exit_qualification;

gpa_t gpa;

int gla_validity;

exit_qualification = vmcs_readl(EXIT_QUALIFICATION);

if (exit_qualification & (1 << 6)) {

printk(KERN_ERR "EPT: GPA exceeds GAW!\n");

return -EINVAL;

}

gla_validity = (exit_qualification >> 7) & 0x3;

if (gla_validity != 0x3 && gla_validity != 0x1 && gla_validity != 0) {

printk(KERN_ERR "EPT: Handling EPT violation failed!\n");

printk(KERN_ERR "EPT: GPA: 0x%lx, GVA: 0x%lx\n",

(long unsigned int)vmcs_read64(GUEST_PHYSICAL_ADDRESS),

vmcs_readl(GUEST_LINEAR_ADDRESS));

printk(KERN_ERR "EPT: Exit qualification is 0x%lx\n",

(long unsigned int)exit_qualification);

vcpu->run->exit_reason = KVM_EXIT_UNKNOWN;

vcpu->run->hw.hardware_exit_reason = EXIT_REASON_EPT_VIOLATION;

return 0;

}

gpa = vmcs_read64(GUEST_PHYSICAL_ADDRESS);

trace_kvm_page_fault(gpa, exit_qualification);

return kvm_mmu_page_fault(vcpu, gpa & PAGE_MASK, 0);

}

VMM 将截获此故障,handle_ept_violation函数被调用,通过EPT的故障处理函数tdp_page_fault进行GPA到HPA处理。如果相应ept页表不存在,构建此页表。

static int tdp_page_fault(struct kvm_vcpu *vcpu, gva_t gpa,

u32 error_code)

{

pfn_t pfn;

int r;

int level;

gfn_t gfn = gpa >> PAGE_SHIFT;

unsigned long mmu_seq;

ASSERT(vcpu);

ASSERT(VALID_PAGE(vcpu->arch.mmu.root_hpa));

r = mmu_topup_memory_caches(vcpu);

if (r)

return r;

mmu_seq = vcpu->kvm->mmu_notifier_seq;

smp_rmb();

level = mapping_level(vcpu, gfn);

gfn &= ~(KVM_PAGES_PER_HPAGE(level) - 1);

pfn = gfn_to_pfn(vcpu->kvm, gfn);

if (is_error_pfn(pfn)) {

kvm_release_pfn_clean(pfn);

return is_fault_pfn(pfn) ? -EFAULT : 1;

}

spin_lock(&vcpu->kvm->mmu_lock);

if (mmu_notifier_retry(vcpu, mmu_seq))

goto out_unlock;

kvm_mmu_free_some_pages(vcpu);

r = __direct_map(vcpu, gpa, error_code & PFERR_WRITE_MASK,

level, gfn, pfn);

spin_unlock(&vcpu->kvm->mmu_lock);

return r;

out_unlock:

spin_unlock(&vcpu->kvm->mmu_lock);

kvm_release_pfn_clean(pfn);

return 0;

}

mmu_topup_memory_caches(vcpu)函数是qemu-kvm自己实现内存管理功能。

客户机物理地址转换为客户机物理页框号,将客户机物理页框号转换为宿主机物理地址页框号。

将客户机物理页框号转换为宿主机物理地址页框号分为两步

pfn_t gfn_to_pfn(struct kvm *kvm, gfn_t gfn)

{

unsigned long addr;

addr = gfn_to_hva(kvm, gfn);

if (kvm_is_error_hva(addr)) {

get_page(bad_page);

return page_to_pfn(bad_page);

}

return hva_to_pfn(kvm, addr);

}

客户机页框号转换为宿主机虚拟地址

unsigned long gfn_to_hva(struct kvm *kvm, gfn_t gfn)

{

struct kvm_memory_slot *slot;

gfn = unalias_gfn_instantiation(kvm, gfn);

slot = gfn_to_memslot_unaliased(kvm, gfn);

if (!slot || slot->flags & KVM_MEMSLOT_INVALID)

return bad_hva();

return (slot->userspace_addr + (gfn - slot->base_gfn) * PAGE_SIZE);

}

将宿主机虚拟地址转换为宿主机物理地址,并将宿主机物理地址装换为宿主机物理地址页框号,注意此转换可能设计宿主机物理页确页不存在,那么需要分配相应物理页

pfn_t hva_to_pfn(struct kvm *kvm, unsigned long addr)

{

struct page *page[1];

int npages;

pfn_t pfn;

might_sleep();

npages = get_user_pages_fast(addr, 1, 1, page);

if (unlikely(npages != 1)) {

struct vm_area_struct *vma;

down_read(&current->mm->mmap_sem);

vma = find_vma(current->mm, addr);

if (vma == NULL || addr < vma->vm_start ||

!(vma->vm_flags & VM_PFNMAP)) {

up_read(&current->mm->mmap_sem);

get_page(fault_page);

return page_to_pfn(fault_page);

}

pfn = ((addr - vma->vm_start) >> PAGE_SHIFT) + vma->vm_pgoff;

up_read(&current->mm->mmap_sem);

BUG_ON(!kvm_is_mmio_pfn(pfn));

} else

pfn = page_to_pfn(page[0]);

return pfn;

}

在ept页表相应页表项中设置客户机的物理地址。

大概过程如下:如果找到最终level的相应ept表项,设置物理地址。否则相应level不存在分配ept页表,把分配页表物理地址设置上一级level页表项中,重复该过程

最终level设置函数调用mmu_set_spte,中间level的设置函数为调用__set_spte 其实本质一样的,只不过相应表项内容的权限不一样。

static int __direct_map(struct kvm_vcpu *vcpu, gpa_t v, int write,

int level, gfn_t gfn, pfn_t pfn)

{

struct kvm_shadow_walk_iterator iterator;

struct kvm_mmu_page *sp;

int pt_write = 0;

gfn_t pseudo_gfn;

for_each_shadow_entry(vcpu, (u64)gfn << PAGE_SHIFT, iterator) {

if (iterator.level == level) {

mmu_set_spte(vcpu, iterator.sptep, ACC_ALL, ACC_ALL,

0, write, 1, &pt_write,

level, gfn, pfn, false, true);

++vcpu->stat.pf_fixed;

break;

}

if (is_shadow_present_pte(*iterator.sptep) &&

!is_large_pte(*iterator.sptep))

continue;

if (is_large_pte(*iterator.sptep)) {

rmap_remove(vcpu->kvm, iterator.sptep);

__set_spte(iterator.sptep, shadow_trap_nonpresent_pte);

kvm_flush_remote_tlbs(vcpu->kvm);

}

if (*iterator.sptep == shadow_trap_nonpresent_pte) {

pseudo_gfn = (iterator.addr & PT64_DIR_BASE_ADDR_MASK) >> PAGE_SHIFT;

sp = kvm_mmu_get_page(vcpu, pseudo_gfn, iterator.addr,

iterator.level - 1,

1, ACC_ALL, iterator.sptep);

if (!sp) {

pgprintk("nonpaging_map: ENOMEM\n");

kvm_release_pfn_clean(pfn);

return -ENOMEM;

}

__set_spte(iterator.sptep,

__pa(sp->spt)

| PT_PRESENT_MASK | PT_WRITABLE_MASK

| shadow_user_mask | shadow_x_mask);

}

}

return pt_write;

}

如果要2MB PMD重新覆盖的PTE页指针,需要取消与父母不可达PTE。

static void mmu_set_spte(struct kvm_vcpu *vcpu, u64 *sptep,

unsigned pt_access, unsigned pte_access,

int user_fault, int write_fault, int dirty,

int *ptwrite, int level, gfn_t gfn,

pfn_t pfn, bool speculative,

bool reset_host_protection)

{

int was_rmapped = 0;

int was_writeble = is_writeble_pte(*sptep);

int rmap_count;

if (is_rmap_spte(*sptep)) {

/*

* If we overwrite a PTE page pointer with a 2MB PMD, unlink

* the parent of the now unreachable PTE.

*/

if (level > PT_PAGE_TABLE_LEVEL &&

!is_large_pte(*sptep)) {

struct kvm_mmu_page *child;

u64 pte = *sptep;

child = page_header(pte & PT64_BASE_ADDR_MASK);

mmu_page_remove_parent_pte(child, sptep);

__set_spte(sptep, shadow_trap_nonpresent_pte);

kvm_flush_remote_tlbs(vcpu->kvm);

} else if (pfn != spte_to_pfn(*sptep)) {

pgprintk("hfn old %lx new %lx\n",

spte_to_pfn(*sptep), pfn);

rmap_remove(vcpu->kvm, sptep);

} else

was_rmapped = 1;

}

if (set_spte(vcpu, sptep, pte_access, user_fault, write_fault,

dirty, level, gfn, pfn, speculative, true,

reset_host_protection)) {

if (write_fault)

*ptwrite = 1;

kvm_x86_ops->tlb_flush(vcpu);

}

if (!was_rmapped && is_large_pte(*sptep))

++vcpu->kvm->stat.lpages;

page_header_update_slot(vcpu->kvm, sptep, gfn);

if (!was_rmapped) {

rmap_count = rmap_add(vcpu, sptep, gfn);

kvm_release_pfn_clean(pfn);

if (rmap_count > RMAP_RECYCLE_THRESHOLD)

rmap_recycle(vcpu, sptep, gfn);

} else {

if (was_writeble)

kvm_release_pfn_dirty(pfn);

else

kvm_release_pfn_clean(pfn);

}

if (speculative) {

vcpu->arch.last_pte_updated = sptep;

vcpu->arch.last_pte_gfn = gfn;

}

}

static int set_spte(struct kvm_vcpu *vcpu, u64 *sptep,

unsigned pte_access, int user_fault,

int write_fault, int dirty, int level,

gfn_t gfn, pfn_t pfn, bool speculative,

bool can_unsync, bool reset_host_protection)

{

u64 spte;

int ret = 0;

/*

* We don‘t set the accessed bit, since we sometimes want to see

* whether the guest actually used the pte (in order to detect

* demand paging).

*/

spte = shadow_base_present_pte | shadow_dirty_mask;

if (!speculative)

spte |= shadow_accessed_mask;

if (!dirty)

pte_access &= ~ACC_WRITE_MASK;

if (pte_access & ACC_EXEC_MASK)

spte |= shadow_x_mask;

else

spte |= shadow_nx_mask;

if (pte_access & ACC_USER_MASK)

spte |= shadow_user_mask;

if (level > PT_PAGE_TABLE_LEVEL)

spte |= PT_PAGE_SIZE_MASK;

if (tdp_enabled)

spte |= kvm_x86_ops->get_mt_mask(vcpu, gfn,

kvm_is_mmio_pfn(pfn));

if (reset_host_protection)

spte |= SPTE_HOST_WRITEABLE;

spte |= (u64)pfn << PAGE_SHIFT;

if ((pte_access & ACC_WRITE_MASK)

|| (write_fault && !is_write_protection(vcpu) && !user_fault)) {

if (level > PT_PAGE_TABLE_LEVEL &&

has_wrprotected_page(vcpu->kvm, gfn, level)) {

ret = 1;

rmap_remove(vcpu->kvm, sptep);

spte = shadow_trap_nonpresent_pte;

goto set_pte;

}

spte |= PT_WRITABLE_MASK;

if (!tdp_enabled && !(pte_access & ACC_WRITE_MASK))

spte &= ~PT_USER_MASK;

/*

* Optimization: for pte sync, if spte was writable the hash

* lookup is unnecessary (and expensive). Write protection

* is responsibility of mmu_get_page / kvm_sync_page.

* Same reasoning can be applied to dirty page accounting.

*/

if (!can_unsync && is_writeble_pte(*sptep))

goto set_pte;

if (mmu_need_write_protect(vcpu, gfn, can_unsync)) {

pgprintk("%s: found shadow page for %lx, marking ro\n",

__func__, gfn);

ret = 1;

pte_access &= ~ACC_WRITE_MASK;

if (is_writeble_pte(spte))

spte &= ~PT_WRITABLE_MASK;

}

}

if (pte_access & ACC_WRITE_MASK)

mark_page_dirty(vcpu->kvm, gfn);

set_pte:

__set_spte(sptep, spte);

return ret;

}

sptep ept页表项指针,spte客户机物理地址

static void __set_spte(u64 *sptep, u64 spte)

{

#ifdef CONFIG_X86_64

set_64bit((unsigned long *)sptep, spte);

#else

set_64bit((unsigned long long *)sptep, spte);

#endif

}

时间: 2024-07-28 17:38:36

qemu-kvn 内存虚拟化---ept的相关文章

KVM 介绍(2):CPU 和内存虚拟化

学习 KVM 的系列文章: (1)介绍和安装 (2)CPU 和 内存虚拟化 (3)I/O 虚拟化 (4)virtio 介绍 (5)libvirt 介绍 (6)OpenStack 和 KVM 1. 为什么需要 CPU 虚拟化 X86 操作系统是设计在直接运行在裸硬件设备上的,因此它们自动认为它们完全占有计算机硬件.x86 架构提供四个特权级别给操作系统和应用程序来访问硬件. Ring 是指 CPU 的运行级别,Ring 0是最高级别,Ring1次之,Ring2更次之…… 就 Linux+x86 来

内存虚拟化

一.内存虚拟化的产生 内存虚拟化的产生源于VMM与客户系统在对物理内存的认识上存在冲突,造成物理内存真正拥有者-VMM必须对系统访问的内存进行一定程度上的虚拟化. 先看非虚拟化环境: ·指令对内存的访问通过处理器来转发>>>>处理器将解码后的请求放到总线上>>>>芯片组负责转发. 为了唯一标示,处理器将采用统一编址的方式将物理内存映射成为一个地址空间(物理地址空间). 1)操作系统会假定内存地址从0开始. 2)内存是连续的或者说在一些大的粒度(比如 256M

CPU 和内存虚拟化原理 - 每天5分钟玩转 OpenStack(6)

前面我们成功地把 KVM 跑起来了,有了些感性认识,这个对于初学者非常重要.不过还不够,我们多少得了解一些 KVM 的实现机制,这对以后的工作会有帮助. CPU 虚拟化 KVM 的虚拟化是需要 CPU 硬件支持的.还记得我们在前面的章节讲过用命令来查看 CPU 是否支持KVM虚拟化吗? [email protected]:~# egrep -o '(vmx|svm)' /proc/cpuinfo vmx 如果有输出 vmx 或者 svm,就说明当前的 CPU 支持 KVM.CPU 厂商 Inte

CPU,内存虚拟化技术

内容从<深度实践KVM>一书总结 CPU.内存虚拟化技术与应用场景 NUMA技术与应用1.SMP技术2.MPP模式3.NUMA技术(none Uniform memory access architecture)非一致性内存访问架构numactl --hardware :查看当前CPU硬件的情况 numastat -c qemu-kvm 关闭Linux系统的自动平衡:echo 0 > /proc/sys/kernel/numa_balancing虚拟机NUMA信息查看与配置 virsh

虚拟化的理论-内存和IO虚拟化

这部分只要介绍下虚拟化技术中的内存虚拟化和IO虚拟化两个部分 内存虚拟化: 上图中的做伴部分是正常情况下内存的使用情况,线性地址到物理地址的一个转换,通常应用程序使用的内存地址是线性地址,需要通过MMU地址转换的一个硬件来实现线性地址到物理地址的一个转换.上图的有半部分是虚拟化情况下内存是如何实现线性地址到物理地址的转换,虚拟机中的OS获得是有Host OS分配的线性地址,Guest OS中的应用程序获取的是相对于Guest os来说的线性地址,那么对于这个线性地址来说最终只能转换成线性地址,无

虚拟化技术原理(CPU、内存、IO)

本文来自:http://www.ywnds.com/?p=5856 虚拟化 云计算现在已经非常成熟了,而虚拟化是构建云计算基础架构不可或缺的关键技术之一. 云计算的云端系统, 其实质上就是一个大型的分布式系统. 虚拟化通过在一个物理平台上虚拟出更多的虚拟平台, 而其中的每一个虚拟平台则可以作为独立的终端加入云端的分布式系统. 比起直接使用物理平台, 虚拟化在资源的有效利用. 动态调配和高可靠性方面有着巨大的优势. 利用虚拟化, 企业不必抛弃现有的基础架构即可构建全新的信息基础架构,从而更加充分地

KVM 介绍(3):I/O 全虚拟化和准虚拟化 [KVM I/O QEMU Full-Virtualizaiton Para-virtualization]

学习 KVM 的系列文章: (1)介绍和安装 (2)CPU 和 内存虚拟化 (3)I/O QEMU 全虚拟化和准虚拟化(Para-virtulizaiton) (4)I/O PCI/PCIe设备直接分配和 SR-IOV (5)libvirt 介绍 (6)Nova 通过 libvirt 管理 QEMU/KVM 虚机 (7)快照 (snapshot) (8)迁移 (migration) 在 QEMU/KVM 中,客户机可以使用的设备大致可分为三类: 1. 模拟设备:完全由 QEMU 纯软件模拟的设备

I/O 全虚拟化和准虚拟化 [KVM I/O QEMU Full-Virtualizaiton Para-virtualization]

KVM 介绍(3):I/O 全虚拟化和准虚拟化 [KVM I/O QEMU Full-Virtualizaiton Para-virtualization] 学习 KVM 的系列文章: (1)介绍和安装 (2)CPU 和 内存虚拟化 (3)I/O QEMU 全虚拟化和准虚拟化(Para-virtulizaiton) (4)I/O PCI/PCIe设备直接分配和 SR-IOV (5)libvirt 介绍 (6)Nova 通过 libvirt 管理 QEMU/KVM 虚机 (7)快照 (snapsh

KVM硬件辅助虚拟化之 EPT(Extended Page Table)

传统OS环境中,CPU对内存的访问都必须通过MMU将虚拟地址VA转换为物理地址PA从而得到真正的Physical Memory Access,即:VA->MMU->PA,见下图. 虚拟运行环境中由于Guest OS所使用的物理地址空间并不是真正的物理内存,而是由VMM供其所使用一层虚拟的物理地址空间,为使MMU能够正确的转换虚实地址,Guest中的地址空间的转换和访问都必须借助VMM来实现,这就是内存虚拟化的主要任务,即:GVA->MMU Virtualation->HPA,见下图