KVM虚拟机IO处理过程(二) ----QEMU/KVM I/O 处理过程

   接着KVM虚拟机IO处理过程中Guest Vm IO处理过程(http://blog.youkuaiyun.com/dashulu/article/details/16820281),本篇文章主要描述IO从guest vm跳转到kvm和qemu后的处理过程.

    首先回顾一下kvm的启动过程(http://blog.youkuaiyun.com/dashulu/article/details/17074675).qemu通过调用kvm提供的一系列接口来启动kvm. qemu的入口为vl.c中的main函数,main函数通过调用kvm_init 和 machine->init来初始化kvm. 其中, machine->init会创建vcpu, 用一个线程去模拟vcpu, 该线程执行的函数为qemu_kvm_cpu_thread_fn, 并且该线程最终kvm_cpu_exec,该函数调用kvm_vcpu_ioctl切换到kvm中,下次从kvm中返回时,会接着执行kvm_vcpu_ioctl之后的代码,判断exit_reason,然后进行相应处理.

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
 
  1. int kvm_cpu_exec(CPUState *cpu)  
  2. {  
  3.     struct kvm_run *run = cpu->kvm_run;  
  4.     int ret, run_ret;  
  5.   
  6.     DPRINTF("kvm_cpu_exec()\n");  
  7.   
  8.     if (kvm_arch_process_async_events(cpu)) {  
  9.         cpu->exit_request = 0;  
  10.         return EXCP_HLT;  
  11.     }  
  12.   
  13.     do {  
  14.         if (cpu->kvm_vcpu_dirty) {  
  15.             kvm_arch_put_registers(cpu, KVM_PUT_RUNTIME_STATE);  
  16.             cpu->kvm_vcpu_dirty = false;  
  17.         }  
  18.   
  19.         kvm_arch_pre_run(cpu, run);  
  20.         if (cpu->exit_request) {  
  21.             DPRINTF("interrupt exit requested\n");  
  22.             /* 
  23.              * KVM requires us to reenter the kernel after IO exits to complete 
  24.              * instruction emulation. This self-signal will ensure that we 
  25.              * leave ASAP again. 
  26.              */  
  27.             qemu_cpu_kick_self();  
  28.         }  
  29.         qemu_mutex_unlock_iothread();  
  30.   
  31.         run_ret = kvm_vcpu_ioctl(cpu, KVM_RUN, 0);  
  32.   
  33.         qemu_mutex_lock_iothread();  
  34.         kvm_arch_post_run(cpu, run);  
  35.   
  36.         if (run_ret < 0) {  
  37.             if (run_ret == -EINTR || run_ret == -EAGAIN) {  
  38.                 DPRINTF("io window exit\n");  
  39.                 ret = EXCP_INTERRUPT;  
  40.                 break;  
  41.             }  
  42.             fprintf(stderr, "error: kvm run failed %s\n",  
  43.                     strerror(-run_ret));  
  44.             abort();  
  45.         }  
  46.   
  47.         trace_kvm_run_exit(cpu->cpu_index, run->exit_reason);  
  48.         switch (run->exit_reason) {  
  49.         case KVM_EXIT_IO:  
  50.             DPRINTF("handle_io\n");  
  51.             kvm_handle_io(run->io.port,  
  52.                           (uint8_t *)run + run->io.data_offset,  
  53.                           run->io.direction,  
  54.                           run->io.size,  
  55.                           run->io.count);  
  56.             ret = 0;  
  57.             break;  
  58.         case KVM_EXIT_MMIO:  
  59.             DPRINTF("handle_mmio\n");  
  60.             cpu_physical_memory_rw(run->mmio.phys_addr,  
  61.                                    run->mmio.data,  
  62.                                    run->mmio.len,  
  63.                                    run->mmio.is_write);  
  64.             ret = 0;  
  65.             break;  
  66.         case KVM_EXIT_IRQ_WINDOW_OPEN:  
  67.             DPRINTF("irq_window_open\n");  
  68.             ret = EXCP_INTERRUPT;  
  69.             break;  
  70.         case KVM_EXIT_SHUTDOWN:  
  71.             DPRINTF("shutdown\n");  
  72.             qemu_system_reset_request();  
  73.             ret = EXCP_INTERRUPT;  
  74.             break;  
  75.         case KVM_EXIT_UNKNOWN:  
  76.             fprintf(stderr, "KVM: unknown exit, hardware reason %" PRIx64 "\n",  
  77.                     (uint64_t)run->hw.hardware_exit_reason);  
  78.             ret = -1;  
  79.             break;  
  80.         case KVM_EXIT_INTERNAL_ERROR:  
  81.             ret = kvm_handle_internal_error(cpu, run);  
  82.             break;  
  83.         default:  
  84.             DPRINTF("kvm_arch_handle_exit\n");  
  85.             ret = kvm_arch_handle_exit(cpu, run);  
  86.             break;  
  87.         }  
  88.     } while (ret == 0);  
  89.   
  90.     if (ret < 0) {  
  91.         cpu_dump_state(cpu, stderr, fprintf, CPU_DUMP_CODE);  
  92.         vm_stop(RUN_STATE_INTERNAL_ERROR);  
  93.     }  
  94.   
  95.     cpu->exit_request = 0;  
  96.     return ret;  
  97. }  

kvm_vcpu_ioctl执行时,调用的kvm函数是virt/kvm/kvm-main.c中的kvm_vcpu_ioctl.c函数.当传入参数为KVM_RUN时,最终会调用到vcpu_enter_guest函数, vcpu_enter_guest函数中调用了kvm_x86_ops->run(vcpu),在intel处理器架构中该函数对应的实现为vmx_vcpu_run, vmx_vcpu_run设置好寄存器状态之后调用VM_LAUNCH或者VM_RESUME进入guest vm, 一旦发生vm exit则从此处继续执行下去.

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
 
  1. static void __noclone vmx_vcpu_run(struct kvm_vcpu *vcpu)  
  2. {  
  3.     struct vcpu_vmx *vmx = to_vmx(vcpu);  
  4.     unsigned long debugctlmsr;  
  5.   
  6.     /*...此处省略n行代码...*/  
  7.     vmx->__launched = vmx->loaded_vmcs->launched;  
  8.     asm(  
  9.         /* Store host registers */  
  10.         "push %%" _ASM_DX "; push %%" _ASM_BP ";"  
  11.         "push %%" _ASM_CX " \n\t" /* placeholder for guest rcx */  
  12.         "push %%" _ASM_CX " \n\t"  
  13.         "cmp %%" _ASM_SP ", %c[host_rsp](%0) \n\t"  
  14.         "je 1f \n\t"  
  15.         "mov %%" _ASM_SP ", %c[host_rsp](%0) \n\t"  
  16.         __ex(ASM_VMX_VMWRITE_RSP_RDX) "\n\t"  
  17.         "1: \n\t"  
  18.         /* Reload cr2 if changed */  
  19.         "mov %c[cr2](%0), %%" _ASM_AX " \n\t"  
  20.         "mov %%cr2, %%" _ASM_DX " \n\t"  
  21.         "cmp %%" _ASM_AX ", %%" _ASM_DX " \n\t"  
  22.         "je 2f \n\t"  
  23.         "mov %%" _ASM_AX", %%cr2 \n\t"  
  24.         "2: \n\t"  
  25.         /* Check if vmlaunch of vmresume is needed */  
  26.         "cmpl $0, %c[launched](%0) \n\t"  
  27.         /* Load guest registers.  Don't clobber flags. */  
  28.         "mov %c[rax](%0), %%" _ASM_AX " \n\t"  
  29.         "mov %c[rbx](%0), %%" _ASM_BX " \n\t"  
  30.         "mov %c[rdx](%0), %%" _ASM_DX " \n\t"  
  31.         "mov %c[rsi](%0), %%" _ASM_SI " \n\t"  
  32.         "mov %c[rdi](%0), %%" _ASM_DI " \n\t"  
  33.         "mov %c[rbp](%0), %%" _ASM_BP " \n\t"  
  34. #ifdef CONFIG_X86_64  
  35.         "mov %c[r8](%0),  %%r8  \n\t"  
  36.         "mov %c[r9](%0),  %%r9  \n\t"  
  37.         "mov %c[r10](%0), %%r10 \n\t"  
  38.         "mov %c[r11](%0), %%r11 \n\t"  
  39.         "mov %c[r12](%0), %%r12 \n\t"  
  40.         "mov %c[r13](%0), %%r13 \n\t"  
  41.         "mov %c[r14](%0), %%r14 \n\t"  
  42.         "mov %c[r15](%0), %%r15 \n\t"  
  43. #endif  
  44.         "mov %c[rcx](%0), %%" _ASM_CX " \n\t" /* kills %0 (ecx) */  
  45.   
  46.         /* Enter guest mode */  
  47.         "jne 1f \n\t"  
  48.         __ex(ASM_VMX_VMLAUNCH) "\n\t"  
  49.         "jmp 2f \n\t"  
  50.         "1: " __ex(ASM_VMX_VMRESUME) "\n\t"  
  51.         "2: "  
  52.         /* Save guest registers, load host registers, keep flags */  
  53.         "mov %0, %c[wordsize](%%" _ASM_SP ") \n\t"  
  54.         "pop %0 \n\t"  
  55.         "mov %%" _ASM_AX ", %c[rax](%0) \n\t"  
  56.         "mov %%" _ASM_BX ", %c[rbx](%0) \n\t"  
  57.         __ASM_SIZE(pop) " %c[rcx](%0) \n\t"  
  58.         "mov %%" _ASM_DX ", %c[rdx](%0) \n\t"  
  59.         "mov %%" _ASM_SI ", %c[rsi](%0) \n\t"  
  60.         "mov %%" _ASM_DI ", %c[rdi](%0) \n\t"  
  61.         "mov %%" _ASM_BP ", %c[rbp](%0) \n\t"  
  62. #ifdef CONFIG_X86_64  
  63.         "mov %%r8,  %c[r8](%0) \n\t"  
  64.         "mov %%r9,  %c[r9](%0) \n\t"  
  65.         "mov %%r10, %c[r10](%0) \n\t"  
  66.         "mov %%r11, %c[r11](%0) \n\t"  
  67.         "mov %%r12, %c[r12](%0) \n\t"  
  68.         "mov %%r13, %c[r13](%0) \n\t"  
  69.         "mov %%r14, %c[r14](%0) \n\t"  
  70.         "mov %%r15, %c[r15](%0) \n\t"  
  71. #endif  
  72.         "mov %%cr2, %%" _ASM_AX "   \n\t"  
  73.         "mov %%" _ASM_AX ", %c[cr2](%0) \n\t"  
  74.   
  75.         "pop  %%" _ASM_BP "; pop  %%" _ASM_DX " \n\t"  
  76.         "setbe %c[fail](%0) \n\t"  
  77.         ".pushsection .rodata \n\t"  
  78.         ".global vmx_return \n\t"  
  79.         "vmx_return: " _ASM_PTR " 2b \n\t"  
  80.         ".popsection"  
  81.           : : "c"(vmx), "d"((unsigned long)HOST_RSP),  
  82.         [launched]"i"(offsetof(struct vcpu_vmx, __launched)),  
  83.         [fail]"i"(offsetof(struct vcpu_vmx, fail)),  
  84.         [host_rsp]"i"(offsetof(struct vcpu_vmx, host_rsp)),  
  85.         [rax]"i"(offsetof(struct vcpu_vmx, vcpu.arch.regs[VCPU_REGS_RAX])),  
  86.         [rbx]"i"(offsetof(struct vcpu_vmx, vcpu.arch.regs[VCPU_REGS_RBX])),  
  87.         [rcx]"i"(offsetof(struct vcpu_vmx, vcpu.arch.regs[VCPU_REGS_RCX])),  
  88.         [rdx]"i"(offsetof(struct vcpu_vmx, vcpu.arch.regs[VCPU_REGS_RDX])),  
  89.         [rsi]"i"(offsetof(struct vcpu_vmx, vcpu.arch.regs[VCPU_REGS_RSI])),  
  90.         [rdi]"i"(offsetof(struct vcpu_vmx, vcpu.arch.regs[VCPU_REGS_RDI])),  
  91.         [rbp]"i"(offsetof(struct vcpu_vmx, vcpu.arch.regs[VCPU_REGS_RBP])),  
  92. #ifdef CONFIG_X86_64  
  93.         [r8]"i"(offsetof(struct vcpu_vmx, vcpu.arch.regs[VCPU_REGS_R8])),  
  94.         [r9]"i"(offsetof(struct vcpu_vmx, vcpu.arch.regs[VCPU_REGS_R9])),  
  95.         [r10]"i"(offsetof(struct vcpu_vmx, vcpu.arch.regs[VCPU_REGS_R10])),  
  96.         [r11]"i"(offsetof(struct vcpu_vmx, vcpu.arch.regs[VCPU_REGS_R11])),  
  97.         [r12]"i"(offsetof(struct vcpu_vmx, vcpu.arch.regs[VCPU_REGS_R12])),  
  98.         [r13]"i"(offsetof(struct vcpu_vmx, vcpu.arch.regs[VCPU_REGS_R13])),  
  99.         [r14]"i"(offsetof(struct vcpu_vmx, vcpu.arch.regs[VCPU_REGS_R14])),  
  100.         [r15]"i"(offsetof(struct vcpu_vmx, vcpu.arch.regs[VCPU_REGS_R15])),  
  101. #endif  
  102.         [cr2]"i"(offsetof(struct vcpu_vmx, vcpu.arch.cr2)),  
  103.         [wordsize]"i"(sizeof(ulong))  
  104.           : "cc", "memory"  
  105. #ifdef CONFIG_X86_64  
  106.         , "rax", "rbx", "rdi", "rsi"  
  107.         , "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15"  
  108. #else  
  109.         , "eax", "ebx", "edi", "esi"  
  110. #endif  
  111.           );  
  112.   
  113.     /* MSR_IA32_DEBUGCTLMSR is zeroed on vmexit. Restore it if needed */  
  114.     if (debugctlmsr)  
  115.         update_debugctlmsr(debugctlmsr);  
  116.   
  117. #ifndef CONFIG_X86_64  
  118.     /* 
  119.      * The sysexit path does not restore ds/es, so we must set them to 
  120.      * a reasonable value ourselves. 
  121.      * 
  122.      * We can't defer this to vmx_load_host_state() since that function 
  123.      * may be executed in interrupt context, which saves and restore segments 
  124.      * around it, nullifying its effect. 
  125.      */  
  126.     loadsegment(ds, __USER_DS);  
  127.     loadsegment(es, __USER_DS);  
  128. #endif  
  129.   
  130.     vcpu->arch.regs_avail = ~((1 << VCPU_REGS_RIP) | (1 << VCPU_REGS_RSP)  
  131.                   | (1 << VCPU_EXREG_RFLAGS)  
  132.                   | (1 << VCPU_EXREG_CPL)  
  133.                   | (1 << VCPU_EXREG_PDPTR)  
  134.                   | (1 << VCPU_EXREG_SEGMENTS)  
  135.                   | (1 << VCPU_EXREG_CR3));  
  136.     vcpu->arch.regs_dirty = 0;  
  137.   
  138.     vmx->idt_vectoring_info = vmcs_read32(IDT_VECTORING_INFO_FIELD);  
  139.   
  140.     if (is_guest_mode(vcpu)) {  
  141.         struct vmcs12 *vmcs12 = get_vmcs12(vcpu);  
  142.         vmcs12->idt_vectoring_info_field = vmx->idt_vectoring_info;  
  143.         if (vmx->idt_vectoring_info & VECTORING_INFO_VALID_MASK) {  
  144.             vmcs12->idt_vectoring_error_code =  
  145.                 vmcs_read32(IDT_VECTORING_ERROR_CODE);  
  146.             vmcs12->vm_exit_instruction_len =  
  147.                 vmcs_read32(VM_EXIT_INSTRUCTION_LEN);  
  148.         }  
  149.     }  
  150.   
  151.     vmx->loaded_vmcs->launched = 1;  
  152.   
  153.     vmx->exit_reason = vmcs_read32(VM_EXIT_REASON);  
  154.     trace_kvm_exit(vmx->exit_reason, vcpu, KVM_ISA_VMX);  
  155.   
  156.     vmx_complete_atomic_exit(vmx);  
  157.     vmx_recover_nmi_blocking(vmx);  
  158.     vmx_complete_interrupts(vmx);  
  159. }  

 

 

    介绍完初始化的流程,可以介绍IO在kvm和qemu中的处理流程了. 当Guest Vm进行IO操作需要访问设备时,就会触发vm exit 返回到vmx_vcpu_run, vmx保存好vmcs并且记录下VM_ExIT_REASON后返回到调用该函数的vcpu_enter_guest, 在vcpu_enter_guest函数末尾调用了r = kvm_x86_ops->handle_exit(vcpu), 该函数对应于vmx_handle_exit函数(intel cpu架构对应关系可以查看vmx.c文件中static struct kvm_x86_ops vmx_x86_ops), vmx_handle_exit 调用kvm_vmx_exit_handlers[exit_reason](vcpu),该语句根据exit_reason调用不同的函数,该数据结构定义如下:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
 
  1. static int (*const kvm_vmx_exit_handlers[])(struct kvm_vcpu *vcpu) = {  
  2.     [EXIT_REASON_EXCEPTION_NMI]           = handle_exception,  
  3.     [EXIT_REASON_EXTERNAL_INTERRUPT]      = handle_external_interrupt,  
  4.     [EXIT_REASON_TRIPLE_FAULT]            = handle_triple_fault,  
  5.     [EXIT_REASON_NMI_WINDOW]          = handle_nmi_window,  
  6.     [EXIT_REASON_IO_INSTRUCTION]          = handle_io,  
  7.     [EXIT_REASON_CR_ACCESS]               = handle_cr,  
  8.     [EXIT_REASON_DR_ACCESS]               = handle_dr,  
  9.     [EXIT_REASON_CPUID]                   = handle_cpuid,  
  10.     [EXIT_REASON_MSR_READ]                = handle_rdmsr,  
  11.     [EXIT_REASON_MSR_WRITE]               = handle_wrmsr,  
  12.     [EXIT_REASON_PENDING_INTERRUPT]       = handle_interrupt_window,  
  13.     [EXIT_REASON_HLT]                     = handle_halt,  
  14.     [EXIT_REASON_INVD]            = handle_invd,  
  15.     [EXIT_REASON_INVLPG]              = handle_invlpg,  
  16.     [EXIT_REASON_RDPMC]                   = handle_rdpmc,  
  17.     [EXIT_REASON_VMCALL]                  = handle_vmcall,  
  18.     [EXIT_REASON_VMCLEAR]                 = handle_vmclear,  
  19.     [EXIT_REASON_VMLAUNCH]                = handle_vmlaunch,  
  20.     [EXIT_REASON_VMPTRLD]                 = handle_vmptrld,  
  21.     [EXIT_REASON_VMPTRST]                 = handle_vmptrst,  
  22.     [EXIT_REASON_VMREAD]                  = handle_vmread,  
  23.     [EXIT_REASON_VMRESUME]                = handle_vmresume,  
  24.     [EXIT_REASON_VMWRITE]                 = handle_vmwrite,  
  25.     [EXIT_REASON_VMOFF]                   = handle_vmoff,  
  26.     [EXIT_REASON_VMON]                    = handle_vmon,  
  27.     [EXIT_REASON_TPR_BELOW_THRESHOLD]     = handle_tpr_below_threshold,  
  28.     [EXIT_REASON_APIC_ACCESS]             = handle_apic_access,  
  29.     [EXIT_REASON_WBINVD]                  = handle_wbinvd,  
  30.     [EXIT_REASON_XSETBV]                  = handle_xsetbv,  
  31.     [EXIT_REASON_TASK_SWITCH]             = handle_task_switch,  
  32.     [EXIT_REASON_MCE_DURING_VMENTRY]      = handle_machine_check,  
  33.     [EXIT_REASON_EPT_VIOLATION]       = handle_ept_violation,  
  34.     [EXIT_REASON_EPT_MISCONFIG]           = handle_ept_misconfig,  
  35.     [EXIT_REASON_PAUSE_INSTRUCTION]       = handle_pause,  
  36.     [EXIT_REASON_MWAIT_INSTRUCTION]       = handle_invalid_op,  
  37.     [EXIT_REASON_MONITOR_INSTRUCTION]     = handle_invalid_op,  
  38. };  

如果是因为IO原因导致的vm exit,则调用的处理函数为handle_io,handle_io的处理可以查看(http://blog.youkuaiyun.com/fanwenyi/article/details/12748613), 该过程结束之后需要qemu去处理IO,这时候会返回到qemu, 在kvm_cpu_exec中继续执行下去,看上面kvm_cpu_exec的代码,如果是因为IO原因返回到qemu,会调用kvm_handle_io函数.

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
 
  1. switch (run->exit_reason) {  
  2.         case KVM_EXIT_IO:  
  3.             DPRINTF("handle_io\n");  
  4.             kvm_handle_io(run->io.port,  
  5.                           (uint8_t *)run + run->io.data_offset,  
  6.                           run->io.direction,  
  7.                           run->io.size,  
  8.                           run->io.count);  
  9.             ret = 0;  
  10.             break;  

kvm_handle_io调用cpu_outb, cpu_outw等指令处理IO操作.

 

    假设虚拟机是用raw格式的磁盘,则IO在qemu中处理时经过的函数栈如下所示:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
 
  1. #0 bdrv_aio_writev (bs=0x55555629e9b0, sector_num=870456,   
  2. qiov=0x555556715ab0, nb_sectors=1,   
  3. cb=0x55555570161b <ide_sector_write_cb>, opaque=0x5555567157b8)  
  4. at block.c:3408  
  5. #1 0x0000555555701960 in ide_sector_write (s=0x5555567157b8)  
  6. at hw/ide/core.c:798  
  7. #2 0x00005555557047ae in ide_data_writew (opaque=0x555556715740, addr=496,   
  8. val=8995) at hw/ide/core.c:1907  
  9. #3 0x00005555558d9e4c in portio_write (opaque=0x5555565c0670, addr=0,   
  10. data=8995, size=2) at /home/dashu/kvm/qemu/qemu-dev-zwu/ioport.c:174  
  11. #4 0x00005555558e13d5 in memory_region_write_accessor (mr=0x5555565c0670,   
  12. addr=0, value=0x7fffb4dbd528, size=2, shift=0, mask=65535)  
  13. at /home/dashu/kvm/qemu/qemu-dev-zwu/memory.c:440  
  14. #5 0x00005555558e151d in access_with_adjusted_size (addr=0,   
  15. value=0x7fffb4dbd528, size=2, access_size_min=1, access_size_max=4,   
  16. access=0x5555558e1341 <memory_region_write_accessor>, mr=0x5555565c0670)  
  17. at /home/dashu/kvm/qemu/qemu-dev-zwu/memory.c:477  
  18. #6 0x00005555558e3dfb in memory_region_dispatch_write (mr=0x5555565c0670,   
  19. addr=0, data=8995, size=2)  
  20. at /home/dashu/kvm/qemu/qemu-dev-zwu/memory.c:984  
  21. #7 0x00005555558e7384 in io_mem_write (mr=0x5555565c0670, addr=0, val=8995,   
  22. size=2) at /home/dashu/kvm/qemu/qemu-dev-zwu/memory.c:1748  
  23. #8 0x000055555586a18e in address_space_rw (as=0x555556216d80, addr=496,   
  24. buf=0x7fffb4dbd670 "##", len=2, is_write=true)  
  25. at /home/dashu/kvm/qemu/qemu-dev-zwu/exec.c:1968  
  26. #9 0x000055555586a474 in address_space_write (as=0x555556216d80, addr=496,   
  27. buf=0x7fffb4dbd670 "##", len=2)  
  28. at /home/dashu/kvm/qemu/qemu-dev-zwu/exec.c:2030  
  29. #10 0x00005555558d98c9 in cpu_outw (addr=496, val=8995)  
  30. at /home/dashu/kvm/qemu/qemu-dev-zwu/ioport.c:61  

