虚拟化原理之KVM

2023-11-06

2.1 kvm技术基础

KVM(kernel-based virtual machine)的名字,基于kernel的虚拟机,已经很准确的说出了kvm的设计思路:也就是依赖linux内核,完全利用linux内核来实现cpu的调度,内存管理的功能。而另一个开源虚拟机xen,则自己开发了一套底层操作系统功能。从vcpu调度到内存管理一应俱全。虽然xen这个系统也是基于linux的,但是发展路线不同,和目前linux内核相比,已经面目全非了。这就是kvm受到开源组织的欢迎,而xen一直被排斥的根源。

虽然说早期的kvm是全虚拟化,而xen是半虚拟化,但发展到今天,xen支持全虚拟化,而kvm也早就有了半虚拟化的patch。技术上可以互相渗透,而软件架构一旦确定了,反而难改。不能因为xen是半虚拟化,就认为linux内核排斥半虚拟化的方案。实际上,另一个进了内核的开源虚拟机Lguest,它就是一个半虚拟化的方案。当然,现在linux内核本身都推出了半虚拟化架构,做半虚拟化也没以前那么繁琐了。

另一个趋势是基于硬件的虚拟化成为主流。早期x86虚拟化的低性能让人印象深刻,所以在intel推出硬件辅助虚拟化之后,虚拟化方案全面向硬件辅助靠拢。而kvm,Lguest这些比较新的方案,则彻底不支持软件的方案,而把硬件辅助当作了设计的根基。

从软件架构上来说,kvm提供了两个内核模块,使用kvm的io_ctl接口可以管理vcpu和内存,为vcpu注入中断和提供时钟信号,而kvm本身没有提供设备的模拟。设备模拟需要应用层软件Qemu来实现。这种架构保证了kvm避免了繁琐的设备模拟和设备驱动部分(内核中80%以上的代码就是驱动部分)。

总结一下kvm软件的架构特点:

q Kvm本身只提供两个内核模块。Kvm实现了vcpu和内存的管理。

q Qemu控制逻辑,负责创建虚拟机,创建vcpu。

 

2.2 Kvm管理接口

Qemu和kvm关系很深,甚至可以认为双方本来是一个软件,Qemu是应用层的控制部分,而kvm是内核执行部分。软件复用能达到如此天衣无缝的地步,是一件很神奇的事情,也说明kvm设计时候的思路之巧。

所以分析kvm,必须首先从Qemu的代码分析入手。为了避免繁琐,引入太多知识点,而混杂不清。所以把Qemu的代码做简化处理。

代码清单2-1 Qemu启动代码

s->fd = qemu_open("/dev/kvm", O_RDWR);

ret = kvm_ioctl(s, KVM_GET_API_VERSION, 0);

s->vmfd = kvm_ioctl(s, KVM_CREATE_VM, 0);

...............................

ret = kvm_vm_ioctl(s, KVM_CREATE_VCPU, env->cpu_index);

.............................

env->kvm_fd = ret;

run_ret = kvm_vcpu_ioctl(env, KVM_RUN, 0);

可以看到,kvm提供了一个设备/dev/kvm,对kvm的控制要通过这个设备提供的io_ctl接口实现。这是linux内核提供服务的最通用方式,不再赘述。

而kvm提供了三种概念,分别通过不同的io_ctl接口来控制。

q kvm:代表kvm模块本身,用来管理kvm版本信息,创建一个vm。

q vm:代表一个虚拟机。通过vm的io_ctl接口,可以为虚拟机创建vcpu,设置内存区间,创建中断控制芯片,分配中断等等。

q vcpu:代表一个vcpu。通过vcpu的io_ctl接口,可以启动或者暂停vcpu,设置vcpu的寄存器,为vcpu注入中断等等。

Qemu的使用方式,首先是打开/dev/kvm设备,通过KVM_CREATE_VM创建一个虚拟机对象,然后通过KVM_CREATE_VCPU为虚拟机创建vcpu对象,最后通过KVM_RUN设置vcpu运行起来。因为是简化的代码,中断芯片的模拟,内存的模拟,寄存器的设置等等都已经省略了。

2.3 VT技术和vmcs结构

前文讲到kvm是基于硬件辅助虚拟化来实现的。这个硬件辅助的虚拟化技术,在不同的cpu架构中有不同的实现。在x86的平台下,intel实现了VT技术,而另一家x86芯片厂家AMD也推出了自己的虚拟化技术AMD-V。反映到代码上,intel技术的代码都在/arch/x86/kvm目录里面的vmx.c文件,而AMD的实现代码在相同目录的svm.c文件中。

回顾一下虚拟化技术的实现,经典的虚拟化使用了陷入-模拟的模式,而硬件辅助虚拟化引入了根模式(root operation)和非根模式(none-root operation),每种模式都有ring0-3的四级特权级别。所以,在硬件辅助虚拟化中,陷入的概念实际上被VM-EXIT操作取代了,它代表从非根模式退出到根模式,而从根模式切换到非根模式是VM-Entry操作。

2.3.1 需要具备的硬件知识

做系统软件的必须和硬件打交道,这就必须深入cpu架构和设备的架构。但是intel的架构浩大繁杂,说明文档多达上千页,深入了解着实有难度,另外一种趋势是软硬件的分离已经进行了多年,而系统软件的作者多半是软件人员,而非硬件人员。作为软件人员,了解必备的硬件知识是需要的,也是理解代码和架构的基础。同时,在操作系统软件的理解中,分清软件部分的工作和硬件部分的工作是必备条件,这也是操作系统软件中最让人困惑的部分。

对于虚拟化的vt技术而言,它的软件部分基本体现在vmcs结构中(virtual machine control block)。主要通过vmcs结构来控制vcpu的运转。

q Vmcs是个不超过4K的内存块。

q Vmcs通过下列的指令控制,vmclear:清空vmcs结构,vmread:读取vmcs数据,vmwrite:数据写入vmcs

q 通过VMPTR指针指向vmcs结构,该指针包含vmcs的物理地址。

Vmcs包含的信息可以分为六个部分。

q Guest state area:虚拟机状态域,保存非根模式的vcpu运行状态。当VM-Exit发生,vcpu的运行状态要写入这个区域,当VM-Entry发生时,cpu会把这个区域保存的信息加载到自身,从而进入非根模式。这个过程是硬件自动完成的。保存是自动的,加载也是自动的,软件只需要修改这个区域的信息就可以控制cpu的运转。

q Host state area:宿主机状态域,保存根模式下cpu的运行状态。只在vm-exit时需要将状态

q VM-Execution control filelds:包括page fault控制,I/O位图地址,CR3目标控制,异常位图,pin-based运行控制(异步事件),processor-based运行控制(同步事件)。这个域可以设置那些指令触发VM-Exit。触发VM-Exit的指令分为无条件指令和有条件指令,这里设置的是有条件指令。

q VM-entry contorl filelds:包括vm-entry控制,vm-entry MSR控制,VM-Entry插入的事件。MSR是cpu的模式寄存器,设置cpu的工作环境和标识cpu的工作状态。

q VM-exit control filelds:包括VM-Exit控制,VM-Exit MSR控制。

q VM退出信息:这个域保存VM-Exit退出时的信息,并且描述原因。

有了vmcs结构后,对虚拟机的控制就是读写vmcs结构。后面对vcpu设置中断,检查状态实际上都是在读写vmcs结构。在vmx.h文件给出了intel定义的vmcs结构的内容。

2.4 cpu虚拟化

2.4.1 Vcpu数据结构

