Friday, February 7, 2020
kvm_all.c - kvm_init, kvm_init_vcpu
继续接着前面的东西来,了解QEMU如何在KVM虚拟化环境中创建和初始化虚拟机和vCPU。
1. kvm_init
kvm_init函数用于打开KVM设备文件,如下所示,它还填充KVMState的fd和vmfd:
static int kvm_init(MachineState *ms) { …… KVMState *s; …… s = KVM_STATE(ms->accelerator); …… s->vmfd = -1; s->fd = qemu_open("/dev/kvm", O_RDWR); if (s->fd == -1) { fprintf(stderr, "Could not access KVM kernel module: %mn"); ret = -errno; goto err; } …… do { ret = kvm_ioctl(s, KVM_CREATE_VM, type); } while (ret == -EINTR); if (ret < 0) { fprintf(stderr, "ioctl(KVM_CREATE_VM) failed: %d %sn", -ret, strerror(-ret)); goto err; } s->vmfd = ret; …… ret = kvm_arch_init(ms, s); if (ret < 0) { goto err; } …… return ret; }
如上所示,带有KVM_CREATE_VM参数的ioctl将返回vmfd。一旦QEMU有了fd和vmfd,就必须再填充一个文件描述符,即kvm_fd或vcpu fd。
2. kvm_init_vcpu
int kvm_init_vcpu(CPUState *cpu) { …… KVMState *s = kvm_state; int ret; …… ret = kvm_get_vcpu(s, kvm_arch_vcpu_id(cpu)); if (ret < 0) { goto err; } cpu->kvm_fd = ret; cpu->kvm_state = s; …… ret = kvm_arch_init_vcpu(cpu); err: return ret; }
一些内存页在qemu-kvm进程和KVM内核模块之间共享。你可以在kvm_init_vcpu()函数中看到这样的映射。也就是说,每个vCPU的两个主机内存页为QEMU用户空间进程和KVM内核模块之间的通信提供了通道:kvm_run和pio_data。
还要理解,在执行这些返回前面fd的ioctl期间,Linux内核会分配一个文件结构和相关的匿名节点。
vCPU是QEMU-KVM创建的线程,要运行来自guest代码,这些vCPU线程需要使用KVM_RUN作为参数执行ioctl,即第一篇文章(http://www.qemu.world/?x=entry:entry200206-201418)的:
run_ret = kvm_vcpu_ioctl(cpu, KVM_RUN, 0);
kvm_all.c - ioctls (kvm_ioctl, kvm_vm_ioctl, kvm_vcpu_ioctl, kvm_device_ioctl)
kvm_all.c是QEMU与KVM通信相关的一个非常重要的文件,因此它必然也有许多和通信相关的内容。KVM是一个内核模块,普通权限的QEMU若要和KVM通信,则使用ioctl是最方便的。
在kvm_all.c中,封装了多个ioctl相关的函数:kvm_ioctl, kvm_vm_ioctl, kvm_vcpu_ioctl, kvm_device_ioctl,这些ioctl最终会反映到系统的KVM,VM和vCPU三个级别上。 这些ioctl与KVM分类的ioctl基本一一对应。
要通过ioctl访问KVM内核模块,QEMU-KVM必须打开/dev/kvm,并将结果文件描述符存储在KVMState->fd中。
这四个函数基本内容一模一样,只是通过不同的文件描述符来发送ioctl。接下来来依次介绍它们。
1. kvm_ioctl
调用这个发送的ioctl,需要由KVMState->fd去发送,其中KVMState->fd是通过打开/dev/kvm获得的文件描述符。
定义如下:
int kvm_ioctl(KVMState *s, int type, ...) { …… ret = ioctl(s->fd, type, arg); …… }
2. kvm_vm_ioctl
调用这个函数发送的ioctl主要经由KVMState->vmfd来执行。
定义如下:
int kvm_vm_ioctl(KVMState *s, int type, ...) { …… ret = ioctl(s->vmfd, type, arg); …… }
3. kvm_vcpu_ioctl
与前面类似的,这个ioctl经由cpu->kvm_fd来执行。 CPUState等的定义可参考上一篇(http://www.qemu.world/?x=entry:entry200207-131048)文章。
定义如下:
int kvm_vcpu_ioctl(CPUState *cpu, int type, ...){ …… ret = ioctl(cpu->kvm_fd, type, arg); …… }
4. kvm_device_ioctl
这个稍稍有点不一样,它是个更通用一点的wrapper,先看定义:
int kvm_device_ioctl(int fd, int type, ...) { …… ret = ioctl(fd, type, arg); …… }
fd是从第一个参数传来的,基本就是个工具函数,使用例子例如:
int kvm_device_check_attr(int dev_fd, uint32_t group, uint64_t attr) { …… return kvm_device_ioctl(dev_fd, KVM_HAS_DEVICE_ATTR, &attribute) ? 0 : 1; }
kvm_all.c - struct KVMState, CPUState, CPUX86State
KVMState结构包含QEMU中VM表示的重要文件描述符。例如fd, vmfd。
struct KVMState { AccelState parent_obj; int nr_slots; int fd; int vmfd; int coalesced_mmio; int coalesced_pio; struct kvm_coalesced_mmio_ring *coalesced_mmio_ring; bool coalesced_flush_in_progress; …… };
QEMU维护一个CPUState结构,它位于include/hw/core/cpu.h,用于定义一个CPU内核/线程的状态。CPUState的结构含义如下:
cpu_index:CPU索引,这里面能获取到很多信息。
cluster_index:标识此CPU所在的群集。对于未定义集群的板卡或未分配给集群的“松散” CPU,将为UNASSIGNED_CLUSTER_INDEX;否则,它将与CPU对象的TYPE_CPU_CLUSTERQOM父级的cluster-id属性相同。
nr_cores:此CPU软件包中的内核数。
nr_threads:此CPU中的线程数。
running:如果CPU当前正在运行(无锁),则为true。
has_waiter:如果CPU当前正在等待cpu_exec_end,true。在cpu_list_lock下有效。
created:指示是否已成功创建CPU线程。
interrupt_request:表示一个未决的中断请求。
halted:如果CPU处于挂起状态,则为非零。
stop:表示待处理的停止请求。
stopped:表示CPU已被人为停止。
unplug:指示挂起的CPU拔出请求。
crash_occurred:表示操作系统报告了该CPU的紧急崩溃事件
singlestep_enabled:单步执行的标志。
icount_extra:直到下一个定时器事件的指令。
can_do_io:如果内存映射的IO安全,则为非零。确定性执行要求仅对TB的最后一条指令执行IO,以便中断立即生效。
cpu_ases:指向CPUAddressSpaces数组的指针(定义该CPU具有的AddressSpaces)
num_ases:@cpu_ases中的CPUAddressSpaces数
as:指向第一个地址空间的指针,以方便仅具有单个地址空间的目标
env_ptr:指向特定于子类的CPUArchState字段的指针。
icount_decr_ptr:指向子类中IcountDecr字段的指针。
gdb_regs:其他GDB寄存器。
gdb_num_regs:GDB可访问的寄存器总数。
gdb_num_g_regs:GDB’g'数据包中的寄存器数。
next_cpu:下一个CPU共享TB缓存。
opaque:用户数据。
mem_io_pc:访问内存的主机程序计数器。
kvm_fd:KVM的vCPU文件描述符。
work_mutex:锁定以防止多次访问queued_work_xxx。
queued_work_first:第一个异步工作挂起。
trace_dstate_delayed:延迟了对trace_dstate的更改(包括对trace_dstate的所有更改)。
trace_dstate:此vCPU的事件的动态跟踪状态(位掩码)。
plugin_mask:插件事件位图。仅通过异步工作进行修改。
ignore_memory_transaction_failures:具有相同名称的MachineState标志的缓存副本:允许开发板禁止调用CPU do_transaction_failed挂钩函数。
对于例如X86模式的CPU来说,QEMU会维护一个CPUX86State结构列表,每个虚拟CPU对应一个结构。它位于target/i386/cpu.h,通用寄存器(以及RSP和RIP)的内容是CPUX86State的一部分。由于定义太长,我删除了绝大多数内容。
typedef struct CPUX86State { /* standard registers */ target_ulong regs[CPU_NB_REGS]; target_ulong eip; target_ulong eflags; …… target_ulong cr[5]; /* NOTE: cr1 is unused */ int32_t a20_mask; …… ZMMReg xmm_regs[CPU_NB_REGS == 8 ? 8 : 32]; ZMMReg xmm_t0; MMXReg mmx_t0; …… /* For KVM */ uint32_t mp_state; int32_t exception_nr; int32_t interrupt_injected; uint8_t soft_interrupt; uint8_t exception_pending; uint8_t exception_injected; …… } CPUX86State;
Thursday, February 6, 2020
kvm_all.c - kvm_cpu_exec, kvm_handle_io
简介
kvm_all.c位于/accel/kvm/kvm-all.c,用于QEMU - KVM的通信,与其相关的许多重要的函数,例如kvm_handle_io、kvm_cpu_exec都位于此。
kvm_cpu_exec,kvm状态下的大消息循环,对应tcg模式下的tcg_cpu_exec。这个函数处理大量的vcpu产生的事件。
该函数在调用cpu_exec_start启动vcpu后,(1)调用kvm_vcpu_ioctl发送ioctl KVM_RUN给kvm,此时启动vCPU。该ioctl会进入对vCPU运行的等待状态。如果kvm发生VMEXIT,即从KVM返回到qemu-kvm用户空间时,则该函数读取run->exit_reason,获取退出的原因,并调用对应的处理函数。
KVM是英特尔和AMD等供应商提供的硬件扩展的推动者,它们的虚拟化扩展包括SVM和VMX。KVM使用这些扩展在主机CPU上直接执行客户代码。但是,如果存在事件(例如,作为由QEMU模拟的客户内核代码访问硬件设备寄存器的操作的一部分),则KVM必须退出回QEMU并传递控制。然后QEMU可以模拟操作的结果。
有不同的退出原因,如以下代码所示:
对应如下:
KVM_EXIT_IO - kvm_handle_io
KVM_EXIT_MMIO - address_space_rw
KVM_EXIT_IRQ_WINDOW_OPEN - return EXCP_INTERRUPT
KVM_EXIT_SHUTDOWN - qemu_system_reset_request
KVM_EXIT_UNKNOWN - 异常退出
KVM_EXIT_INTERNAL_ERROR - kvm_handle_internal_error
KVM_EXIT_SYSTEM_EVENT - 根据事件类型,分别有
KVM_SYSTEM_EVENT_SHUTDOWN = qemu_system_shutdown_request
KVM_SYSTEM_EVENT_RESET = qemu_system_reset_request
KVM_SYSTEM_EVENT_CRASH = qemu_system_guest_panicked
其他 = kvm_arch_handle_exit
其他 - kvm_arch_handle_exit
处理完后,如果没有产生错误,则继续循环,回到(1)处等待kvm。
如果产生错误,则调用cpu_exec_end,停止cpu执行。
——————————–
kvm_handle_io则相对简单,它其实是对address_space_rw的封装。它会将传入的,要写入对应设备的数据依次写入。通常,这与直接写往ioport并没有什么区别。