前言: 这里作者再次自不量力了,以一点微末的道行分析一下KVM的CPU虚拟化部分的代码。 分析: 1,分析具体代码逻辑之前,可以先使用strace大致看一下qemu启动的时候,和kernel的交互。 在正常启动qemu的命令之前加入strace即可:strace qemu-system-x86_64 -enable-kvm -m 2048 -drive if=virtio,file=/home/ubuntu-server-1604.qcow2,cache=none -redir :8090::80 -redir :8022::22 -cpu qemu64, vmx -vnc :0 -smp 2
通过这段trace可以大致看出来,qemu首先打开/dev/kvm这个device,然后通过ioctl和它交互。 重点关注KVM_CREATE_VM,又创建出来了新的fd。 查看qemu打开的所有文件(包括设备,Linux上一切皆文件),可以使用lsof -p PID,或者ls -al /proc/PID/fd:
结合上述的strace,不难发现,先打开/dev/kvm作为文件描述符9,再通过ioctl 9得到文件描述符10,即anon_inode:kvm-vm;同理继续可以分析出来其他的fd。大致可以简单了解逻辑。 2,/dev/kvm 先来分析一下/dev/kvm的创建过程。代码在linux-4.0.4/virt/kvm目录。 在kvm_main.c中,kvm_init函数中会注册一个device(by misc_register(&kvm_dev)),
kvm_dev的名字就是“kvm”,即之前看到的/dev/kvm设备,KVM_MINOR是宏定义232,通过命令可以验证:file /dev/kvm /dev/kvm: character special (10/232) /dev/kvm的ioctl就是通过kvm_chardev_ops.kvm_dev_ioctl函数。 3,KVM_CREATE_VM 从kvm_dev_ioctl中选择KVM_CREATE_VM分支,即kvm_dev_ioctl_create_vm函数:
kvm_create_vm函数主要用来创建并初始化kvm数据结构,包括lock,memslot,mmu notifier等,并把数据结构加入到vm_list(双链表,用来保存本机上KVM创建的的所有vm)中; coalesced mmio不在这里分析。 anon_inode_getfd函数创建一个名为“kvm-vm”的一个新的匿名文件,即上文的文件描述符为10的anon_inode:kvm-vm,并注册这个文件的file operation-----kvm_vm_fops。 4,create vcpu 通过函数kvm_vm_fops .kvm_vm_ioctl的KVM_CREATE_VCPU分支----kvm_vm_ioctl_create_vcpu。
首先创建kvm_vcpu类型的vcpu数据结构。intel架构下,会使用linux-4.0.4/arch/x86/kvm/vmx.c中的vmx_create_vcpu函数。函数中初始化vcpu,并分配vmx数据结构,同时申请vmcs(vmcs很复杂,需要参考文档:https://software.intel.com/en-us/articles/intel-sdm,3B部分介绍intel的VMX)。 然后添加vcpu到vm中。当然,这里会根据id检查是否已经添加过了。 最后,还是会通过create_vcpu_fd函数创建匿名文件kvm-vcpu,即上文的anon_inode:kvm-vcpu。同理,用户进程可以通过这个匿名文件的描述符进行ioctl操作。 5,vcpu run anon_inode:kvm-vcpu提供了kvm_vcpu_ioctl函数进行vcpu的ioctl。 当用户进程请求了KVM_RUN之后,会通过这样的路径让CPU进入vm模式: kvm_arch_vcpu_ioctl_run(linux-4.0.4/arch/x86/kvm/x86.c)->__vcpu_run->vcpu_enter_guest->vmx_vcpu_run(linux-4.0.4/arch/x86/kvm/vmx.c) cpu进入到了vm模式,就在跑虚拟机中的代码。在虚拟机中,这就是一个cpu。如果遇到异常,比如说Guest使用了IO指令访问,那么就会让cpu退出vm模式,并把异常原因交给qemu来处理;如果qemu进程发生了缺页中断,那么host就需要给qemu进程分配page,分配之后,qemu进程继续跑,不需要qemu进程处理什么,当然,Guest中也不感知。 6,qemu KVM_CREATE_VM qemu中,init machine中,因为选择了kvm硬件加速,所以会通过kvm_init函数创建kvm的vm。下图是backtrace。
7,qemu KVM_CREATE_VCPU & KVM_RUN
pthread_create中会调用clone创建新的线程,由上图的backtrace可见,每一个vcpu都对应一个用户态的线程。用户态的多线程同时在跑,对应的就是vm中的多核心在跑。
创建完vcpu,qemu通过kvm_cpu_exec,进而进入vm模式(by kvm_vcpu_ioctl(cpu, KVM_RUN, 0))。 8,vcpu exit 如上文所说,vcpu如果遇到异常,且是需要qemu来处理的情况下,那么将会退出vm模式。qemu继续执行kvm_cpu_exec函数。 qemu从kernel的kvm中获取到exit reason,然后做出相应的逻辑:
例如,Guest中访问使用了IO指令导致了退出了vm模式,那么qemu会调用kvm_handle_io。处理完成后,继续进入vm模式执行。 后记: 水平有限,CPU过于相关的,包括一些汇编代码,都需要对照intel的开发文档来分析。这里只对整体逻辑做了简单分析。 惭愧,惭愧~