struct kvm_vcpu {

struct kvm *kvm;

#ifdef CONFIG_PREEMPT_NOTIFIERS

struct preempt_notifier preempt_notifier;

#endif

int vcpu_id;

struct mutex mutex;

int cpu;

struct kvm_run *run;

unsigned long requests;

unsigned long guest_debug;

int fpu_active;

int guest_fpu_loaded;

wait_queue_head_t wq;

int sigset_active;

sigset_t sigset;

struct kvm_vcpu_stat stat;

#ifdef CONFIG_HAS_IOMEM

int mmio_needed;

int mmio_read_completed;

int mmio_is_write;

int mmio_size;

unsigned char mmio_data[8];

gpa_t mmio_phys_addr;

#endif

struct kvm_vcpu_arch arch;

};

这个结构定义了vcpu的通用结构,其中重点是kvm_vcpu_arch,这个是和具体cpu型号有关的信息。

struct kvm_vcpu_arch {

u64 host_tsc;

 

unsigned long regs[NR_VCPU_REGS];

u32 regs_avail;

u32 regs_dirty;

unsigned long cr0;

unsigned long cr2;

unsigned long cr3;

unsigned long cr4;

unsigned long cr8;

u32 hflags;

u64 pdptrs[4]; 

u64 shadow_efer;

u64 apic_base;

struct kvm_lapic *apic; 

int32_t apic_arb_prio;

int mp_state;

int sipi_vector;

u64 ia32_misc_enable_msr;

bool tpr_access_reporting;

struct kvm_mmu mmu;

 

struct kvm_pv_mmu_op_buffer mmu_op_buffer;

struct kvm_mmu_memory_cache mmu_pte_chain_cache;

struct kvm_mmu_memory_cache mmu_rmap_desc_cache;

struct kvm_mmu_memory_cache mmu_page_cache;

struct kvm_mmu_memory_cache mmu_page_header_cache;

gfn_t last_pt_write_gfn;

int last_pt_write_count;

u64 *last_pte_updated;

gfn_t last_pte_gfn;

struct {

gfn_t gfn; 

pfn_t pfn; 

unsigned long mmu_seq;

} update_pte;

struct i387_fxsave_struct host_fx_image;

struct i387_fxsave_struct guest_fx_image;

gva_t mmio_fault_cr2;

struct kvm_pio_request pio;

void *pio_data;

u8 event_exit_inst_len;

struct kvm_queued_exception {

bool pending;

bool has_error_code;

u8 nr;

u32 error_code;

} exception;

struct kvm_queued_interrupt {

bool pending;

bool soft;

u8 nr;

} interrupt;

int halt_request; 

int cpuid_nent;

struct kvm_cpuid_entry2 cpuid_entries[KVM_MAX_CPUID_ENTRIES];

 

struct x86_emulate_ctxt emulate_ctxt;

gpa_t time;

struct pvclock_vcpu_time_info hv_clock;

unsigned int hv_clock_tsc_khz;

unsigned int time_offset;

struct page *time_page;

bool singlestep; 

bool nmi_pending;

bool nmi_injected;

struct mtrr_state_type mtrr_state;

u32 pat;

int switch_db_regs;

unsigned long db[KVM_NR_DB_REGS];

unsigned long dr6;

unsigned long dr7;

unsigned long eff_db[KVM_NR_DB_REGS];

u64 mcg_cap;

u64 mcg_status;

u64 mcg_ctl;

u64 *mce_banks;

};

q 有寄存器信息,cr0,cr2,cr3等。

q 有内存mmu的信息,

q 有中断控制芯片的信息kvm_lapic

q 有io请求信息kvm_pio_request

q 有vcpu的中断信息interrupt

2.4.2 vcpu创建

首先是Qemu创建VM,从代码分析一下:

代码清单2-2 V

static int kvm_dev_ioctl_create_vm(void)

{

int fd;

struct kvm *kvm;

kvm = kvm_create_vm();

if (IS_ERR(kvm))

return PTR_ERR(kvm);

 

fd = anon_inode_getfd("kvm-vm", &kvm_vm_fops, kvm, 0);

if (fd < 0)

kvm_put_kvm(kvm);

return fd;

}

调用了函数kvm_create_vm,然后是创建一个文件,这个文件作用是提供对vm的io_ctl控制。

代码清单2-3 V

static struct kvm *kvm_create_vm(void)

{

struct kvm *kvm = kvm_arch_create_vm();

 

kvm->mm = current->mm;

atomic_inc(&kvm->mm->mm_count);

spin_lock_init(&kvm->mmu_lock);

spin_lock_init(&kvm->requests_lock);

kvm_io_bus_init(&kvm->pio_bus);

kvm_eventfd_init(kvm);

mutex_init(&kvm->lock);

mutex_init(&kvm->irq_lock);

kvm_io_bus_init(&kvm->mmio_bus);

init_rwsem(&kvm->slots_lock);

atomic_set(&kvm->users_count, 1);

spin_lock(&kvm_lock);

 

list_add(&kvm->vm_list, &vm_list);

spin_unlock(&kvm_lock);

return kvm;

}

可以看到,这个函数首先是申请一个kvm结构。然后执行初始化工作。

初始化第一步是把kvm的mm结构设置为当前进程的mm。我们知道,mm结构反应了整个进程的内存使用情况,也包括进程使用的页目录信息。

然后是初始化io bus和eventfd。这两者和设备io有关。

最后把kvm加入到一个全局链表头。通过这个链表头,可以遍历所有的vm虚拟机。

创建VM之后,就是创建VCPU。

代码清单2-4 V

static int kvm_vm_ioctl_create_vcpu(struct kvm *kvm, u32 id)

{

int r;

struct kvm_vcpu *vcpu, *v;

 

vcpu = kvm_arch_vcpu_create(kvm, id);

if (IS_ERR(vcpu))

return PTR_ERR(vcpu);

preempt_notifier_init(&vcpu->preempt_notifier, &kvm_preempt_ops);

 

r = kvm_arch_vcpu_setup(vcpu);

if (r)

return r;

 

mutex_lock(&kvm->lock);

if (atomic_read(&kvm->online_vcpus) == KVM_MAX_VCPUS) {

r = -EINVAL;

goto vcpu_destroy;

}

 

kvm_for_each_vcpu(r, v, kvm)

if (v->vcpu_id == id) {

r = -EEXIST;

goto vcpu_destroy;

}

 

 

kvm_get_kvm(kvm);

r = create_vcpu_fd(vcpu);

if (r < 0) {

kvm_put_kvm(kvm);

goto vcpu_destroy;

}

kvm->vcpus[atomic_read(&kvm->online_vcpus)] = vcpu;

smp_wmb();

atomic_inc(&kvm->online_vcpus);

mutex_unlock(&kvm->lock);

return r;

vcpu_destroy:

mutex_unlock(&kvm->lock);

kvm_arch_vcpu_destroy(vcpu);

return r;

}

从代码可见,分别调用相关cpu提供的vcpu_create和vcpu_setup来完成vcpu创建。

Intel的vt技术和amd的svm技术所提供的vcpu调用各自不同。我们集中在intel的vt技术,

而省略AMD的SVM。

代码清单2-5 vmx_create_vcpu

static struct kvm_vcpu *vmx_create_vcpu(struct kvm *kvm, unsigned int id)

{

int err;

 

struct vcpu_vmx *vmx = kmem_cache_zalloc(kvm_vcpu_cache, GFP_KERNEL);

int cpu;

.......................................

err = kvm_vcpu_init(&vmx->vcpu, kvm, id);

 

vmx->guest_msrs = kmalloc(PAGE_SIZE, GFP_KERNEL);

vmx->host_msrs = kmalloc(PAGE_SIZE, GFP_KERNEL);

 

vmx->vmcs = alloc_vmcs();

vmcs_clear(vmx->vmcs);

cpu = get_cpu();

vmx_vcpu_load(&vmx->vcpu, cpu);

 

err = vmx_vcpu_setup(vmx);

vmx_vcpu_put(&vmx->vcpu);

put_cpu();

if (vm_need_virtualize_apic_accesses(kvm))

if (alloc_apic_access_page(kvm) != 0)

goto free_vmcs;

return &vmx->vcpu;

}

