KVM虚拟化源码分析之KVM_TOOLS(三)

回顾

上篇博文我们分析到kvm_cmd_run命令中有3个核心函数

这里写图片描述

这篇博文主要分析kvm_cmd_run_init函数

kvm_cmd_run_init

这里写图片描述

确定CPU个数
而后是一个循环解析命令行参数,具体可以参考kvm_cmd_run_init的帮助信息

这里写图片描述

如果在上边解析参数的时候发现用户并没有制定内核文件位置,则通过find_kernel()来获得
而find_kernel()其实就是通过下边的位置顺序来找内核

这里写图片描述

这里写图片描述

这里找的是elf文件格式的内核

这里写图片描述

这里确定客户机内存的大小,如果用户没有在参数中指出,则通过get_ram_size()来获得,而实际上就是根据当前host的内存大小算一个比例。

这里写图片描述

设置内核启动的参数

这里写图片描述

设置启动用的根文件系统,如果用户没有指定,就自己创建一个最小根文件系统
后面跟着好多行都是干这事

这里写图片描述

kvm_run_init的最后调用了Init_list_init 函数。

这里写图片描述
这里写图片描述

从代码我们看到其实就是有一个链表数组,里面都是函数指针,用循环把他们都调一遍。。但是我们从main分析到现在并没有发现哪里有初始化这个链表数组,那里面的成员从何而来呢。。我们接着分析

init_lists链表数组的初始化

我们利用source insight的搜索功能,看哪里用到了这个数组

这里写图片描述

我们在init_list_add发现了往链表添加节点的操作,那么又是在哪里调用了init_list_add呢。

这里写图片描述
这里写图片描述

核心在于constructor属性,并赋予这个属性的函数会在main函数之前被执行一遍,比如我们关心core_init,

KVM虚拟化源码分析之KVM_TOOLS(三)插图14

例如上边,扩展后,就会变成一个名字为__init__kvm_init的函数,并且该函数是constructor属性,所以会在main之前被调用,而作为就是将kvm_init函数连入init_lists数组的第0个链表

我们先来看kvm_init做什么事

kvm_init

这里写图片描述

检查cpu是否开启了虚拟化支持

这里写图片描述

打开kvm设备节点/dev/kvm

这里写图片描述

检查API版本

这里写图片描述

创建虚拟机

这里写图片描述

这里其实就是检查kvm模块是否支持一些kvm_tools需要的接口

这里写图片描述

体系结构相关的一些初始化,主要有

ret = ioctl(kvm->vm_fd, KVM_SET_TSS_ADDR, 0xfffbd000);
ret = ioctl(kvm->vm_fd, KVM_CREATE_PIT2, &pit_config);
ret = ioctl(kvm->vm_fd, KVM_CREATE_IRQCHIP);

还有一个非常重要的是通过mmap分配虚拟地址空间,后面作为gust的物理地址
return mmap(NULL, size, PROT_RW, MAP_ANON_NORESERVE, -1, 0);

这里写图片描述

注册gust内存
利用来,kvm__register_mem(kvm, phys_start, phys_size, host_mem);
而该函数又通过下边来向Kvm模块注册gust物理内存 
ret = ioctl(kvm->vm_fd, KVM_SET_USER_MEMORY_REGION, &mem);

这里写图片描述

装载内核,后面几行是如果没有操作系统就装载固件,显然我们关心的是有操作系统的情况,这里其实我们也就发现了,kvm_tools并不是先运行Bios再由BIOS加载操作系统的
深入kvm_load_kernel我们发现,其实就是根据内核被加载到内存后应当所在的位置,直接将内核镜像拷贝到该guest物理地址所对应的当前进程的虚拟地址处。也就是上边通过mmap得到的那块内存。这就模拟来BIOS加载操作系统到内存的过程。

到此kvm_init函数结束,kvm_init是利用宏core_init来通过constructor属性连如init_lists中的,而类似的还有很多种xxx_init,大多数的xx_init都和设备模拟有关,我们现在并不关心,但是我们需要关心一下使用base_init宏连入init_lists的函数

这里写图片描述

ioeventfd_init

这里写图片描述

