https://github.com/qemu/qemu/blob/stable-4.2/cpus.c#L1290 中蕴藏着一个非常重要的Qemu。我想这是 KVM 上 CPU 的事件循环。
这是代码:
static void *qemu_kvm_cpu_thread_fn(void *arg)
{
CPUState *cpu = arg;
int r;
rcu_register_thread();
qemu_mutex_lock_iothread();
qemu_thread_get_self(cpu->thread);
cpu->thread_id = qemu_get_thread_id();
cpu->can_do_io = 1;
current_cpu = cpu;
r = kvm_init_vcpu(cpu);
if (r < 0) {
error_report("kvm_init_vcpu failed: %s", strerror(-r));
exit(1);
}
kvm_init_cpu_signals(cpu);
/* signal CPU creation */
cpu->created = true;
qemu_cond_signal(&qemu_cpu_cond);
qemu_guest_random_seed_thread_part2(cpu->random_seed);
do {
if (cpu_can_run(cpu)) {
r = kvm_cpu_exec(cpu);
if (r == EXCP_DEBUG) {
cpu_handle_guest_debug(cpu);
}
}
qemu_wait_io_event(cpu);
} while (!cpu->unplug || cpu_can_run(cpu));
qemu_kvm_destroy_vcpu(cpu);
cpu->created = false;
qemu_cond_signal(&qemu_cpu_cond);
qemu_mutex_unlock_iothread();
rcu_unregister_thread();
return NULL;
}
我对do
循环感兴趣。它在循环中调用kvm_cpu_exec
,定义如下:https://github.com/qemu/qemu/blob/stable-4.2/accel/kvm/kvm-all.c#L2285
在kvm_cpu_exec
的某一点,它调用run_ret = kvm_vcpu_ioctl(cpu, KVM_RUN, 0);
,它调用这里记录的KVM_RUN
ioctl:https://www.kernel.org/doc/Documentation/virtual/kvm/api.txt
4.10 KVM_RUN
功能:基本
体系结构:全部
类型:vcpu ioctl
参数:无
返回:成功时返回 0,出错时返回 -1
错误:
EINTR:未屏蔽的信号挂起此 ioctl 用于运行客户机虚拟 CPU。 虽然没有
显式参数,但有一个隐式参数<块可以通过 在偏移量=" _x0030_=" 处对=" vcpu=" fd=" 进行=">获得,大小由
KVM_GET_VCPU_MMAP_SIZE 给出。 参数块的格式为"struct
kvm_run"(见下文)。块可以通过>
我正在努力理解这个ioctl是否阻止执行?在哪些情况下它会返回?
我想了解一下正在发生的事情的背景。给定行qemu_wait_io_event(cpu)
,看起来至少,每次向 CPU 读取/写入事件时,ioctl都会返回。我不知道,我很困惑。
KVM API 设计要求虚拟机中的每个虚拟 CPU 在程序(如控制该虚拟机的 QEMU)中都有一个关联的用户空间线程(此程序通常称为"虚拟机监视器"或 VMM,它不必是 QEMU;其他示例是 KVMtool 和 Firecracker)。
该线程的行为类似于 QEMU 中的普通用户空间线程,直到它使 KVM_RUN ioctl。此时,内核使用该线程在与该线程关联的 vCPU 上执行客户机代码。这一直持续到遇到某些情况,这意味着来宾执行无法继续。(一种常见情况是"来宾对 QEMU 正在模拟的设备进行了内存访问"。此时,内核将停止在此线程上运行来宾代码,而是使其从KVM_RUN ioctl 返回。然后,QEMU 中的代码查看返回代码等,以找出它为什么重新获得控制权,处理任何情况,然后循环回KVM_RUN再次调用以请求内核继续运行来宾代码。
通常,在运行 VM 时,你会看到线程几乎一直位于 KVM_RUN ioctl 内,运行真正的来宾代码。偶尔执行会返回,QEMU 会花尽可能少的时间做它需要做的任何事情,然后它会循环并再次运行来宾代码。提高 VM 效率的一种方法是尝试确保这些"VM 出口"的数量尽可能低(例如,通过仔细选择为来宾提供的网络或块设备类型)。