首先申请一个vcpu_vmx结构,然后初始化vcpu_vmx包含的mmu,仿真断芯片等等成员。

MSR寄存器是cpu模式寄存器,所以要分别为guest和host申请页面,这个页面要保存MSR寄存器的信息。然后申请一个vmcs结构。然后调用vmx_vcpu_setup设置vcpu工作在实模式。

代码清单2-6 vmx_vcpu_setup

static int vmx_vcpu_setup(struct vcpu_vmx *vmx)

{u32 host_sysenter_cs, msr_low, msr_high;

u32 junk;

u64 host_pat, tsc_this, tsc_base;

unsigned long a;

struct descriptor_table dt;

int i;

unsigned long kvm_vmx_return;

u32 exec_control;

 

vmcs_write32(PIN_BASED_VM_EXEC_CONTROL,

vmcs_config.pin_based_exec_ctrl);

exec_control = vmcs_config.cpu_based_exec_ctrl;

 

if (!enable_ept)

exec_control |= CPU_BASED_CR3_STORE_EXITING |

CPU_BASED_CR3_LOAD_EXITING |

CPU_BASED_INVLPG_EXITING;

vmcs_write32(CPU_BASED_VM_EXEC_CONTROL, exec_control);

if (cpu_has_secondary_exec_ctrls()) {

exec_control = vmcs_config.cpu_based_2nd_exec_ctrl;

if (!vm_need_virtualize_apic_accesses(vmx->vcpu.kvm))

exec_control &=

~SECONDARY_EXEC_VIRTUALIZE_APIC_ACCESSES;

if (vmx->vpid == 0)

exec_control &= ~SECONDARY_EXEC_ENABLE_VPID;

if (!enable_ept)

exec_control &= ~SECONDARY_EXEC_ENABLE_EPT;

if (!enable_unrestricted_guest)

exec_control &= ~SECONDARY_EXEC_UNRESTRICTED_GUEST;

vmcs_write32(SECONDARY_VM_EXEC_CONTROL, exec_control);

}

vmcs_write32(PAGE_FAULT_ERROR_CODE_MASK, !!bypass_guest_pf);

vmcs_write32(PAGE_FAULT_ERROR_CODE_MATCH, !!bypass_guest_pf);

vmcs_write32(CR3_TARGET_COUNT, 0); 

vmcs_writel(HOST_CR0, read_cr0()); 

vmcs_writel(HOST_CR4, read_cr4()); 

vmcs_writel(HOST_CR3, read_cr3()); 

vmcs_write16(HOST_CS_SELECTOR, __KERNEL_CS); 

vmcs_write16(HOST_DS_SELECTOR, __KERNEL_DS); 

vmcs_write16(HOST_ES_SELECTOR, __KERNEL_DS); 

vmcs_write16(HOST_FS_SELECTOR, kvm_read_fs()); 

vmcs_write16(HOST_GS_SELECTOR, kvm_read_gs()); 

vmcs_write16(HOST_SS_SELECTOR, __KERNEL_DS); 

vmcs_writel(HOST_FS_BASE, 0); 

vmcs_writel(HOST_GS_BASE, 0); 

vmcs_write16(HOST_TR_SELECTOR, GDT_ENTRY_TSS*8); 

kvm_get_idt(&dt);

vmcs_writel(HOST_IDTR_BASE, dt.base); 

asm("mov $.Lkvm_vmx_return, %0" : "=r"(kvm_vmx_return));

vmcs_writel(HOST_RIP, kvm_vmx_return); 

vmcs_write32(VM_EXIT_MSR_STORE_COUNT, 0);

vmcs_write32(VM_EXIT_MSR_LOAD_COUNT, 0);

vmcs_write32(VM_ENTRY_MSR_LOAD_COUNT, 0);

rdmsr(MSR_IA32_SYSENTER_CS, host_sysenter_cs, junk);

vmcs_write32(HOST_IA32_SYSENTER_CS, host_sysenter_cs);

rdmsrl(MSR_IA32_SYSENTER_ESP, a);

vmcs_writel(HOST_IA32_SYSENTER_ESP, a); 

rdmsrl(MSR_IA32_SYSENTER_EIP, a);

vmcs_writel(HOST_IA32_SYSENTER_EIP, a); 

if (vmcs_config.vmexit_ctrl & VM_EXIT_LOAD_IA32_PAT) {

rdmsr(MSR_IA32_CR_PAT, msr_low, msr_high);

host_pat = msr_low | ((u64) msr_high << 32);

vmcs_write64(HOST_IA32_PAT, host_pat);

}

if (vmcs_config.vmentry_ctrl & VM_ENTRY_LOAD_IA32_PAT) {

rdmsr(MSR_IA32_CR_PAT, msr_low, msr_high);

host_pat = msr_low | ((u64) msr_high << 32);

 

vmcs_write64(GUEST_IA32_PAT, host_pat);

 

vmx->vcpu.arch.pat = host_pat;

}

 

for (i = 0; i < NR_VMX_MSR; ++i) {

u32 index = vmx_msr_index[i];

u32 data_low, data_high;

u64 data;

int j = vmx->nmsrs;

if (rdmsr_safe(index, &data_low, &data_high) < 0)

continue;

if (wrmsr_safe(index, data_low, data_high) < 0)

continue;

data = data_low | ((u64)data_high << 32);

vmx->host_msrs[j].index = index;

vmx->host_msrs[j].reserved = 0;

vmx->host_msrs[j].data = data;

vmx->guest_msrs[j] = vmx->host_msrs[j];

++vmx->nmsrs;

}

vmcs_write32(VM_EXIT_CONTROLS, vmcs_config.vmexit_ctrl);

 

vmcs_write32(VM_ENTRY_CONTROLS, vmcs_config.vmentry_ctrl);

vmcs_writel(CR0_GUEST_HOST_MASK, ~0UL);

vmcs_writel(CR4_GUEST_HOST_MASK, KVM_GUEST_CR4_MASK);

tsc_base = vmx->vcpu.kvm->arch.vm_init_tsc;

rdtscll(tsc_this);

if (tsc_this < vmx->vcpu.kvm->arch.vm_init_tsc)

tsc_base = tsc_this;

guest_write_tsc(0, tsc_base);

return 0;

}

这个函数要写一堆的寄存器和控制信息,信息很多。所以只重点分析其中的几个地方:

当cpu不支持EPT扩展技术时候,有条件退出vm的指令要增加。这些指令是cr3 store和cr3 load,要把这个新内容写入cpu_based控制里面。(cpu_based控制是vmcs结构的一部分)。

然后是写cr0,cr3寄存器以及cs,ds以及es等段选择寄存器。

之后,要保存host的MSR寄存器的值到前面分配的guest_msrs页面。

2.4.3 Vcpu运行

推动vcpu运行,让虚拟机开始运行,主要在__vcpu_run函数执行。

代码清单2-7 V

static int __vcpu_run(struct kvm_vcpu *vcpu, struct kvm_run *kvm_run)