bdrv_aio_writev最终调用bdrv_co_aio_rw_vector函数, 该函数调用co = qemu_coroutine_create(bdrv_co_do_rw) 创建一个协程去执行bdrv_co_do_rw函数,bdrv_co_wo_rw函数的函数栈如下:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
 
  1. #1 0x000055555563653c in paio_submit (bs=0x5555562a13d0, fd=10, sector_num=2,   
  2. qiov=0x555556715ab0, nb_sectors=1,   
  3. cb=0x5555556028b1 <bdrv_co_io_em_complete>, opaque=0x555556964e30, type=1)  
  4. at block/raw-posix.c:825  
  5. #2 0x0000555555636659 in raw_aio_submit (bs=0x5555562a13d0, sector_num=2,   
  6. qiov=0x555556715ab0, nb_sectors=1,   
  7. cb=0x5555556028b1 <bdrv_co_io_em_complete>, opaque=0x555556964e30, type=1)  
  8. at block/raw-posix.c:853  
  9. #3 0x00005555556366c9 in raw_aio_readv (bs=0x5555562a13d0, sector_num=2,   
  10. qiov=0x555556715ab0, nb_sectors=1,   
  11. cb=0x5555556028b1 <bdrv_co_io_em_complete>, opaque=0x555556964e30)  
  12. at block/raw-posix.c:861  
  13. #4 0x00005555556029b8 in bdrv_co_io_em (bs=0x5555562a13d0, sector_num=2,   
  14. nb_sectors=1, iov=0x555556715ab0, is_write=false) at block.c:4038  
  15. #5 0x0000555555602a49 in bdrv_co_readv_em (bs=0x5555562a13d0, sector_num=2,   
  16. nb_sectors=1, iov=0x555556715ab0) at block.c:4055  
  17. #6 0x00005555555fed61 in bdrv_co_do_readv (bs=0x5555562a13d0, sector_num=2,   
  18. nb_sectors=1, qiov=0x555556715ab0, flags=0) at block.c:2547  
  19. #7 0x00005555555fee03 in bdrv_co_readv (bs=0x5555562a13d0, sector_num=2,   
  20. nb_sectors=1, qiov=0x555556715ab0) at block.c:2573  
  21. #8 0x0000555555637d8c in raw_co_readv (bs=0x55555629e9b0, sector_num=2,   
  22. nb_sectors=1, qiov=0x555556715ab0) at block/raw.c:47  
  23. #9 0x00005555555fed61 in bdrv_co_do_readv (bs=0x55555629e9b0, sector_num=2,   
  24. nb_sectors=1, qiov=0x555556715ab0, flags=0) at block.c:2547  
  25. #10 0x00005555556023af in bdrv_co_do_rw   

