虚拟机创建流程-qemu篇(下)

达芬奇密码2018-07-12 09:16
  1. 设置硬件版本
if (machine_class->hw_version) {
        qemu_set_version(machine_class->hw_version);
    }
  1. cpudef_init初始化支持的cpu feature,可以通过如下命令查询当前qemu支持的cpu feature
/usr/bin/qemu-system-x86_64 -cpu help

    if (cpu_model && is_help_option(cpu_model)) {
        list_cpus(stdout, &fprintf, cpu_model);
        exit(0);
    }

data_dir_idx 遗留 怀疑是bios文件路径

  1. 解析-smp参数记录到全局变量中
smp_parse(qemu_opts_find(qemu_find_opts("smp-opts"), NULL));
machine_class->max_cpus = machine_class->max_cpus ?: 1; /* Default to UP */#未配置cpu情况下 默认配置一个
#校验参数合法性
if (smp_cpus > machine_class->max_cpus) {
    fprintf(stderr, "Number of SMP cpus requested (%d), exceeds max cpus "
            "supported by machine `%s' (%d)\n", smp_cpus,
            machine_class->name, machine_class->max_cpus);
    exit(1);
}
  1. 根据machine类型判断是否配置默认的串口,并口等设备。如果需要则创建默认的设备配置
  2. 初始化所有的char dev(pty--serial/socket--qga)
if (qemu_opts_foreach(qemu_find_opts("chardev"), chardev_init_func, NULL, 1) != 0)
        exit(1);
  1. 打印device help日志
if (qemu_opts_foreach(qemu_find_opts("device"), device_help_func, NULL, 0)
        != 0) {
        exit(0);
    }
  1. 从qemu command中获取设置的machine相关参数 并赋值给current_machine
machine_opts = qemu_get_machine_opts();
    if (qemu_opt_foreach(machine_opts, object_set_property, current_machine,
                         1) < 0) {
        object_unref(OBJECT(current_machine));
        exit(1);
    }
  1. 初始化虚拟化加速器configure_accelerator。这里的作用其实就是配置一些qemu与hypervisor层的交互接口。qemu通过一些句柄以ioctl的方式与kmod交互,完成虚拟化相关的操作。在qemu中维护的句柄包括:
  • vmfd 虚拟机相关操作句柄,通过该句柄读写的都是与虚拟机相关的信息。
  • devkvmfd qemu与kmod交互的句柄,负责读取kmod中的公共信息。
  • vcpufd 虚拟CPU句柄,每个vcpu会分配一个句柄用于与kmod交互。

在qemu中维护了一个结构体数组,用于记录各种虚拟化方案下的加速器初始化入口:

accel_list[] = {
    { "tcg", "tcg", tcg_available, tcg_init, &tcg_allowed },
    { "xen", "Xen", xen_available, xen_init, &xen_allowed },
    { "kvm", "KVM", kvm_available, kvm_init, &kvm_allowed },
    { "qtest", "QTest", qtest_available, qtest_init, &qtest_allowed },
};
  1. 从上面的映射关系中可以看到,我们当前的配置下使用kvm_init作为初始化入口。
  • 为虚拟机初始化一个KVMState *s,用于保存vmfd,devkvmfd,中断路由等与kvm交互的信息
  • 版本协商 devkvmfd
ret = kvm_ioctl(s, KVM_GET_API_VERSION, 0);
  • 支持的最大内存插槽数量 devkvmfd
s->nr_slots = kvm_check_extension(s, KVM_CAP_NR_MEMSLOTS);
ret = kvm_ioctl(s, KVM_CHECK_EXTENSION, extension);
  • 获取kvm建议的每个虚拟机支持的最大vcpu数量(soft) devkvmfd
int ret = kvm_check_extension(s, KVM_CAP_NR_VCPUS);
  • 获取每个虚拟机支持的最大vcpu数量(hard) devkvmfd
int ret = kvm_check_extension(s, KVM_CAP_MAX_VCPUS);
  • 校验传入的vcpu参数smp/maxvcpus
  • 没用到kvm_type,貌似在powerpc里面会用到这个参数
  • 在kmod中初始化一个与虚拟机一一对应的kvm结构体,用于保存qemu与kvm交互的状态。返回qemu的vmfd
ret = kvm_ioctl(s, KVM_CREATE_VM, type);
linux kernel
static long kvm_dev_ioctl(struct file *filp, unsigned int ioctl, unsigned long arg)
static int kvm_dev_ioctl_create_vm(unsigned long type)
kvm = kvm_create_vm(type);
  • 检查qemu capability在kvm中是否支持
missing_cap = kvm_check_extension_list(s, kvm_required_capabilites);
  • kvm_arch_init中初始化硬件架构相关的一些特性,比如e820表,中断路由表等

    检查kvm是否支持MSR(model specific register,用于标识cpu的工作环境和工作状态),通常为一组寄存器,分别表示不同的标志位。其中一些要求必须存在,否则无法正常启动。

    ret = kvm_ioctl(s, KVM_GET_MSR_INDEX_LIST, &msr_list);

    初始化e820 entry,以及e820 table。(e820表用于维护机器的物理内存布局)

    创建中断管理单元kvm_vm_ioctl(s, KVM_CREATE_IRQCHIP);

    通过vmfd在kmod中创建一个虚拟pic

    初始化中断路由表(irqrouting 中断路由,与中断亲和性等相关。kvm通过该表可以知道将某一个中断路由到哪一个具体的vcpu上处理)

  1. 设置无硬盘启动相关参数