{

int r;

..................................

down_read(&vcpu->kvm->slots_lock);

vapic_enter(vcpu);

r = 1;

while (r > 0) {

 

if (vcpu->arch.mp_state == KVM_MP_STATE_RUNNABLE)

r = vcpu_enter_guest(vcpu, kvm_run);

else {

up_read(&vcpu->kvm->slots_lock);

kvm_vcpu_block(vcpu);

down_read(&vcpu->kvm->slots_lock);

if (test_and_clear_bit(KVM_REQ_UNHALT, &vcpu->requests))

{

switch(vcpu->arch.mp_state) {

case KVM_MP_STATE_HALTED:

vcpu->arch.mp_state =

KVM_MP_STATE_RUNNABLE;

case KVM_MP_STATE_RUNNABLE:

break;

case KVM_MP_STATE_SIPI_RECEIVED:

default:

r = -EINTR;

break;

}

}

}

..............................

clear_bit(KVM_REQ_PENDING_TIMER, &vcpu->requests);

 

if (kvm_cpu_has_pending_timer(vcpu))

kvm_inject_pending_timer_irqs(vcpu);

 

if (dm_request_for_irq_injection(vcpu, kvm_run)) {

r = -EINTR;

kvm_run->exit_reason = KVM_EXIT_INTR;

++vcpu->stat.request_irq_exits;

}

 

if (signal_pending(current)) {

r = -EINTR;

kvm_run->exit_reason = KVM_EXIT_INTR;

++vcpu->stat.signal_exits;

}

 

if (need_resched()) {

up_read(&vcpu->kvm->slots_lock);

kvm_resched(vcpu);

down_read(&vcpu->kvm->slots_lock);

}

}

up_read(&vcpu->kvm->slots_lock);

post_kvm_run_save(vcpu, kvm_run);

vapic_exit(vcpu);

return r;

}

这里理解的关键是vcpu_enter_guest进入了Guest,然后一直是vcpu在运行,当退出这个函数的时候,虚拟机已经执行了VM-Exit指令,也就是说,已经退出了虚拟机,进入根模式了。

退出之后,要检查退出的原因。如果有时钟中断发生,则插入一个时钟中断,如果是用户空间的中断发生,则退出原因要填写为KVM_EXIT_INTR。

注意一点的是,对于导致退出的事件,vcpu_enter_guest函数里面已经处理了一部分,处理的是虚拟机本身运行导致退出的事件。比如虚拟机内部写磁盘导致退出,就在vcpu_enter_guest里面处理(只是写了退出的原因,并没有真正处理)。Kvm是如何知道退出的原因的?这个就是vmcs结构的作用了,vmcs结构里面有VM-Exit的信息。

退出VM之后,如果内核没有完成处理,那么要退出内核到QEMU进程。然后是QEMU进程要处理。后面io处理一节可以看到QEMU的处理过程。

代码清单2-8 vcpu_enter_guest

static int vcpu_enter_guest(struct kvm_vcpu *vcpu, struct kvm_run *kvm_run)

{

int r;

bool req_int_win = !irqchip_in_kernel(vcpu->kvm) &&

kvm_run->request_interrupt_window;

 

r = kvm_mmu_reload(vcpu);

kvm_x86_ops->prepare_guest_switch(vcpu);

kvm_load_guest_fpu(vcpu);

 

inject_pending_event(vcpu, kvm_run);

if (kvm_lapic_enabled(vcpu)) {

update_cr8_intercept(vcpu);

kvm_lapic_sync_to_vapic(vcpu);

}

 

kvm_guest_enter();

kvm_x86_ops->run(vcpu, kvm_run);

 

 

kvm_guest_exit();

................................

r = kvm_x86_ops->handle_exit(kvm_run, vcpu);

out:

return r;

}

首先要装载mmu,然后注入事件,像中断,异常什么的。然后调用cpu架构相关的run函数,这个函数里面有一堆汇编写的语句,用来进入虚拟机以及指定从虚拟机退出的执行地址。最后调用cpu的handle_exit,用来从vmcs读取退出的信息。

将注入中断的函数简化一下。

代码清单2-9 V

static void vmx_inject_irq(struct kvm_vcpu *vcpu)

{

int irq = vcpu->arch.interrupt.nr;

..........................

intr = irq | INTR_INFO_VALID_MASK;

...............................

vmcs_write32(VM_ENTRY_INTR_INFO_FIELD, intr);

}

可以看到,实际上注入中断就是写vmcs里面的VM_ENTRY_INTR_INFO_FIELD这个域。然后在cpu的run函数里面设置cpu进入非根模式,vcpu会自动检查vmcs结构,然后注入中断,这是硬件自动完成的工作。而处理中断,就是Guest os内核所完成的工作了。

2.4.4 调度

kvm只是个内核模块,虚拟机实际上是运行在QEMU的进程上下文中。所以vcpu的调度实际上直接使用了linux自身的调度机制。也就是linux自身的进程调度机制。

QEMU可以设置每个vcpu都运作在一个线程中。

代码清单2-10 qemu_kvm_start_vcpu

static void qemu_kvm_start_vcpu(CPUState *env)

{

env->thread = qemu_mallocz(sizeof(QemuThread));

env->halt_cond = qemu_mallocz(sizeof(QemuCond));

qemu_cond_init(env->halt_cond);

qemu_thread_create(env->thread, qemu_kvm_cpu_thread_fn, env);

.................................................

}

从Qemu的代码,看到Qemu启动了一个kvm_cpu_thread线程。这个线程是循环调用

kvm_cpu_exec函数。

代码清单2-11 kvm_cpu_exec

int kvm_cpu_exec(CPUState *env)

{

struct kvm_run *run = env->kvm_run;

int ret, run_ret;

do {

...............................

run_ret = kvm_vcpu_ioctl(env, KVM_RUN, 0);

......................................

 

switch (run->exit_reason) {

case KVM_EXIT_IO:

DPRINTF("handle_io\n");

kvm_handle_io(run->io.port,

(uint8_t *)run + run->io.data_offset,

run->io.direction,

run->io.size,

run->io.count);

ret = 0;

break;

case KVM_EXIT_MMIO:

DPRINTF("handle_mmio\n");

cpu_physical_memory_rw(run->mmio.phys_addr,

run->mmio.data,

run->mmio.len,

run->mmio.is_write);

ret = 0;

break;

case KVM_EXIT_IRQ_WINDOW_OPEN:

DPRINTF("irq_window_open\n");

ret = EXCP_INTERRUPT;

break;

case KVM_EXIT_SHUTDOWN:

DPRINTF("shutdown\n");

qemu_system_reset_request();

ret = EXCP_INTERRUPT;

break;

case KVM_EXIT_UNKNOWN:

fprintf(stderr, "KVM: unknown exit, hardware reason %" PRIx64 "\n",

(uint64_t)run->hw.hardware_exit_reason);

ret = -1;

break;

case KVM_EXIT_INTERNAL_ERROR:

ret = kvm_handle_internal_error(env, run);

break;

default:

DPRINTF("kvm_arch_handle_exit\n");

ret = kvm_arch_handle_exit(env, run);

break;

}

} while (ret == 0);

..............................

env->exit_request = 0;

cpu_single_env = NULL;

return ret;

}

这个函数就是调用了前面分析过的KVM_RUN。回顾一下前面的分析,KVM_RUN就进入了虚拟机,如果从虚拟化退出到这里,那么Qemu要处理退出的事件。这些事件,可能是因为io引起的KVM_EXIT_IO,也可能是内部错误引起的KVM_EXIT_INTERNAL_ERROR。如果事件没有被完善处理,那么要停止虚拟机。

2.4.5 中断

如何向vcpu注入中断?是通过向真实cpu加入NMI中断来实现(xen是通过处理器间中断来实现)。

KVM要模拟一个中断控制芯片,这个是通过KVM_CREATE_IRQCHIP来实现的。然后,如果Qemu想注入一个中断,就通过KVM_IRQ_LINE实现。这个所谓中断控制芯片只是在内存中存在的结构,kvm通过软件方式模拟了中断的机制。

