前言
gdb调试程序会阻塞被调试程序运行,gdb走一步,程序才能走一步,形如:
1 | gdb |
那qemu的monitor中使用HMP/QMP与虚拟机交互交互会不会导致qemu挂起呢?monitor和qemu交互是同步还是异步?如果是同步那qemu是如何挂起的
monitor等待命令输入时不会阻塞qemu进行,不像gdb那样等待命令时阻塞被调试程序运行。只是在输入命令敲入回车的瞬间,vCPU将事件通知到主循环让主循环dispatch执行,而在这之前主循环一直在处理其他工作,主循环执行完成后vCPU继续他的工作。
存在几种同步方法
可以用work item和全局锁同步。测试方法:info register
主循环循环prepare,poll,dispatch等待vCPU通知事件发生。当与monitor交互后主循环dispatch到monitor的处理流程:
单步跟踪发现info register
对应的处理函数为cpu_dump_state
:
1 | void cpu_dump_state(CPUState *cpu, FILE *f, int flags) |
cpu_dump_state
首先进行一些同步相关的函数cpu_synchronize_state()
,之后执行主功能函数cc->dump_state
。
1 | void cpu_synchronize_state(CPUState *cpu) |
可见不是任何情况都需要做cpu_synchronize_state
操作的(下面会介绍kvm和tcg下的情况)。而x86虚拟机的cc->dump_state = x86_cpu_dump_state
,主要是用qemu_fprintf
这个qapi打印信息
cpu work item
kvm下可以将事件添加到cpu的work list中,vCPU线程会负责处理work list中的事件。
kvm
启用kvm的情况下会调用cpus_accel->synchronize_state
“同步函数”,为kvm_cpu_synchronize_state
:
1 | void kvm_cpu_synchronize_state(CPUState *cpu) |
run_on_cpu
调用do_run_on_cpu
让主循环进入等待,等待vCPU线程处理,从而实现同步:
1 | void do_run_on_cpu(CPUState *cpu, run_on_cpu_func func, run_on_cpu_data data, |
queue_work_on_cpu()
将一个work item加入cpu的work list尾部,下面的while循环中最终会调用pthread库的pthread_cond_wait()
函数等待vCPU调用qemu_wait_io_event()
处理,从而实现主循环线程和vCPU线程的同步。
1 | static void *kvm_vcpu_thread_fn(void *arg) |
全局锁
全局锁是tcg和kvm下都存在的同步方式,下面用tcg下输入info register
命令说明同步机制
tcg
tcg无法通过cpus_accel->synchronize_state
判断不会像kvm一样有特殊的同步方法。通过gdb调试,猜测tcg的同步通过全局锁完成,证明如下:
1 | if (cpus_accel->synchronize_state) { |
如下命令启动虚拟机,在cpu_dump_state
初打上断点,info register
触发。
1 | gdb \ |
切换至vCPU线程thread 3
,发现其似乎在等待锁。当前位置打上断点,观察何时会触发:
回到主循环,一路finish执行,观察vCPU线程断点的触发情况。发现在qemu_mutex_unlock(&qemu_global_mutex);
后断点触发,即主循环释放锁后vCPU线程继续执行。
解释:注意到main loop中是先unlock在lock的,而vCPU线程主函数也有全局锁保护
1 | qemu_main_loop -> main_loop_wait -> os_host_main_loop_wait |
主循环和vCPU都是在while循环中,锁保护的是主循环的dispatch
和vCPU线程的qemu_wait_io_event
。而主循环的poll和vCPU线程的cpu_exec
和同时发生。
因此monitor作为一个iothread,mon_iothread
,在主循环dispatch中进行,vCPU想要cpu_exec
之后的操作需要等待下一次主循环poll发生。