使用epoll机制,初始化一个epoll_fd, 创建一个线程用于epoll_wait
特别注意这个epoll_stop_fd ,这个其实是用于停止线程的,因为eventfd函数会创建一个fd用于简单的类似管道的通信。我们看处理epoll_wait的线程中有如下一段

这里写图片描述

也就是说如果有人往epoll_stop_fd中写东西,线程就退出了。

我们发现ioeventfd_init函数只创建来epoll机制的框架,并没有添加实质性的eventfd,我们搜一下哪里用了epoll_fd

这里写图片描述

我们发现只有2个模块使用了epoll_fd
一个是kvm-ipc.c中, 另外一个看样子还是在ioeventfd.c中
我们先看ioeventfd.c中的

ioeventfd__add_event

我们发现是一个辅助函数,用于添加eventfd, 我们看看他做了什么。

这里写图片描述

我们发现其创建了一个kvm_ioeventfd结构体,然后利用ioctrl往vm_fd中设置,现在还不知道这个是做什么的,后面我们分析内核中KVM模块源码的时候再去研究。

这里写图片描述

再往epoll_fd中添加fd。
关键我们看看哪些地方用到了ioeventfd__add_event

这里写图片描述

看来只有两个地方。而且全都和IO有关系。我们大概过一眼到底具体为什么注册ioevent。

这里写图片描述

在mmio中,对应的ioevent结构体被初始化成这样,再结合上边我们看到的线程中处理event_wait的方法,显然我们能够猜到,如果VIRTIO_MMIO事件发生,则会通过event_wait返回,并执行回调函数
virtio_mmio_ioevent_callback 过一眼

这里写图片描述

看来是交给具体设备自身来处理,我们不再深入分析,到后面分析设备模拟原理的时候再深入。

看一眼pio.c中的情况

这里写图片描述

和mmio一个模子。

kvm_cpu__init

我们再来看第二个用base_init宏申明的函数kvm_cpu_init

这里写图片描述

在确定来CPU个数之后,循环初始化,我们进入kvm_cpu_arch_init看看

这里写图片描述

这里写图片描述

先用ioctrl创建VCPU,然后通过ioctrl获得mmap_size,然后通过mmap得到一块内存区,这个内存区就是用来和KVM模块通信的。

最后kvm_cpu_set_lint

这里写图片描述

设置了LAPIC的模式

kvm_ipc__init

最后一个被base_init宏声明的函数

这里写图片描述

就是用来进行进程间通信的,因为当前进程用来跑guest了,所以需要另外一个进程来控制这个guest倒是要暂定还是要退出。

kvm_cmd_run_init总结

本博文主要是深入分析来kvm_cmd_run_init所做的工作,其中包括
1. 寻找用于启动的内核
2. 创建根文件系统
3. mmap分配内存
4. 通过ioctrl注册内存
5. 加载内核到guest物理内存中
6. 通过ioctrl 创建虚拟机
7. 初始化来ioevent
8. 通过ioctrl创建VCPU
9. 通过ioctrl设置LAPIC

其中所使用的核心KVM模块ioctrl按顺序有

ioctl(kvm->sys_fd, KVM_GET_API_VERSION, 0);
ioctl(kvm->sys_fd, KVM_CREATE_VM, KVM_VM_TYPE);
ioctl(kvm->sys_fd, KVM_CHECK_EXTENSION, extension);

ioctl(kvm->vm_fd, KVM_SET_TSS_ADDR, 0xfffbd000);
ioctl(kvm->vm_fd, KVM_CREATE_PIT2, &pit_config);
ioctl(kvm->vm_fd, KVM_CREATE_IRQCHIP);
ioctl(kvm->vm_fd, KVM_SET_USER_MEMORY_REGION, &mem);
ioctl(vcpu->kvm->vm_fd, KVM_CREATE_VCPU, cpu_id);
ioctl(vcpu->kvm->sys_fd, KVM_GET_VCPU_MMAP_SIZE, 0);

ioctl(kvm->sys_fd, KVM_CHECK_EXTENSION, KVM_CAP_COALESCED_MMIO);

ioctl(vcpu->vcpu_fd, KVM_GET_LAPIC, &lapic)

标签