KVM_CREATE_IRQCHIP实际上调用了kvm_create_pic这个函数。

代码清单2-12 kvm_create_pic

struct kvm_pic *kvm_create_pic(struct kvm *kvm)

{

struct kvm_pic *s;

int ret;

s = kzalloc(sizeof(struct kvm_pic), GFP_KERNEL);

if (!s)

return NULL;

spin_lock_init(&s->lock);

s->kvm = kvm;

s->pics[0].elcr_mask = 0xf8;

s->pics[1].elcr_mask = 0xde;

s->irq_request = pic_irq_request;

s->irq_request_opaque = kvm;

s->pics[0].pics_state = s;

s->pics[1].pics_state = s;

 

kvm_iodevice_init(&s->dev, &picdev_ops);

ret = kvm_io_bus_register_dev(kvm, &kvm->pio_bus, &s->dev);

if (ret < 0) {

kfree(s);

return NULL;

}

return s;

}

可以看到,这个函数很简单,其实就是申请了一个kvm_pic的结构。然后指定irq_request指针为pic_irq_request。

而KVM_IRQ_LINE实际上调用的是kvm_set_irq,分析一下它是如何注入中断的。

代码清单2-13 kvm_set_irq

int kvm_set_irq(struct kvm *kvm, int irq_source_id, int irq, int level)

{

struct kvm_kernel_irq_routing_entry *e;

unsigned long *irq_state, sig_level;

int ret = -1;

...................................................

 

list_for_each_entry(e, &kvm->irq_routing, link)

if (e->gsi == irq) {

int r = e->set(e, kvm, sig_level);

if (r < 0)

continue;

ret = r + ((ret < 0) ? 0 : ret);

}

return ret;

}

从英文解释可以看到,因为不可能判断Guest使用的是PIC还是APIC,所以为每一个中断路由都设置中断。

这里解释一下,PIC就是传统的中断控制器8259,x86体系最初使用的中断控制器。后来,又推出了APIC,也就是高级中断控制器。APIC为多核架构做了更多设计。

这里的这个set函数,其实就是kvm_pic_set_irq。

代码清单2-14 V

int kvm_pic_set_irq(void *opaque, int irq, int level)

{struct kvm_pic *s = opaque;

............................

if (irq >= 0 && irq < PIC_NUM_PINS) {

ret = pic_set_irq1(&s->pics[irq >> 3], irq & 7, level);

pic_update_irq(s);

}

............................................

}

可以看到,前面申请的kvm_pic结构作为参数被引入。然后设置irq到这个结构的pic成员。

代码清单2-15 pic_update_irq

static void pic_update_irq(struct kvm_pic *s)

{

int irq2, irq;

irq2 = pic_get_irq(&s->pics[1]);

if (irq2 >= 0) {

 

pic_set_irq1(&s->pics[0], 2, 1);

pic_set_irq1(&s->pics[0], 2, 0);

}

irq = pic_get_irq(&s->pics[0]);

if (irq >= 0)

s->irq_request(s->irq_request_opaque, 1);

else

s->irq_request(s->irq_request_opaque, 0);

}

此时调用irq_request,就是初始化中断芯片时候绑定的函数pic_irq_request。

代码清单2-16 pic_irq_request

static void pic_irq_request(void *opaque, int level)

{

struct kvm *kvm = opaque;

struct kvm_vcpu *vcpu = kvm->bsp_vcpu;

struct kvm_pic *s = pic_irqchip(kvm);

int irq = pic_get_irq(&s->pics[0]);

 

s->output = level;

if (vcpu && level && (s->pics[0].isr_ack & (1 << irq))) {

s->pics[0].isr_ack &= ~(1 << irq);

kvm_vcpu_kick(vcpu);

}

}

这个函数很简单,就是设置中断控制芯片的output,然后调用kvm_vcpu_kick。

kvm_vcpu_kick这个地方很容易混淆。

等VM-exit退出后,就接上了前文分析过的部分。Vcpu再次进入虚拟机的时候,通过inject_pengding_event检查中断。这里面就查出来通过KVM_IRQ_LINE注入的中断,然后后面就是写vmcs结构了,已经分析过了。

2.5 vcpu的内存虚拟化

在kmv初始化的时候,要检查是否支持vt里面的EPT扩展技术。如果支持,enable_ept这个变量置为1,然后设置tdp_enabled为1。Tdp就是两维页表的意思,也就是EPT技术。

为陈述方便,给出kvm中下列名字的定义:

q GPA:guest机物理地址

q GVA:guest机虚拟地址

q HVA:host机虚拟地址

q HPA:host机物理地址

2.5.1 虚拟机页表初始化

在vcpu初始化的时候,要调用init_kvm_mmu来设置不同的内存虚拟化方式。

代码清单2-17 init_kvm_mmu

static int init_kvm_mmu(struct kvm_vcpu *vcpu)

{

vcpu->arch.update_pte.pfn = bad_pfn;

if (tdp_enabled)

return init_kvm_tdp_mmu(vcpu);

else

return init_kvm_softmmu(vcpu);

}

设置两种方式,一种是支持EPT的方式,一种是soft mmu,也就是影子页表的方式。

代码清单2-18 V

static int init_kvm_softmmu(struct kvm_vcpu *vcpu)

{

int r;

 

if (!is_paging(vcpu))

r = nonpaging_init_context(vcpu);

else if (is_long_mode(vcpu)) 

r = paging64_init_context(vcpu);

else if (is_pae(vcpu))

r = paging32E_init_context(vcpu);

else

r = paging32_init_context(vcpu);

vcpu->arch.mmu.base_role.glevels = vcpu->arch.mmu.root_level;

return r;

}

这个函数为多种模式的cpu设置了不同的虚拟化处理函数。选择32位非PAE模式的cpu进行分析。

代码清单2-19 V

static int paging32_init_context(struct kvm_vcpu *vcpu)

{

struct kvm_mmu *context = &vcpu->arch.mmu;

reset_rsvds_bits_mask(vcpu, PT32_ROOT_LEVEL);

context->new_cr3 = paging_new_cr3;

context->page_fault = paging32_page_fault;

context->gva_to_gpa = paging32_gva_to_gpa;

context->free = paging_free;

context->prefetch_page = paging32_prefetch_page;

context->sync_page = paging32_sync_page;

context->invlpg = paging32_invlpg;

context->root_level = PT32_ROOT_LEVEL;

context->shadow_root_level = PT32E_ROOT_LEVEL;

 

context->root_hpa = INVALID_PAGE;

return 0;

}

这个函数要设置一堆函数指针。其中paging32_page_fault等函数直接找是找不到的。这是内核代码经常用的一个技巧(好像别的代码很少见到这种用法)。真正定义在paging_tmpl.h这个文件。通过FNAME这个宏根据不同的cpu平台定义了各自的函数。比如paging32_page_fault实际上就是FNAME(page_fault)这个函数。

我们知道,linux为不同的cpu提供不同的页表层级。64位cpu使用了四级页表。这里指定页表是两级,也就是PT32_ROOT_LEVEL,同时设定页表根地址为无效。此时页表尚未分配。

何时去分配vcpu的页表哪?是在vcpu_enter_guest的开始位置,通过调用kvm_mmu_reload实现。

代码清单2-20 kvm_mmu_reload

static inline int kvm_mmu_reload(struct kvm_vcpu *vcpu)

if (likely(vcpu->arch.mmu.root_hpa != INVALID_PAGE))

return 0;

return kvm_mmu_load(vcpu);

}

首先检查页表根地址是否无效,如果无效,则调用kvm_mmu_load。

代码清单2-21 V

int kvm_mmu_load(struct kvm_vcpu *vcpu)

