首页 > 系统服务 > 详细

关于Linux虚拟化技术KVM的科普

时间:2017-03-03 23:58:17      阅读:1173      评论:0      收藏:0      [点我收藏+]

科普一(先用起来!)

是骡子是马是拉出来溜溜,通过《KVM虚拟化技术之使用Qemu-kvm创建和管理虚拟机》跑一遍,就会对KVM、QEMU-KVM有个大概的认识了。

qemu-kvm已经不单独存在,qemu加上-enable-kvm可以实现同样的功能。

关于不同CPU支持的硬件虚拟化技术,参考Processor_support

Intel: VT-x,在/proc/cpuinfo中对应vmx标识。
AMD:AMD-V,在/proc/cpuinfo中对应svm标识。
ARM:ARMv7-A的A15、A7、A17和ARMv8-A。

查看x86系列CPU支持硬件虚拟化情况:

egrep ‘(vmx|svm)‘ /proc/cpuinfo

Ubuntu下QEMU开发环境搭建

开始QEMU-KVM从QEMU fork一个分支开发qemu-kvm,后来都何如QEMU,并且fork的分支停止更新。官方WiKi

git clone git://git.qemu-project.org/qemu.git  获取qemu源文件
由于此git存在问题,github.com有个QEMU mirror,其他相关bios文件亦可以在里面找到:
git clone https://github.com/qemu/qemu.git

 

sudo apt-get install git libglib2.0-dev libfdt-dev libpixman-1-dev zlib1g-dev  安装编译依赖文件

编译安装QEMU:

mkdir -p bin/debug/native
cd bin/debug/native
../../../configure --enable-debug  配置编译环境
make -j4  开始编译

sudo make install  安装

创建磁盘文件:

qemu-img create -o preallocation=metadata -f qcow2 images/centos.qcow2 60G

在虚拟机上安装操作系统:

qemu-system-x86_64 -smp 2 -m 2048 -enable-kvm images/centos.qcow2 -cdrom images/CentOS-7-x86_64-DVD-1611.iso -vnc :1

使用2个CPU,2048M内存,使用KVM虚拟化技术安装CentOS 7,开始安装CentOS。

安装gvncviewer,通过VNC查看界面:

gvncviewer 127.0.0.1:1

输出如下:

Connected to server
Remote desktop size changed to 640x480
Connection initialized
Remote desktop size changed to 720x400
Remote desktop size changed to 1024x768

技术分享

 

启动虚拟机:

qemu-system-x86_64 -smp 2 -m 2048 -enable-kvm images/centos.qcow2 -vnc :1

技术分享

一个简单的启动性能测试:

背景:基于Host-Ubuntu Desktop 14.04启动Guest-Ubuntu Server 16.04。

4CPU 1G:

技术分享

2CPU 2G RAM:

技术分享

1CPU 1G RAM:

技术分享

粗看结果貌似RAM不是瓶颈,CPU影响相对比较大一点。这也和systemd的并行启动特定吻合。


科普二(KVM虚拟机代码揭秘)

代码分析文章《KVM虚拟机代码揭秘——QEMU代码结构分析》、《KVM虚拟机代码揭秘——中断虚拟化》、《KVM虚拟机代码揭秘——设备IO虚拟化》、《KVM虚拟机代码揭秘——QEMU的PCI总线与设备(上)》、《KVM虚拟机代码揭秘——QEMU的PCI总线与设备(下)》。先从大的方面分析代码结构,然后分中断、IO、PCI总线与设备详细介绍。

qemu-img

在根目录生成,参照Makefile可知有如下文件组成:

qemu-img$(EXESUF): qemu-img.o $(block-obj-y) $(crypto-obj-y) $(io-obj-y) $(qom-obj-y) $(COMMON_LDADDS)
qemu-nbd$(EXESUF): qemu-nbd.o $(block-obj-y) $(crypto-obj-y) $(io-obj-y) $(qom-obj-y) $(COMMON_LDADDS)
qemu-io$(EXESUF): qemu-io.o $(block-obj-y) $(crypto-obj-y) $(io-obj-y) $(qom-obj-y) $(COMMON_LDADDS)

qemu-system-x86_64

由于target比较多,编译也费时。可以指定便以特定的target:

./configure --target-list=x86_64-softmmu

qemu-system-x86_64的入口定义在vl.c的main中:

main
    ->main_loop
        ->main_loop_wait
            ->os_host_main_loop_wait

下面是启动QEMU,然后systemctl poweroff关闭QEMU的log:

File: vl.c main line=2976
File: /media/al/EXT4/kvm/qemu/hw/i386/pc_piix.c pc_machine_init_v2_9 line=460
初始化不同版本的pc machine。
File: /media/al/EXT4/kvm/qemu/hw/i386/pc_q35.c pc_machine_init_v2_4 line=375
File: /media/al/EXT4/kvm/qemu/target/i386/cpu.c x86_cpu_register_types line=3798
File: vl.c runstate_init line=690
File: /media/al/EXT4/kvm/qemu/target/i386/cpu.c x86_cpu_common_class_init line=3732
File: /media/al/EXT4/kvm/qemu/hw/i386/pc_piix.c pc_machine_v0_12_class_init line=915
初始化MachineClass,包括->init函数。
File: /media/al/EXT4/kvm/qemu/hw/i386/pc_piix.c pc_machine_v2_9_class_init line=460
File: /media/al/EXT4/kvm/qemu/hw/i386/pc_piix.c pc_machine_v0_13_class_init line=880
File: vl.c select_machine line=2733 name=pc-i440fx-2.9
File: vl.c set_memory_options line=2832
File: os-posix.c os_daemonize line=219
File: util/main-loop.c qemu_init_main_loop line=158
File: /media/al/EXT4/kvm/qemu/exec.c cpu_exec_init_all line=2931
File: vl.c main line=4246
File: /media/al/EXT4/kvm/qemu/translate-all.c page_size_init line=397
File: block.c bdrv_fill_options line=1234
File: block.c bdrv_fill_options line=1234
File: block.c bdrv_find_protocol line=531
File: /media/al/EXT4/kvm/qemu/numa.c parse_numa_opts line=310
File: vl.c main line=4496  从此到 line=4499都是machine_class->init的内容,此处machine_class->init指向pc_init_v2_9,进而调用pc_init1。
File: /media/al/EXT4/kvm/qemu/hw/i386/pc_piix.c pc_init1 line=127
File: qom/cpu.c cpu_class_by_name line=313
File: /media/al/EXT4/kvm/qemu/target/i386/cpu.c x86_cpu_class_by_name line=712
File: /media/al/EXT4/kvm/qemu/target/i386/cpu.c x86_cpu_initfn line=3567
File: /media/al/EXT4/kvm/qemu/target/i386/cpu.c x86_cpu_realizefn line=3204
File: /media/al/EXT4/kvm/qemu/cpus.c qemu_init_vcpu line=1545
File: /media/al/EXT4/kvm/qemu/cpus.c qemu_kvm_start_vcpu line=1510
File: /media/al/EXT4/kvm/qemu/cpus.c qemu_kvm_cpu_thread_fn line=987
File: /media/al/EXT4/kvm/qemu/kvm-all.c kvm_init_vcpu line=308
File: /media/al/EXT4/kvm/qemu/target/i386/kvm.c kvm_arch_init_vcpu line=719
File: /media/al/EXT4/kvm/qemu/target/i386/cpu.c x86_cpu_reset line=2811
File: /media/al/EXT4/kvm/qemu/target/i386/cpu.c x86_cpu_initfn line=3567
File: /media/al/EXT4/kvm/qemu/target/i386/cpu.c x86_cpu_realizefn line=3204
File: /media/al/EXT4/kvm/qemu/cpus.c qemu_init_vcpu line=1545
File: /media/al/EXT4/kvm/qemu/cpus.c qemu_kvm_start_vcpu line=1510
File: /media/al/EXT4/kvm/qemu/cpus.c qemu_kvm_cpu_thread_fn line=987
File: /media/al/EXT4/kvm/qemu/kvm-all.c kvm_init_vcpu line=308
File: /media/al/EXT4/kvm/qemu/target/i386/kvm.c kvm_arch_init_vcpu line=719
File: /media/al/EXT4/kvm/qemu/target/i386/cpu.c x86_cpu_reset line=2811
File: /media/al/EXT4/kvm/qemu/hw/i386/pc_piix.c pc_init_v2_9 line=460
File: vl.c main line=4499
File: ui/console.c init_displaystate line=1790
File: os-posix.c os_setup_signal_handling line=79
File: replay/replay.c replay_start line=315
File: /media/al/EXT4/kvm/qemu/target/i386/cpu.c x86_cpu_reset line=2811
File: /media/al/EXT4/kvm/qemu/target/i386/cpu.c x86_cpu_reset line=2811
File: os-posix.c os_setup_post line=272
File: vl.c main_loop enter do-while  进入main_loop循环体
kvm_cpu_exec()
kvm_cpu_exec()
injected SMI
io window exit
kvm_cpu_exec()
injected SMI

injected SMI
io window exit
kvm_cpu_exec()
kvm_arch_handle_exit
kvm_cpu_exec()
kvm_arch_handle_exit
kvm_cpu_exec()
kvm_arch_handle_exit
kvm_cpu_exec()
kvm_arch_handle_exit
kvm_cpu_exec()
File: vl.c main_loop exit do-while  退出main_loop循环体
File: iothread.c iothread_stop_all line=307
io window exit
io window exit
File: chardev/char.c qemu_chr_cleanup line=1328

