使用qemu创建并运行虚拟机,背后是怎么的流程呢?
主函数main vl.c:2365
->注册atexit(qemu_run_exit_notifiers),表示注册的函数在进程exit时自动执行,如有多个,则倒序执行,目前仅usb加入notifier,(usb_host_exit_notifier)
->error_set_progname,获取程序名,目的打印日志
->g_mem_set_vtable(&mem_trace),对内存操作注册trace点,也就是g_malloc,g_realloc,g_free
->g_thread_supported 检查glibc的pthread库是否支持
->module_call_init(MODULE_INIT_QOM),链表完成初始化
->runstate_init,qemu状态数组初始化,runstate_transitions_def数组内状态变化才是合法的,当前状态可以通过qmp_query_status查询
->init_clocks,初始化clock,将时钟初始化为host_clock
->QLIST_INIT (&vm_change_state_head),初始化vm状态变化列表,注册状态变化时的回调,实现状态变化的动作。
->os_setup_early_signal_handling(),忽略SIGPIPE信号,当内核向进程send一个dsconnect pipe信号时由进程默认退出变为忽略该信号
->查看是否存在QEMU_OPTION_nodefconfig和QEMU_OPTION_nouserconfig这两个参数配置,如果存在则使用配置文件的(qemu.conf或target_”TRAGET_ARCH“.conf),lookup_opt为查找对于的选项,返回QemuOption参数optarg、option的序号
->继续解析参数,如果没有‘-’选项,则表示hda选项
->解析sandbox
->设置qemu日志输出文件,-D logfile表示将日志输出到logfile文件,Log_mask:-d,Log_file:-D
->trace_backend_init 初始化trace event与file(打印目标文件)
->检查最大smp cpu数目
->default设备处理,关于设备,不包括device,chardev,包括parallel,serial,usb,virtcon等
->初始化chardev、fsdev、device、内存设备
->os_daemonize,后台进程
->configure_accelerator ->kvm_init km-all.c,打开/dev/kvm,s->fd = qemu_open("/dev/kvm", O_RDWR);,获得kvmfd,s->vmfd = kvm_ioctl(s, KVM_CREATE_VM, 0),创建虚拟机,获得vmfd
->qemu_init_cpu_loop 初始化cpu用到的条件变量
->半虚的参数设置,kernel_filename、initrd_filename、kernel_cmdline
->os_set_line_buffering 标准输出以行缓冲方式
->注册退出函数 net_init_clients、cpu_exec_init_all、cpu_exec_init_all、bdrv_init_with_whitelist、blk_mig_init
->machine->init(ram_size, boot_devices,
kernel_filename, kernel_cmdline, initrd_filename, cpu_model),参数包括从命令行传入解析后得到的,ram的大小,内核镜像文件名,内核启动参数,initramdisk文件名,cpu模式等–>使用系统的默认machine,初始化为函数为pc_init_pci 。

pc_init_pci() file: pc_piix.c, line: 294
 —>pc_init1() file: pc_piix.c, line: 123
 —>pc_cpus_init() file: pc.c, line: 941


在命令行启动时配置的smp参数在这里启作用了,qemu根据配置的cpu个数,进行n次的cpu初始化,相当于n个核的执行体。

void pc_cpus_init(const char cpu_model)
 {
 int i;
 / init CPUs */
 for(i = 0; i < smp_cpus; i++) {
 pc_new_cpu(cpu_model);
 }


继续cpu的初始化:

pc_new_cpu() file: hw/pc.c, line: 915
 —>cpu_x86_init() file: target-i386/helper.c, line: 1150
 —>x86_cpu_realize() file: target-i386/cpu.c, line: 1767
 —>qemu_init_vcpu() file: cpus.c, line: 1039
 —>qemu_kvm_start_vcpu() file: cpus.c, line: 1011
 —>qemu_kvm_cpu_thread_fn file: cpus.c, line:728


VM真正的执行体是QEMU进程创建的一系列POSIX线程,而线程执行函数为qemu_kvm_cpu_thread_fn。
kvm_init_vcpu()通过KVM_CREATE_VCPU创建了vcpu描述符/vcpufd。
并进入了while(1)的循环循环,反复调用kvm_cpu_exec()。
kvm_cpu_exec(CPUArchState *env) file: kvm-all.c, line: 1543 看到kvm_cpu_exec()中又是一个do()while(ret == 0)的循环体,该循环体中主要通过KVM_RUN启动VM的运行,从此处进入了kvm的内核处理阶段,并等待返回结果,同时根据返回的原因进行相关的处理,最后将处理结果返回。因为整个执行体在上述函数中也是在循环中,所以后续又会进入到该函数的处理中,而整个VM的cpu的处理就是在这个循环中不断的进行。
Conclusion

总结下kvm run在Qemu中的核心流程:

解析参数;

创建三大描述符:kvmfd/vmfd/vcpufd,及相关的初始化,为VM的运行创造必要的条件;

根据cpu的配置数目,启动n个POSIX线程运行VM实体,所以vm的执行环境追根溯源是在Qemu创建的线程环境中开始的。

通过KVM_RUN调用KVM提供的API发起KVM的启动,从这里进入到了内核空间运行,等待运行返回;

重复循环进入run阶段。

qemu替代 qemu trace_qemu