{

int r;

r = mmu_alloc_roots(vcpu);

 

mmu_sync_roots(vcpu);

 

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

....................

}

mmu_alloc_roots这个函数要申请内存,作为根页表使用,同时root_hpa指向根页表的物理地址。然后可以看到,vcpu中cr3寄存器的地址要指向这个根页表的物理地址。

2.5.2 虚拟机物理地址

我们已经分析过,kvm的虚拟机实际上运行在Qemu的进程上下文中。于是,虚拟机的物理内存实际上是Qemu进程的虚拟地址。Kvm要把虚拟机的物理内存分成几个slot。这是因为,对计算机系统来说,物理地址是不连续的,除了bios和显存要编入内存地址,设备的内存也可能映射到内存了,所以内存实际上是分为一段段的。

Qemu通过KVM_SET_USER_MEMORY_REGION来为虚拟机设置内存。

代码清单2-22 kvm_set_memory_region

int __kvm_set_memory_region(struct kvm *kvm,

struct kvm_userspace_memory_region *mem,

int user_alloc)

{

int r;

gfn_t base_gfn;

unsigned long npages;

unsigned long i;

struct kvm_memory_slot *memslot;

struct kvm_memory_slot old, new;

r = -EINVAL;

 

memslot = &kvm->memslots[mem->slot];

base_gfn = mem->guest_phys_addr >> PAGE_SHIFT;

npages = mem->memory_size >> PAGE_SHIFT;

new = old = *memslot;

 

new.base_gfn = base_gfn;

new.npages = npages;

new.flags = mem->flags;

new.user_alloc = user_alloc;

 

if (user_alloc)

new.userspace_addr = mem->userspace_addr;

spin_lock(&kvm->mmu_lock);

if (mem->slot >= kvm->nmemslots)

kvm->nmemslots = mem->slot + 1;

*memslot = new;

spin_unlock(&kvm->mmu_lock);

kvm_free_physmem_slot(&old, npages ? &new : NULL);

return 0;

}

这个函数大幅简化了。看代码时候,要注意对内存地址页的检查和内存overlap的检查部分。经过简化之后,代码很清晰了。就是创建一个新的memslot,代替原来的memslot。一个内存slot,最重要部分是指定了vm的物理地址,同时指定了Qemu分配的用户地址,前面一个地址是GPA,后面一个地址是HVA。可见,一个memslot就是建立了GPA到HVA的映射关系。

2.5.3 内存虚拟化过程

这里,有必要描述一下内存虚拟化的过程:

VM要访问GVA 0,那么首先查询VM的页表得到PTE(页表项),通过PTE将GVA 0映射到物理地址GPA 0.

GPA 0此时不存在,发生页缺失。

KVM接管。

从memslot,可以知道GPA对应的其实是HVA x,然后从HVA x,可以查找得到HPA y,然后将HPA y这个映射写入到PTE。

VM再次存取GVA 0,这是从页表项已经可以查到HPA y了,内存可正常访问。

首先,从page_fault处理开始。从前文的分析,知道VM里面的异常产生VM-Exit,然后由各自cpu提供的处理函数处理。对intel的vt技术,就是handle_exception这个函数。

代码清单2-23 V

static int handle_exception(struct kvm_vcpu *vcpu, struct kvm_run *kvm_run)

{

 

intr_info = vmcs_read32(VM_EXIT_INTR_INFO);

 

if (is_page_fault(intr_info)) {

 

 

if (enable_ept)

BUG();

 

cr2 = vmcs_readl(EXIT_QUALIFICATION);

trace_kvm_page_fault(cr2, error_code);

if (kvm_event_needs_reinjection(vcpu))

kvm_mmu_unprotect_page_virt(vcpu, cr2);

return kvm_mmu_page_fault(vcpu, cr2, error_code);

}

return 0;

}

从这个函数,可以看到对vmcs的使用。通过读vmcs的域,可以获得退出vm的原因。如果是page_fault引起,则调用kvm_mmu_page_fault去处理。

代码清单2-24 kvm_mmu_page_fault

int kvm_mmu_page_fault(struct kvm_vcpu *vcpu, gva_t cr2, u32 error_code)

{

int r;

enum emulation_result er;

 

r = vcpu->arch.mmu.page_fault(vcpu, cr2, error_code);

if (r < 0)

goto out;

if (!r) {

r = 1;

goto out;

}

 

er = emulate_instruction(vcpu, vcpu->run, cr2, error_code, 0);

..................................

}

这里调用了MMU的page_fault处理函数。这个函数就是前面初始化时候设置的paging32_page_fault。也就是通过FNAME宏展开的FNAME(page_fault)。

代码清单2-25 page_fault

static int FNAME(page_fault)(struct kvm_vcpu *vcpu, gva_t addr,

u32 error_code)

{

 

r = FNAME(walk_addr)(&walker, vcpu, addr, write_fault, user_fault,

fetch_fault);

 

if (!r) {

pgprintk("%s: guest page fault\n", __func__);

inject_page_fault(vcpu, addr, walker.error_code);

vcpu->arch.last_pt_write_count = 0; 

return 0;

}

if (walker.level >= PT_DIRECTORY_LEVEL) {

level = min(walker.level, mapping_level(vcpu, walker.gfn));

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

}

 

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

 

if (is_error_pfn(pfn)) {

pgprintk("gfn %lx is mmio\n", walker.gfn);

kvm_release_pfn_clean(pfn);

return 1;

}

 

sptep = FNAME(fetch)(vcpu, addr, &walker, user_fault, write_fault,

level, &write_pt, pfn);

.............................

}

对照前面的分析,比较容易理解这个函数了。首先是查guest机的页表,如果从GVA到GPA的映射都没建立,那么返回,让Guest OS做这个工作。

然后,如果映射已经建立,GPA存在,那么从Guest的页面号,查找Host的页面号。如何执行这个查找?从memslot可以知道user space首地址,就可以把物理地址GPA转为HVA,通过HVA就可以查到HPA,然后找到所在页的页号。

最后,写HVA到页表里面。页表在那里?回顾一下前面kvm_mmu_load的过程,页表是host申请的。通过页表搜索,就可以找到要写入的页表项。

2.6 IO虚拟化

IO虚拟化有两种方案,一种是半虚拟化方案,一种是全虚拟化方案。全虚拟化方案不需要该Guest的代码,那么Guest里面的io操作最终都变成io指令。在前面的分析中,其实已经涉及了io虚拟化的流程。在VM-exit的时候,前文分析过page fault导致的退出。那么io指令,同样会导致VM-exit退出,然后kvm会把io交给Qemu进程处理。

而半虚拟化方案,基本都是把io变成了消息处理,从guest机器发消息出来,然后由host机器处理。此时,在guest机器的驱动都被接管,已经不能被称为驱动(因为已经不再处理io指令,不和具体设备打交道),称为消息代理更合适。

2.6.1 Vmm对io的处理

当guest因为执行io执行退出后,由handle_io函数处理。

代码清单2-26 V

static int handle_io(struct kvm_vcpu *vcpu, struct kvm_run *kvm_run)

{

++vcpu->stat.io_exits;

exit_qualification = vmcs_readl(EXIT_QUALIFICATION);

...................................

size = (exit_qualification & 7) + 1;

in = (exit_qualification & 8) != 0;

port = exit_qualification >> 16;

.................................................

return kvm_emulate_pio(vcpu, kvm_run, in, size, port);

}

要从vmcs读退出的信息,然后调用kvm_emulate_pio处理。

代码清单2-27 V

int kvm_emulate_pio(struct kvm_vcpu *vcpu, struct kvm_run *run, int in,

int size, unsigned port)