最终在paio_summit中会往线程池中提交一个请求thread_pool_submit_aio(pool, aio_worker, acb, cb, opaque), 由调度器去执行aio_worker函数,aio_worker是真正做IO操作的函数,它通过pwrite和pread去读取磁盘.

 

    当qemu完成IO操作后,会在kvm_cpu_exec函数的循环中,调用kvm_vcpu_ioctl重新进入kvm.

    以上阐述了IO操作在kvm和qemu中处理的整个过程.

 

参考资料:

1. kvm代码解析连载(二):io的虚拟化:http://blog.youkuaiyun.com/fanwenyi/article/details/12748613

<think>我们正在讨论Qemu-KVM虚拟机迁移的具体流程。根据引用内容,我们知道QEMU-KVM是一个用户态进程,负责设备模拟,并通过ioctl内核模块KVM交互。虚拟机迁移涉及将虚拟机状态(包括CPU、内存、设备状态等)从源主机传输到目标主机。 迁移流程通常包括以下步骤: 1. 预迁移(Pre-migration):源主机检查虚拟机是否可迁移(如设备是否支持迁移)。 2. 预留资源(Reserve resources):目标主机预留资源(如内存、CPU等)以接收虚拟机。 3. 迭代迁移(Iterative migration):源主机将内存页面复制到目标主机。在复制过程中,源主机继续运行,因此被修改的页面需要重新复制(称为脏页)。这个过程会迭代多次,直到脏页数量足够少。 4. 停机拷贝(Stop-and-copy):当脏页数量低于阈值时,源主机暂停虚拟机,将剩余脏页和虚拟机设备状态传输到目标主机。 5. 提交(Commit):目标主机确认接收成功,源主机确认迁移完成。 6. 启动(Activation):目标主机启动虚拟机,源主机销毁原虚拟机。 由于用户要求流程图,我们可以用文字描述流程,然后使用Mermaid语法绘制流程图。 流程图描述如下: - 开始 - 源主机:发起迁移命令 - 目标主机:创建虚拟机环境(但不运行) - 循环(迭代迁移): - 源主机传输内存页到目标主机 - 源主机记录脏页 - 如果脏页数量小于阈值,则退出循环 - 源主机暂停虚拟机 - 源主机传输剩余脏页和设备状态到目标主机 - 目标主机加载状态并启动虚拟机 - 源主机确认迁移成功,关闭原虚拟机 - 结束 下面用Mermaid语法绘制流程图: ```mermaid flowchart TD A[开始迁移] --> B[源主机: 检查迁移条件] B --> C[目标主机: 创建虚拟机环境] C --> D[迭代迁移] D --> E[源主机传输内存页] E --> F[源主机记录脏页] F --> G{脏页数量是否小于阈值?} G ----> D G ----> H[源主机暂停虚拟机] H --> I[源主机传输剩余脏页和设备状态] I --> J[目标主机启动虚拟机] J --> K[源主机确认迁移] K --> L[源主机关闭原虚拟机] L --> M[结束] ``` 详细步骤说明: 1. **预迁移**:在迁移开始前,源主机上的QEMU进程会检查虚拟机状态,确认是否支持迁移(如设备状态是否支持迁移)。 2. **目标主机准备**:目标主机上通过QEMU命令创建一个虚拟机配置相同的虚拟机(处于暂停状态),准备接收数据。 3. **迭代迁移**:源主机将内存分多次传输到目标主机。每次传输后,源主机继续运行虚拟机,同时通过KVM记录被修改的内存页(脏页)。然后,在下一次迭代中传输这些脏页。这个过程重复进行,直到脏页的数量足够少(通常由迁移算法确定)。 4. **停机拷贝**:当脏页数量低于阈值(或达到一定迭代次数)时,源主机暂停虚拟机,将剩余的脏页以及虚拟机的设备状态(通过设备模型保存的状态)传输到目标主机。这一步确保虚拟机状态的一致性。 5. **提交阶段**:目标主机接收到所有数据后,通知源主机迁移成功。源主机则确认迁移完成。 6. **启动目标虚拟机**:目标主机上的QEMU进程启动虚拟机,此时虚拟机从暂停状态恢复运行。 7. **清理**:源主机上的原虚拟机被销毁。 注意:设备状态的迁移是复杂的一步,因为每个设备(如磁盘、网卡)都有自己的状态。QEMU在设备模型中提供了状态序列化的方法,以便在迁移时能够保存和恢复设备状态。 引用说明: - 引用[1]说明了KVM虚拟机QEMU-KVM进程,其CPU和内存都是该进程的一部分,因此迁移时需要迁移该进程的地址空间(即内存)和CPU状态(寄存器等)。 - 引用[2]和[3]说明了QEMUKVM的交互方式,迁移过程中QEMU通过ioctlKVM通信来获取虚拟机的状态(如脏页信息、寄存器状态等)。 相关问题: 1. 虚拟机迁移过程中如何保证数据一致性? 2. 迁移过程中网络连接如何保持? 3. 虚拟机迁移的性能优化有哪些方法?</think>### Qemu-KVM虚拟机迁移流程详解 Qemu-KVM虚拟机迁移是将运行中的虚拟机从源主机迁移到目标主机的过程,核心是通过QEMU进程KVM模块协作实现状态转移。以下是完整迁移流程及流程图: #### **迁移流程图** ```mermaid flowchart TD A[源主机] -->|1. 预迁移检查| B[目标主机] B -->|2. 资源预留| C[创建空虚拟机] C -->|3. 迭代内存迁移| D[传输内存页] D -->|4. 脏页跟踪| E{脏页率<br>是否达标?} E -->|否| D E -->|是| F[暂停源虚拟机] F -->|5. 状态同步| G[传输剩余脏页+设备状态] G -->|6. 目标机激活| H[启动目标虚拟机] H -->|7. 清理| I[释放源主机资源] ``` #### **详细步骤解析** 1. **预迁移阶段(Pre-migration)** - 源主机QEMU进程通过`ioctl(KVM_CHECK_MIGRATION)`验证虚拟机是否可迁移[^3]。 - 目标主机创建相同配置的空虚拟机QEMU-KVM进程),但不启动CPU线程[^1]。 2. **迭代内存迁移(Iterative Memory Copy)** - 源主机QEMU通过`ioctl(KVM_GET_DIRTY_LOG)`获取脏页位图[^2]。 - 仅传输被修改的内存页(脏页),未修改页面直接复用。 - 循环执行直到脏页率低于阈值(通常<0.1%)。 3. **停机状态同步(Stop-and-Copy)** - 源主机暂停虚拟机KVM冻结所有vCPU线程[^1]。 - 传输剩余脏页+完整设备状态: - CPU寄存器:通过`ioctl(KVM_GET_REGS)`获取[^2] - 设备状态:QEMU序列化模拟设备(磁盘/网卡等)到内存缓冲区[^3] - 目标主机加载状态并恢复vCPU线程。 4. **迁移完成(Commit)** - 目标主机QEMU发送迁移完成信号。 - 源主机销毁原虚拟机进程,释放内存和资源[^1]。 - 网络流量自动重定向到目标主机(通过ARP更新)。 #### **关键机制说明** - **脏页跟踪**: KVM在EPT/NPT页表中标记写保护页,当客户机修改内存时触发VMExit,KVM记录脏页位置[^2]。 - **设备状态迁移**: QEMU调用每个模拟设备的`save_state`回调函数(如网卡缓存、磁盘未完成IO),序列化为进制流传输[^3]。 - **零停机优化**: 使用`Post-Copy`模式:先迁移CPU状态快速启动目标机,后台异步拉取内存页,减少停机时间。 #### **典型性能瓶颈** 1. **内存脏页率**:高负载虚拟机脏页生成速度快于网络传输速度。 2. **设备状态大小**:GPU等复杂设备状态序列化耗时长。 3. **网络带宽**:千兆网络迁移8GB内存虚拟机约需60-90秒。 > 迁移成功率依赖设备模拟的兼容性。例如使用virtio-blk/virtio-net等标准设备可确保目标主机状态还原一致性[^3]。 --- ### 相关问题 1. 如何通过预拷贝(Pre-copy)和后期拷贝(Post-copy)优化迁移停机时间? 2. 虚拟机迁移过程中如何保证存储设备的数据一致性? 3. KVM的脏页跟踪机制在CPU虚拟化层面如何实现? 4. 迁移失败时有哪些回滚机制保障业务连续性?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值