下面是Ctrl+C非正常退出:

injected SMI
injected SMI
injected SMI
io window exit
kvm_cpu_exec()
^CFile: os-posix.c termsig_handler line=71  Ctrl+C强制退出,
qemu-system-x86_64: terminating on signal 2  signal 2对应SIGINT,来自键盘Ctrl+C。在main_loop_should_exit中会对其进行处理。
File: vl.c main_loop exit do-while
File: iothread.c iothread_stop_all line=307
io window exit
io window exit

 


科普三(From OenHan)

http://oenhan.com/archives,包括《KVM源代码分析1:基本工作原理》、《KVM源代码分析2:虚拟机的创建与运行》、《KVM源代码分析3:CPU虚拟化》、《KVM源代码分析4:内存虚拟化》、《KVM源代码分析5:IO虚拟化之PIO》,可以有个基本认识,以及CPU、内存、IO虚拟化(里面的一些图居然没有了,可以在转载地址找到)。

这一系列文章按照基础原理、使用以及CPU/Memory/IO虚拟化分析来进行的。



KVM源代码分析1:基本工作原理

Linux Kernel在市场上的需求:虚拟化、存储、网络和驱动

作者给出的进行虚拟化开发准备工作:1.操作系统基础知识;2.《深入Linux内核架构》、《深入理解Linux内核》;3.Intel的《系统虚拟化-原理与实现》。

关于Guest OS、QEMU、KVM、Host OS不同角色及其职责:

技术分享

Guest OS是不经修改可以直接运行的一套系统,保证具体运行场景中的程序正常执行。而KVM的代码则部署在Host上,Kernel对应的是KVM Driver,KVM Driver负责模拟虚拟机的CPU运行、内存管理、设备管理等等;Userspace对应的是QEMU,QEMU则模拟虚拟机的IO设备接口以及用户态控制接口,QEMU通过KVM等fd进行ioctl控制KVM驱动的运行。

Guest有自己的用户模式和内核模式,Guest是在Host中作为一个用户态进程存在的,这个进程就是QEMU,QEMU本省就是一个虚拟化程序,它被KVM改造后,作为KVM的前端存在,用来进行创建进程或者IO交互等;而KCM Driver则是Linux内核模式,它提供KVM fs给QEMU调用,用来进行CPU虚拟化、内存虚拟化等。QEMU通过KVM提供的fd接口,通过ioctl系统调用创建和运行虚拟机。KVM Driver使得整个Linux成为一个虚拟机监控器,负责接收QEMU模拟效率很低的命令。

 

KVM的执行流程:

技术分享

上图是一个执行过程图,首先启动一个虚拟化管理软件qemu,开始启动一个虚拟机,通过ioctl等系统调用向内核中申请指定的资源,搭建好虚拟环境,启动虚拟机内的OS,执行 VMLAUCH 指令,即进入了guest代码执行过程。如果 Guest OS 发生外部中断或者影子页表缺页之类的事件,暂停 Guest OS 的执行,退出QEMU即guest VM-exit,进行一些必要的处理,然后重新进入客户模式,执行guest代码;这个时候如果是io请求,则提交给用户态下的qemu处理,qemu处理后再次通过IOCTL反馈给KVM驱动。

 

CPU虚拟化:
Guest和Host之间的切换

X86虚拟化技术Intel VT-x,提供了两种工作环境,VMCS实现两种环境之间的切换。VM Entry是虚拟机进入guest模式,VM Exit使虚拟机退出guest模式。

VMM调度guest执行时,qemu通过ioctl系统调用进入内核模式,在KVM Driver中获得当前物理CPU的引用。之后将guest状态从VMCS中读出,并装入物理CPU中。执行 VMLAUCH 指令使得物理处理器进入非根操作环境,运行guest OS代码。

当 guest OS 执行一些特权指令或者外部事件时, 比如I/O访问,对控制寄存器的操作,MSR的读写等, 都会导致物理CPU发生 VMExit, 停止运行 Guest OS,将 Guest OS保存到VMCS中, Host 状态装入物理处理器中, 处理器进入根操作环境,KVM取得控制权,通过读取 VMCS 中 VM_EXIT_REASON 字段得到引起 VM Exit 的原因。 从而调用kvm_exit_handler 处理函数。 如果由于 I/O 获得信号到达,则退出到userspace模式的 Qemu 处理。处理完毕后,重新进入guest模式运行虚拟 CPU。

Memory虚拟化:

OS对于物理内存主要有两点认识:1.物理地址从0开始;2.内存地址是连续的。VMM接管了所有内存,但guest OS的对内存的使用就存在这两点冲突了,除此之外,一个guest对内存的操作很有可能影响到另外一个guest乃至host的运行。VMM的内存虚拟化就要解决这些问题。

在OS代码中,应用也是占用所有的逻辑地址,同时不影响其他应用的关键点在于有线性地址这个中间层;解决方法则是添加了一个中间层:guest物理地址空间;guest看到是从0开始的guest物理地址空间(类比从0开始的线性地址),而且是连续的,虽然有些地址没有映射;同时guest物理地址映射到不同的host逻辑地址,如此保证了VM之间的安全性要求。

这样MEM虚拟化就是GVA->GPA->HPA的寻址过程,传统软件方法有影子页表,硬件虚拟化提供了EPT支持。
可能GVA->GPA->HVA->HPA更全面一点。
GVA: Guest Virtual Address
GPA: Guest Physical Address
HVA: Host Virtual Address
HPA: Host Physical Address

KVM源代码分析2:虚拟机的创建与运行

在进行本章阅读之前首先了解一下KVM、QEMU-KVM、libvirt之间的关系。

参看文档:《KVM-Qemu-Libvirt三者之间的关系》和《KVM,QEMU,libvirt入门学习笔记》。

KVM是linux内核的模块,它需要CPU的支持,采用硬件辅助虚拟化技术Intel-VT,AMD-V,内存的相关如Intel的EPT和AMD的RVI技术,Guest OS的CPU指令不用再经过Qemu转译,直接运行,大大提高了速度,KVM通过/dev/kvm暴露接口,用户态程序可以通过ioctl函数来访问这个接口。KVM内核模块本身只能提供CPU和内存的虚拟化,所以它必须结合QEMU才能构成一个完成的虚拟化技术,这就是下面要说的qemu-kvm。

QEMU-KVM是基于Qemu将KVM整合进来,通过ioctl调用/dev/kvm接口,将有关CPU指令的部分交由内核模块来做。kvm负责cpu虚拟化+内存虚拟化,实现了cpu和内存的虚拟化,但kvm不能模拟其他设备。qemu模拟IO设备(网卡,磁盘等),kvm加上qemu之后就能实现真正意义上服务器虚拟化。因为用到了上面两个东西,所以称之为qemu-kvm。Qemu模拟其他的硬件,如Network, Disk,同样会影响这些设备的性能,于是又产生了pass through半虚拟化设备virtio_blk, virtio_net,提高设备性能。

技术分享

libvirt是目前使用最为广泛的对KVM虚拟机进行管理的工具和API。Libvirtd是一个daemon进程,可以被本地的virsh调用,也可以被远程的virsh调用,Libvirtd调用qemu-kvm操作虚拟机。

技术分享

从上面分析可以知道:KVM是内核的一个模块,提供CPU和Memory的虚拟化;QEMU-KVM是基于QEMU针对KVM修改后的工具,用于提供完整的KVM虚拟化环境;libvirt是用来管理虚拟化的通用库,支持但不限于KVM。

module_call_init

从vl.c的main开始,atexit注册了qemu退出处理函数。module_call_init则开始初始化qemu的各个模块,有:

typedef enum {
    MODULE_INIT_BLOCK,
    MODULE_INIT_OPTS,
    MODULE_INIT_QAPI,
    MODULE_INIT_QOM,
    MODULE_INIT_TRACE,
    MODULE_INIT_MAX
} module_init_type;

最开始初始化MODULE_INIT_TRACE,然后依次执行。module_call_init实际上是执行不同type函数链表ModuleTypeList上的ModuleEntry。

void module_call_init(module_init_type type)
{
    ModuleTypeList *l;
    ModuleEntry *e;

    l = find_type(type);

    QTAILQ_FOREACH(e, l, node) {
        e->init();
    }
}

实际上就是执行e->init,那么e->init是什么时候被赋值的呢?是通过register_module_init注册到对应ModuleTypeList的。

调用关系如:block_init/opts_init/qaqi_init/type_init/trace_init->module_init->register_module_init。

下面可以看到初始化函数和module_init_type的一一对应关系。

#define block_init(function) module_init(function, MODULE_INIT_BLOCK)
#define opts_init(function) module_init(function, MODULE_INIT_OPTS)
#define qapi_init(function) module_init(function, MODULE_INIT_QAPI)
#define type_init(function) module_init(function, MODULE_INIT_QOM)
#define trace_init(function) module_init(function, MODULE_INIT_TRACE)

#define module_init(function, type)                                         \
static void __attribute__((constructor)) do_qemu_init_ ## function(void)    \
{                                                                           \
    register_module_init(function, type);                                   \
}

小知识:

修饰符__attribute__((constructor))导致module_init会在main()之前就被执行。所以所有的block_init/opts_init/qaqi_init/type_init/trace_init在main()之前已经被执行。同样__attribute__((destructor))会在main()结束之后调用。

由于module_register_init已经先于main()执行,所有module_call_init可以遍历各种类型的ModuleTypeList。

 

pc_init1

pc_init1是一个核心函数,那么他是怎么被调用的呢?

#define DEFINE_I440FX_MACHINE(suffix, name, compatfn, optionfn) \
    static void pc_init_##suffix(MachineState *machine) \
    { \
        void (*compat)(MachineState *m) = (compatfn); \
        if (compat) { \
            compat(machine); \
        } \
        pc_init1(machine, TYPE_I440FX_PCI_HOST_BRIDGE, \
                 TYPE_I440FX_PCI_DEVICE); \  所有此类型的Machine都会调用pc_init1。
        fprintf(stderr, "File: %s %s line=%d\n", __FILE__, __func__, __LINE__); \
    } \
    DEFINE_PC_MACHINE(suffix, name, pc_init_##suffix, optionfn)

那么pc_init_##suffix又是怎么被调用的呢?从下面代码可以看出type_init会将pc_machine_init_##suffix注册。最终mc->init会指向pc_machine_##suffix##_class_init,进而调用pc_init1。

#define DEFINE_PC_MACHINE(suffix, namestr, initfn, optsfn) \
    static void pc_machine_##suffix##_class_init(ObjectClass *oc, void *data) \
    { \
        MachineClass *mc = MACHINE_CLASS(oc); \
        optsfn(mc); \
    fprintf(stderr, "File: %s %s line=%d\n", __FILE__, __func__, __LINE__); \
        mc->init = initfn; \
    } \
    static const TypeInfo pc_machine_type_##suffix = { \
        .name       = namestr TYPE_MACHINE_SUFFIX, \
        .parent     = TYPE_PC_MACHINE, \
        .class_init = pc_machine_##suffix##_class_init, \
    }; \
    static void pc_machine_init_##suffix(void) \
    { \
        type_register(&pc_machine_type_##suffix); \
    fprintf(stderr, "File: %s %s line=%d\n", __FILE__, __func__, __LINE__); \
    } \
    type_init(pc_machine_init_##suffix)

由于type_init的特殊属性(在main()之前已经被执行),所以在main中执行machine_class->init的时候函数已经就绪。

pc_init1分析如下,主要进行CPU、Memory、VGA、NIC、PCI等的初始化

static void pc_init1(MachineState *machine,
                     const char *host_type, const char *pci_type)
{

    pc_cpus_init(pcms);  初始化CPU

    if (kvm_enabled() && pcmc->kvmclock_enabled) {
        kvmclock_create();
    }

    if (pcmc->pci_enabled) {
        pci_memory = g_new(MemoryRegion, 1);
        memory_region_init(pci_memory, NULL, "pci", UINT64_MAX);
        rom_memory = pci_memory;
    } else {
        pci_memory = NULL;
        rom_memory = system_memory;
    }

    pc_guest_info_init(pcms);

    if (pcmc->smbios_defaults) {
        MachineClass *mc = MACHINE_GET_CLASS(machine);
        /* These values are guest ABI, do not change */
        smbios_set_defaults("QEMU", "Standard PC (i440FX + PIIX, 1996)",
                            mc->name, pcmc->smbios_legacy_mode,
                            pcmc->smbios_uuid_encoded,
                            SMBIOS_ENTRY_POINT_21);
    }

    /* allocate ram and load rom/bios */
    if (!xen_enabled()) {
        pc_memory_init(pcms, system_memory,
                       rom_memory, &ram_memory);
    } else if (machine->kernel_filename != NULL) {
        /* For xen HVM direct kernel boot, load linux here */
        xen_load_linux(pcms);
    }

    gsi_state = g_malloc0(sizeof(*gsi_state));
    if (kvm_ioapic_in_kernel()) {
        kvm_pc_setup_irq_routing(pcmc->pci_enabled);
        pcms->gsi = qemu_allocate_irqs(kvm_pc_gsi_handler, gsi_state,
                                       GSI_NUM_PINS);
    } else {
        pcms->gsi = qemu_allocate_irqs(gsi_handler, gsi_state, GSI_NUM_PINS);
    }

    if (pcmc->pci_enabled) {
        pci_bus = i440fx_init(host_type,
                              pci_type,
                              &i440fx_state, &piix3_devfn, &isa_bus, pcms->gsi,
                              system_memory, system_io, machine->ram_size,
                              pcms->below_4g_mem_size,
                              pcms->above_4g_mem_size,
                              pci_memory, ram_memory);
        pcms->bus = pci_bus;
    } else {
        pci_bus = NULL;
        i440fx_state = NULL;
        isa_bus = isa_bus_new(NULL, get_system_memory(), system_io,
                              &error_abort);
        no_hpet = 1;
    }
    isa_bus_irqs(isa_bus, pcms->gsi);

    pc_register_ferr_irq(pcms->gsi[13]);

    pc_vga_init(isa_bus, pcmc->pci_enabled ? pci_bus : NULL);

    assert(pcms->vmport != ON_OFF_AUTO__MAX);
    if (pcms->vmport == ON_OFF_AUTO_AUTO) {
        pcms->vmport = xen_enabled() ? ON_OFF_AUTO_OFF : ON_OFF_AUTO_ON;
    }

    /* init basic PC hardware */
    pc_basic_device_init(isa_bus, pcms->gsi, &rtc_state, true,
                         (pcms->vmport != ON_OFF_AUTO_ON), pcms->pit, 0x4);

    pc_nic_init(isa_bus, pci_bus);

    ide_drive_get(hd, ARRAY_SIZE(hd));

    pc_cmos_init(pcms, idebus[0], idebus[1], rtc_state);

    if (pcmc->pci_enabled && machine_usb(machine)) {
        pci_create_simple(pci_bus, piix3_devfn + 2, "piix3-usb-uhci");
    }

    if (pcmc->pci_enabled) {
        pc_pci_device_init(pci_bus);
    }

    if (pcms->acpi_nvdimm_state.is_enabled) {
        nvdimm_init_acpi_state(&pcms->acpi_nvdimm_state, system_io,
                               pcms->fw_cfg, OBJECT(pcms));
    }
}

pc_cpus_init

main
    ->machine_class->init
        ->pc_init1
            ->pc_cpus_init(i386/pc.c)
                ->cpu_class_by_name
                ->object_class_get_name
                ->pc_new_cpu
                    ->object_new
                        ->object_new_with_type
                            ->object_initialize_with_type
                                ->object_init_with_type
                                    ->ti->instance_init(x86_cpu_initfn)
                                    ->x86_cpu_realizefn
                                        ->qemu_init_vcpu
                                            ->qemu_kvm_start_vcpu
                                                ->qemu_kvm_cpu_thread_fn
                                                    ->kvm_init_vcpu
                                                        ->kvm_arch_init_vcpu

pc_cpus_init中循环对smp_cpus个数执行pc_new_cpu。pc_new_cpu进入到x86_cpu_initfn

qemu_init_vcpu用于创建CPU,根据条件创建KVM、HAX、TCG。DUMMY四种类型。

这里重点看看KVM类型的VCPU创建,qemu_kvm_start_vcpu:

static void qemu_kvm_start_vcpu(CPUState *cpu)
{
    char thread_name[VCPU_THREAD_NAME_SIZE];

    cpu->thread = g_malloc0(sizeof(QemuThread));
    cpu->halt_cond = g_malloc0(sizeof(QemuCond));
    qemu_cond_init(cpu->halt_cond);
    snprintf(thread_name, VCPU_THREAD_NAME_SIZE, "CPU %d/KVM",
             cpu->cpu_index);
    qemu_thread_create(cpu->thread, thread_name, qemu_kvm_cpu_thread_fn,
                       cpu, QEMU_THREAD_JOINABLE);  创建CPU的线程
    while (!cpu->created) {  如果cpu->reated为否,既没有创建成功,则一直while(1)阻塞。说明多核vcpu创建是顺序的。
        qemu_cond_wait(&qemu_cpu_cond, &qemu_global_mutex);
    }
}

qemu_kvm_cpu_thread_fn作为创建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);  初始化vcpu
    if (r < 0) {
        fprintf(stderr, "kvm_init_vcpu failed: %s\n", strerror(-r));
        exit(1);
    }

    qemu_kvm_init_cpu_signals(cpu);

    /* signal CPU creation */
    cpu->created = true;  和之前的while(!cpu->created)
    qemu_cond_signal(&qemu_cpu_cond);

    do {
        if (cpu_can_run(cpu)) {  进入CPU执行状态
            r = kvm_cpu_exec(cpu);
            if (r == EXCP_DEBUG) {
                cpu_handle_guest_debug(cpu);
            }
        }
        qemu_kvm_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();
    return NULL;
}

kvm_init_vcpu通过kvm_vm_ioctl,KVM_CREATE_VCPU创建VCPU,用KVM_GET_VCPU_MMAP_SIZE获取cpu->kvm_run对应的内存映射。kvm_arch_init_vcpu则填充对应的kvm_arch内容。

qemu_kvm_init_cpu_signals则是将中断组合掩码传递给kvm_set_signal_mask,最终给内核KVM_SET_SIGNAL_MASK。kvm_cpu_exec此时还在阻塞过程中,先挂起来,看内存的初始化。

 

在qemu_init_vcpu执行完成后,下面就是cpu_reset。

pc_memory_init

 

kvm_init

type_init(kvm_type_init)->kvm_accel_type->kvm_accel_class_init->kvm_init依次完成注册,然后在configure_accelerator的时候调用这些函数。

main->configure_accelerator->accel_init_machine->kvm_init是到kvm_init的调用路径。

技术分享

static int kvm_init(MachineState *ms)
{

    s = KVM_STATE(ms->accelerator);  填充KVMState结构体。

    s->vmfd = -1;
    s->fd = qemu_open("/dev/kvm", O_RDWR);  打开/dev/kvm,获取句柄。
    if (s->fd == -1) {
        fprintf(stderr, "Could not access KVM kernel module: %m\n");
        ret = -errno;
        goto err;
    }

    ret = kvm_ioctl(s, KVM_GET_API_VERSION, 0);  获取KVM版本信息

    s->nr_slots = kvm_check_extension(s, KVM_CAP_NR_MEMSLOTS);  获取最大内存插槽数

    /* If unspecified, use the default value */
    if (!s->nr_slots) {
        s->nr_slots = 32;
    }

    /* check the vcpu limits */
    soft_vcpus_limit = kvm_recommended_vcpus(s);
    hard_vcpus_limit = kvm_max_vcpus(s);

    do {
        ret = kvm_ioctl(s, KVM_CREATE_VM, type);  创建KVM虚拟机,获取虚拟机句柄。
    } while (ret == -EINTR);

    if (ret < 0) {
        fprintf(stderr, "ioctl(KVM_CREATE_VM) failed: %d %s\n", -ret,
                strerror(-ret));


        goto err;
    }

    s->vmfd = ret;
    missing_cap = kvm_check_extension_list(s, kvm_required_capabilites); 一系列通过ioctl读取s->fd参数。

    ret = kvm_arch_init(ms, s);  初始化KVMState
    if (ret < 0) {
        goto err;
    }

    if (machine_kernel_irqchip_allowed(ms)) { 
    kvm_irqchip_create(ms, s);  创建kcm中断管理内容,通过kvm_vm_ioctl的KVM_CAP_IRQCHIP实现。
    }

    kvm_state = s;

    if (kvm_eventfds_allowed) {
        s->memory_listener.listener.eventfd_add = kvm_mem_ioeventfd_add;
        s->memory_listener.listener.eventfd_del = kvm_mem_ioeventfd_del;
    }
    s->memory_listener.listener.coalesced_mmio_add = kvm_coalesce_mmio_region;
    s->memory_listener.listener.coalesced_mmio_del = kvm_uncoalesce_mmio_region;

    kvm_memory_listener_register(s, &s->memory_listener,  注册内存管理函数
                                 &address_space_memory, 0);
    memory_listener_register(&kvm_io_listener,
                             &address_space_io);

    s->many_ioeventfds = kvm_check_many_ioeventfds();

    cpu_interrupt_handler = kvm_handle_interrupt;

    return 0;
}

到kvm_init_vcpu用于创建CPU,并执行,调用路径:

x86_cpu_realizefn
    ->qemu_init_vcpu
        ->qemu_kvm_start_vcpu
            ->qemu_kvm_cpu_thread_fn
                ->kvm_init_vcpu
                    ->kvm_get_vcpu
                        ->kvm_vm_ioctl(KVM_CREATE_VCPU)
                ->kvm_cpu_exec
                    ->kvm_vcpu_ioctl(KVM_RUN)

代码如下:

int kvm_init_vcpu(CPUState *cpu)
{
    KVMState *s = kvm_state;
    long mmap_size;
    int ret;

    ret = kvm_get_vcpu(s, kvm_arch_vcpu_id(cpu));  创建VCPU句柄
    if (ret < 0) {
        DPRINTF("kvm_create_vcpu failed\n");
        goto err;
    }

    cpu->kvm_fd = ret;
    cpu->kvm_state = s;
    cpu->kvm_vcpu_dirty = true;

    mmap_size = kvm_ioctl(s, KVM_GET_VCPU_MMAP_SIZE, 0);  获取VCPU mmap大小,并且创建mmap映射给cpu->kvm_run。

    cpu->kvm_run = mmap(NULL, mmap_size, PROT_READ | PROT_WRITE, MAP_SHARED,
                        cpu->kvm_fd, 0);

    ret = kvm_arch_init_vcpu(cpu);  架构相关的CPUState结构体初始化
err:
    return ret;
}

kvm_cpu_exec如下:

int kvm_cpu_exec(CPUState *cpu)
{
    struct kvm_run *run = cpu->kvm_run;
    int ret, run_ret;

    DPRINTF("kvm_cpu_exec()\n");

    if (kvm_arch_process_async_events(cpu)) {
        cpu->exit_request = 0;
        return EXCP_HLT;
    }

    qemu_mutex_unlock_iothread();

    do {
        MemTxAttrs attrs;

        if (cpu->kvm_vcpu_dirty) {
            kvm_arch_put_registers(cpu, KVM_PUT_RUNTIME_STATE);
            cpu->kvm_vcpu_dirty = false;
        }

        kvm_arch_pre_run(cpu, run);  RUN前准备
        if (cpu->exit_request) {
            DPRINTF("interrupt exit requested\n");
            /*
             * KVM requires us to reenter the kernel after IO exits to complete
             * instruction emulation. This self-signal will ensure that we
             * leave ASAP again.
             */
            qemu_cpu_kick_self();
        }

        run_ret = kvm_vcpu_ioctl(cpu, KVM_RUN, 0);  RUN

        attrs = kvm_arch_post_run(cpu, run);  RUN后收尾工作

        trace_kvm_run_exit(cpu->cpu_index, run->exit_reason);
        switch (run->exit_reason) {
…  各种退出原因处理 

        }
    } while (ret == 0);

    qemu_mutex_lock_iothread();

    if (ret < 0) {
        cpu_dump_state(cpu, stderr, fprintf, CPU_DUMP_CODE);
        vm_stop(RUN_STATE_INTERNAL_ERROR);
    }

    cpu->exit_request = 0;
    return ret;
}

 

KVM内存初始化

注册Memory Listener过程:

kvm_init
    ->kvm_memory_listener_register
       ->.region_add = kvm_region_add
          .region_del = kvm_region_del

增加Memory Region:

kvm_region_add/kvm_region_del
    ->kvm_set_phys_mem
        ->kvm_set_user_memory_region
            ->kvm_vm_ioctl(KVM_SET_USER_MEMORY_REGION)

调用路径:

memory_listener_register
    ->listener_add_address_space
        ->listener->log_start
        ->listener->region_add
        ->listener->commit

 

select_machine

QEMU的及其类型是通过select_machine获得的,也可以通过-machine参数传入。

查看支持的machine列表,可以通过qemu-system-x86_64 -machine help得到。

如果没有指定,则使用默认的machine_class。

static MachineClass *select_machine(void)
{
    MachineClass *machine_class = find_default_machine();  初始化为默认的machine_class
    const char *optarg;
    QemuOpts *opts;
    Location loc;

    loc_push_none(&loc);

    opts = qemu_get_machine_opts();
    qemu_opts_loc_restore(opts);

    optarg = qemu_opt_get(opts, "type");
    if (optarg) {
        machine_class = machine_parse(optarg);  通过参数获取的machine_class
    }

    loc_pop(&loc);
    return machine_class;
}

 

 

 

KVM源代码分析3:CPU虚拟化

KVM源代码分析4:内存虚拟化

KVM源代码分析5:IO虚拟化之PIO


另一组关于KVM的分析文档,虚拟化相关概念KVM基本原理和架构一-概念和术语KVM基本原理和架构二-基本原理KVM基本原理及架构三-CPU虚拟化KVM基本原理及架构四-内存虚拟化KVM基本原理及架构六-KVM APIKVM基本原理及架构七-KVM内核模块中重要的数据结构

 

KVM架构与原理详解,一片简洁明了介绍KVM的文章,有框架图、工作原理描述等。

 

对比Xen和KVM:Linux虚拟化技术选择,关于Xen和KVM的对比,可以看出未来的趋势还是KVM。

 

另一位大神写到KVM文章,KVM 介绍(1):简介及安装KVM 介绍(2):CPU 和内存虚拟化KVM 介绍(3):I/O 全虚拟化和准虚拟化 [KVM I/O QEMU Full-Virtualizaiton Para-virtualization]KVM 介绍(4):I/O 设备直接分配和 SR-IOV [KVM PCI/PCIe Pass-Through SR-IOV]KVM 介绍(5):libvirt 介绍 [ Libvrit for KVM/QEMU ]KVM 介绍(6):Nova 通过 libvirt 管理 QEMU/KVM 虚机 [Nova Libvirt QEMU/KVM Domain]KVM 介绍(7):使用 libvirt 做 QEMU/KVM 快照和 Nova 实例的快照 (Nova Instances Snapshot Libvirt)KVM 介绍(8):使用 libvirt 迁移 QEMU/KVM 虚机和 Nova 虚机 [Nova Libvirt QEMU/KVM Live Migration]

关于Linux虚拟化技术KVM的科普

原文:http://www.cnblogs.com/arnoldlu/p/6421317.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!