{

unsigned long val;

 

vcpu->run->exit_reason = KVM_EXIT_IO;

vcpu->run->io.direction = in ? KVM_EXIT_IO_IN : KVM_EXIT_IO_OUT;

vcpu->run->io.size = vcpu->arch.pio.size = size;

vcpu->run->io.data_offset = KVM_PIO_PAGE_OFFSET * PAGE_SIZE;

vcpu->run->io.count = vcpu->arch.pio.count = vcpu->arch.pio.cur_count = 1;

vcpu->run->io.port = vcpu->arch.pio.port = port;

vcpu->arch.pio.in = in;

vcpu->arch.pio.string = 0;

vcpu->arch.pio.down = 0;

vcpu->arch.pio.rep = 0;

.................................

 

if (!kernel_pio(vcpu, vcpu->arch.pio_data)) {

complete_pio(vcpu);

return 1;

}

return 0;

}

这里要为io处理赋值各种参数,然后看内核能否处理这个io,如果内核能处理,就不用Qemu进程处理,否则退出内核态,返回用户态。从前文的分析中,我们知道返回是到Qemu的线程上下文中。实际上就是kvm_handle_io这个函数里面。

2.6.2 虚拟化io流程

用户态的Qemu如何处理io指令?首先,每种设备都需要注册自己的io指令处理函数到Qemu。

这是通过register_ioport_write和register_ioport_read是实现的。

代码清单2-28 register_ioport_read

int register_ioport_read(pio_addr_t start, int length, int size,

IOPortReadFunc *func, void *opaque)

{

int i, bsize;

 

for(i = start; i < start + length; i += size) {

ioport_read_table[bsize][i] = func;

if (ioport_opaque[i] != NULL && ioport_opaque[i] != opaque)

hw_error("register_ioport_read: invalid opaque for address 0x%x",

i);

ioport_opaque[i] = opaque;

}

return 0;

}

通过这个函数,实际上把io指令处理函数登记到一个全局的数组。每种支持的设备都登记在这个数组中。

再分析kvm_handle_io的流程。

代码清单2-29 V

static void kvm_handle_io(uint16_t port, void *data, int direction, int size,

uint32_t count)