machine_opts = qemu_get_machine_opts();
    kernel_filename = qemu_opt_get(machine_opts, "kernel");
    initrd_filename = qemu_opt_get(machine_opts, "initrd");
    kernel_cmdline = qemu_opt_get(machine_opts, "append");
    bios_name = qemu_opt_get(machine_opts, "firmware");
  1. 设置标准输出缓冲区
os_set_line_buffering
  1. 初始化vcpu相关的锁,信号量
qemu_init_cpu_loop
  1. 初始化网络设备。完成后端tap设备的初始化,与宿主机kernel交互,拉起vhost内核线程,完成vhost的初始化。正常情况下,虚拟机内部网卡收发数据会通过vring和内存共享。首先走一遍虚拟机内核的网络栈将数据包放入共享内存,通过vring通知后端网络设备拷贝共享内存,因为后端网络设备是在用户态的,因此又要重新走一次宿主机的内核网络协议栈。这种流程会导致网络IO性能较差,因此引入了vhost的概念。vhost是一个内核中的线程,会映射qemu中的共享内存页。当虚拟机内部发出网络包的时候,从后端共享内存直接映射到了host内核,跳过qemu处理环节,节省了处理时间,提升了性能。
int net_init_clients(void)
net_init_netdev
net_client_init1
net_init_tap
net_init_tap_one
    后端tap设备初始化
    (vhost即虚拟机网卡IO数据通过一个内核线程在内核中直接处理而不需要经过qemu)
    vhost设备初始化--与内核交互,拉起vhost内核线程。

On 32-bit hosts, QEMU is limited by virtual address space

  1. 磁盘设备初始化,与磁盘热插流程类似。把设备fd加入main_loop,注册read和write的回调函数
if (qemu_opts_foreach(qemu_find_opts("drive"), drive_init_func,
                          &machine_class->block_default_type, 1) != 0) {
        exit(1);
    }
-drive file=rbd:switch01_sas_vms/493c6d20-3329-480f-ad6e-391d9e997f52_disk.config:auth_supported=none:mon_host=10.180.0.32\:6789\;10.180.0.49\:6789\;10.180.0.161\:6789,format=raw,if=none,id=drive-virtio-disk25,readonly=on,cache=none 
-device virtio-blk-pci,scsi=off,bus=pci.0,addr=0x7,drive=drive-virtio-disk25,id=virtio-disk25
  1. 创建qemu monitor,用于qemu与外部的通信,使用qmp协议。
if (qemu_opts_foreach(qemu_find_opts("mon"), mon_init_func, NULL, 1) != 0) {
        exit(1);
    }

25.初始化主板(VCPUbios北桥内存南桥,外围设备,中断等初始化)

current_machine->ram_size = ram_size;
    current_machine->maxram_size = maxram_size;
    current_machine->ram_slots = ram_slots;
    current_machine->boot_order = boot_order;
    current_machine->cpu_model = cpu_model;

    machine_class->init(current_machine);
    pc_init1
        pc_cpus_init 创建vcpu线程
        pc_memory_init 创建虚拟机内存
        kvm_pc_setup_irq_routing 创建中断路由表
        i440fx_init 初始化北桥
        kvm_i8259_init 初始化中断控制器
        pc_vga_init 初始化显卡设备
        pc_basic_device_init 初始化基础设备(一些默认的设备,如IDE总线,ISA总线,USB总线等) 
        pc_cmos_init 初始化bios
  1. 前端设备初始化(qemu command中的-device参数)
qemu_opts_foreach(qemu_find_opts("device"), device_init_func, NULL, 1)
  1. 加载设备rom(/usr/share/qemu)
int rom_load_all(void)
  1. 经过上面步骤的准备之后,一台虚拟机的所有虚拟硬件都已经准备完毕,这时候qemu会进入关键的流程main_loop中。main_loop是一个死循环,通过内核的epoll机制监听上面创建的所有fd,包括设备,vcpu等。虚拟机内部所有对设备的读写/对vcpu的操作都会触发句柄改变状态并被main_loop循环监听到,分发给注册好的回调函数处理这些事件。处理完成之后继续进入wait状态等待下一次触发。

以上就是qemu进程启动的主要流程,接下来启动虚拟机操作系统的流程为:

  • libvirt通过qmp协议下发启动命令
  • main_loop中捕获qemu monitor句柄事件
  • qemu monitor回调函数中调用上电流程。
  • 系统上电,执行bios。
  • bios根据设置的boot order依次引导每一个启动设备,直到遇到第一个有活动分区的设备。(一般第一块硬盘的第一个分区是活动分区,分区表中标志位为80。)
  • 如果是启动设备是硬盘设备,会把这块硬盘上前446字节的bootloader读入到内存中。
  • bootloader开始引导硬盘上的操作系统。

相关阅读:虚拟机创建流程-qemu篇(上)

本文来自网易实践者社区,经作者岳文远授权发布。