{

.............................

for (i = 0; i < count; i++) {

if (direction == KVM_EXIT_IO_IN) {

switch (size) {

case 1:

stb_p(ptr, cpu_inb(port));

break;

}

ptr += size;

}

}

对于退出原因是KVM_EXIT_IO_IN的情况,调用cpu_inb处理。Cpu_inb是个封装函数,它的作用就是调用ioport_read.

代码清单2-30 ioport_read

static uint32_t ioport_read(int index, uint32_t address)

{

static IOPortReadFunc * const default_func[3] = {

default_ioport_readb,

default_ioport_readw,

default_ioport_readl

};

 

IOPortReadFunc *func = ioport_read_table[index][address];

if (!func)

func = default_func[index];

return func(ioport_opaque[address], address);

}

这里代码很清晰,就是从登记io指令函数的数组中读出处理函数,然后调用每种设备所登记的指令处理函数处理,完成io。

各种设备都有自己的处理函数,所以Qemu需要支持各种不同的设备,Qemu的复杂性也体现在这

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

虚拟化原理之KVM 的相关文章

  • KVM interface passthrough

    nbsp nbsp nbsp nbsp kvm passthrouth sr iov nbsp nbsp https blog csdn net yzy1103203312 article details 81092647 nbsp nbs
  • kvm的快照功能 (二、基于libvirt的快照)

    实例二 利用libvirt使用快照 virsh snapshot create domain name 一 创建虚机快照 名字自动生成 可在开机 关机 suspend等各种状态下做 virsh snapshot create test Do
  • kvm常见故障及解决

    一 启动虚拟机Connection reset by peer virsh start vmhost1error Failed to start domain vmhost1error Unable to read from monitor
  • KVM的HVM虚拟机使用非串口方式建立virsh console 连接

    在去年写的文章中 http blog csdn net dobell article details 14442457 写到了怎么利用serial 设备进行console连接 不过比较麻烦 因为1 需要修改虚拟机内部的grub启动选项 2
  • ubuntu18.04下pass-through直通realteck PCI设备到qemu-kvm虚拟机实践

    设备直通是一种虚拟化资源分配方式 通过将物理设备直通给虚拟机环境 达到虚拟机可以直接访问物理设备的目的 直通功能对设备的要求不高 不需要设备支持PF VF 目前市面上的显卡 网卡一般都支持直通 典型场景比如有两块显卡 一块主机用 另一块虚拟
  • QEMU-KVM基本操作

    本文主要介绍KVM虚拟机的一些基本实践操作 对KVM虚拟机的管理操作主要是基于libvirt的命令行工具virsh进行的 一 安装与启动 1 KVM模块检查 1 查看当前Linux系统核心是否包含KVM模块 Linux内核2 6 20及以上
  • qemu-system-x86_64方式创建KVM虚拟机

    一 QEMU介绍 QEMU是一款高效而实用的模拟器及虚拟机监管器 Virtual Machine Monitor VMM 主要提供两种功能给用户使用 一是作为用户态模拟器 利用动态代码翻译机制来执行不同于主机架构的代码 二是作为虚拟机监管器
  • CentOS 7 virt-install 命令行方式(非图形界面)安装KVM虚拟机

    环境及网卡配置请参考 https blog csdn net mshxuyi article details 98305715 创建镜像目录 mkdir p home vms virt install 配置文件 virt install n
  • 关于对cpu的理解和kvm虚拟机到物理cpu的绑定

    这段时间一直在想 云计算除了虚拟化之外 还应该有其它的东西 那就是优化 因为我们虚拟出来资源之后怎么用 怎么划分 是并行也好 是租给用户使用也好 都要实现资源调度和使用的最优化 嗯 这是这段时间关于云计算的想法 下面分享一下最近两天做的一些
  • virt与virsh常用命令

    前提 客户机虚拟机上配置qemu guest agent 并对guest的xml配置文件做一些修改 那么就可以使用很多特有的命令 对虚拟机进行配置 例如 修改虚拟机密码 root localhost virsh set user passw
  • kvm虚拟化技术

    前言 kvm是一种虚拟化技术 使用 概念 kvm是linux内核的模块 它需要CPU支持 采用硬件辅助虚拟化技术Intel VT AMD V 内存的相关技术如Intel的EPT和AMD的RVI 是底层虚拟化内核模块 检查cpu是否支持虚拟化
  • 虚拟化原理之KVM

    2 1 kvm技术基础 KVM kernel based virtual machine 的名字 基于kernel的虚拟机 已经很准确的说出了kvm的设计思路 也就是依赖linux内核 完全利用linux内核来实现cpu的调度 内存管理的功
  • 33 KVM管理设备-配置虚拟机PCIe控制器

    文章目录 33 KVM管理设备 配置虚拟机PCIe控制器 33 1 概述 33 2 配置PCIe Root PCIe Root Port和PCIe PCI Bridge 33 2 1 简化配置方法 33 2 1完整配制方法 33 KVM管理
  • Linux 桌面虚拟化技术 KVM

    KVM 是 Kernel based Virtual Machine 的简称 是一个开源的系统虚拟化模块 自Linux 2 6 20之后集成在Linux的各个主要发行版本中 它使用Linux自身的调度器进行管理 所以相对于Xen 其核心源码
  • kvm虚拟机读取宿主机usb设备

    1 查看usb设备 用lsusb查看宿主机usb设备信息 2 在kvm虚拟机的xml文件中添加如下
  • 60 KVM Skylark虚拟机混部-安装和配置

    文章目录 60 KVM Skylark虚拟机混部 安装和配置 60 1 安装Skylark 60 1 1 硬件要求 60 1 2 软件要求 60 1 3 安装方法 60 2 配置Skylark 60 2 1 日志 60 2 2 功耗干扰控制
  • KVM源代码分析1:基本工作原理

    http www oenhan com kvm src 1 13年的时候准备挖 KVM源代码分析 的坑 陆陆续续2年过去了 坑也没有填上 当时是因为对KVM了解的肤浅 真正的理解必然要深入到代码级别 所谓 摈弃皮毛 看到血肉 看到真相 当时
  • 是否可以在 Azure 上运行 KVM

    Azure Fabric 似乎运行在 Hyper V 的定制版本上 是否可以在 Azure 上运行 KVM 虚拟化实例 使用嵌套虚拟化 我有一个基于 Debian 的自定义 VHD 比如说 根据本文档https learn microsof
  • Android模拟器和virtualbox不能同时运行

    每当我运行 Virtualbox 时 我都无法启动 Android 模拟器映像 反之亦然 AVD管理器中的错误消息是 ioctl KVM CREATE VM failed Device or resource busy ko failed
  • update-alternatives:错误:arptables 的替代 /usr/sbin/arptables-legacy 未注册;不设置

    我在 Buster 主机上有 Debian 10 Buster KVM 来宾计算机 尝试切换到旧版iptables在以下虚拟机上Debian 维基 https wiki debian org iptables update alternat

随机推荐

  • 【多线程】三种实现方案

    目录 1 多线程中的并发和并行概念 2 多线程中的进程和线程概念 3 多线程的实现方案 3 1 方式1 继承Thread类的方式进行实现 3 2 方式2 实现Runnable接口 3 3 方式3 Callble和Future 可以获取返回结
  • 关于MaxCompute的基本了解

    大数据计算服务 MaxCompute 原名 ODPS 是一种快速 完全托管的 GB TB PB 级数据仓库解决方案 MaxCompute 为您提供了完善的数据导入方案以及多种经典的分布式计算模型 能够更快速的解决海量数据计算问题 有效降低企
  • 【高数】导数存在,导数就连续吗?

    高数 导数存在 导数就连续吗 一 概念理解 二 问题讨论 三 小结 一 概念理解 函数连续 y f x 在 x 0 的某邻域有定义 且满足下式 也就说明 连续意味着 x 0 处 f x 的极限存在 也即 f x 的左极限 右极限 该点函数值
  • 【STM32】【HAL库】遥控关灯1主机

    相关连接 STM32 HAL库 遥控关灯0 概述 STM32 HAL库 遥控关灯1主机 STM32 HAL库 遥控关灯2 分机 STM32 HAL库 遥控关灯3 遥控器 需求 主机需要以下功能 接收来自物联网平台的命令 发送RF433信号给
  • crmeb多商户系统安装(1)

    服务器配置 以阿里云ECS服务器为例 1 在阿里云控制台 云服务器ECS 实例 点击最右侧更多 实力状态 停止 2 选择停止 点击确定 3 输入短信验证码 4 等待服务器状态为 已停止 点击最右侧更多 磁盘和镜像 更换系统盘 5 点击确定
  • 踩坑日记:java.lang.IllegalStateException: For queries with named parameters you need to use provide ...

    今天的错误是这个 java lang IllegalStateException For queries with named parameters you need to use provide names for method para
  • SQLI-labs-第五关和第六关

    目录 1 判断注入点 2 判断当前表的字段数 3 爆库名 4 爆表名 5 爆字段名 6 爆值 知识点 布尔盲注 思路 1 判断注入点 首先 我们看看正常的回显内容 id 1 接着输入 id 1 结果出现语句错误 这里说明存在单引号的闭合错误
  • 【信号分解】基于辛几何模态SGMD实现信号数据分解附Matlab代码

    作者简介 热爱科研的Matlab仿真开发者 修心和技术同步精进 matlab项目合作可私信 个人主页 Matlab科研工作室 个人信条 格物致知 更多Matlab仿真内容点击 智能优化算法 神经网络预测 雷达通信 无线传感器 电力系统 信号
  • 【网络工程】如何本地调试微信公众号开发教程(Nginx代理方法)

    目录 前言 目的 通过Nginx代理实现本地调试微信公众号 实现工具 实现步骤 1 启动本地前端项目 2 首先配置Nginx 3 填写app conf内容 把本地前端项目与域名形成映射 4 把app conf加入到Nginx配置中 5 打开
  • vue-cordova修改app图标+文字以及启动图

    前言 在使用 vue cordova 打包成apk的时候 我们一定会根据项目改把app的图标还有显示文字 加载图片也有可能改动 这里来说一说这个 1 修改显示文字 地址 D cordova app config xml 修改name里面的文
  • 鸿蒙相符合的图片,鸿蒙操作系统亮相——目标是未来的物联网

    原标题 鸿蒙操作系统亮相 目标是未来的物联网 本文由什么值得买用户原创 太空铁 咱们好 我是太空铁 这两天 华为开发者大会很热闹 华为接连发布了鸿蒙 凌霄以及才智屏等软硬件产品 其间最让人等待的当属鸿蒙操作体系了 鸿蒙操作体系早前可说是进行
  • 系统环境变量修改后需要重启qt才能响应

    修改系统环境变量 不需要重启系统 但需要重启qt才能感知新的环境变量
  • 物业小区管理系统登录页面以及逻辑实现

    学习vue3和springboot那肯定是少不了写项目的 在各个项目中肯定是离不开登录和注册的事情的 这也是一个项目起步的需求 接下来我们来看看我们所写的项目起步 首先搭建vue3和springboot的项目环境 这些搭建大家自行完成即可
  • 《Centos7——实战-完全备份和增量备份》

    目录 完全备份和增量备份 1 需要开启log bin日志 2 增量和全备脚本 3 计划任务 模拟数据丢失 1 新增数据 2 数据备份 3 删除数据 4 数据的恢复 完全备份和增量备份 一般都三种备份种类 完全备份 差异备份 增量备份 完全备
  • Android中的延时工作队列——WorkQueue教程

    Android中的延时工作队列 WorkQueue教程 工作队列是Android开发中常用的一种机制 它允许我们按照一定的顺序和延时执行任务 在本教程中 我们将介绍如何使用Android的WorkQueue来创建延时工作队列 并提供相应的源
  • 云计算————系统部署

    云计算基础篇 前言 云计算 cloud computing 是基于互联网的相关服务的增加 使用和交付模式 通常涉及通过互联网来提供动态易扩展且经常是虚拟化的资源 云是网络 互联网的一种比喻说法 过去在图中往往用云来表示电信网 后来也用来表示
  • hibernate的insert数据正常,但是update数据乱码问题解决

    在做项目是发现 hibernate的insert数据正常 但是update数据是却出现乱码 解决办法 在tomcat的conf server xml中找到下面代码的位置
  • Java JUnit 单元测试小结

    测试类型 单元测试 Unit test 单元测试关注单一的类 它们存在的目的是检查这个类中的代码是否按照期望正确运行 集成测试 Integration test 集成测试是检查开发的模块和其他模块整合时是否正常工作 虽然集成测试的代码影响范
  • 【JavaScript基础】你真正了解如今的Js数组吗,看这篇就(Go)够了

    JavaScript基础 你真正了解如今的Js数组吗 看这篇就 Go 够了 博客说明 文章所涉及的部分资料来自互联网整理 当然还有自己个人的总结和看法 分享的目的在于共建社区和巩固自己 引用的资料如有侵权 请联系本人删除 幸好我在 感谢你来
  • 虚拟化原理之KVM

    2 1 kvm技术基础 KVM kernel based virtual machine 的名字 基于kernel的虚拟机 已经很准确的说出了kvm的设计思路 也就是依赖linux内核 完全利用linux内核来实现cpu的调度 内存管理的功