首页
社区
课程
招聘
[原创]QEMU/KVM虚拟机运行核心流程
发表于: 2022-9-7 12:23 19245

[原创]QEMU/KVM虚拟机运行核心流程

2022-9-7 12:23
19245

这几天学习了虚拟机在创建和运行过程中,QEMU和KVM的核心执行流程。当然只是大概过程,并没有做到流程中的每个函数都分析。
很喜欢侯捷老师的一句话:源码之前,了无秘密。我阅读的源码是qemu-6.2.0和linux-5.15.39。

编译安装qemu的过程很简单,参考官方文档就行。
可以直接用gdb命令行调试qemu,也可以vscode搭配gdb,调试属于基本能力,不多说。
动态调试qemu,并结合qemu源码分析流程。
启动参数如下:

qemu-6.2.0/softmmu/vl.c,line 2765
在vl.c文件2765行是入口,对运行程序传入的参数进行解析。

qemu-6.2.0/accel/kvm/kvm-all.c line 2306
在kvm-all.c文件2306行是kvm_init
调用栈:

主要函数kvm_init

其主要功能是保存了kvm设备描述符s->fd,创建的虚拟机的描述符s->vmfd。

在2.1节中有提到,qmp_x_exit_preconfig函数与虚拟cpu的创建有关。
动态调试跟踪分析
qmp_x_exit_preconfig qemu-6.2.0\softmmu\vl.c:2740
--> qemu_init_board qemu-6.2.0\softmmu\vl.c:2652
--> machine_run_board_init qemu-6.2.0\hw\core\machine.c:1181
--> pc_q35_init qemu-6.2.0\hw\i386\pc_q35.c:182
--> x86_cpus_init qemu-6.2.0\hw\i386\x86.c:141
--> x86_cpu_new qemu-6.2.0\hw\i386\x86.c:114

在machine_run_board_init函数中根据参数中给的机器类型调用不同的pc_machine_init函数
machine_class->init(machine)----pc_q35_init

在x86_cpu_new中继续虚拟cpu的创建
x86_cpu_new
--> qdev_realize qemu-6.2.0\hw\core\qdev.c:333
--> device_set_realized qemu-6.2.0\hw\core\qdev.c:531
--> x86_cpu_realizefn qemu-6.2.0\target\i386\cpu.c:6447
--> qemu_init_vcpu qemu-6.2.0\softmmu\cpus.c:613
在x86_cpu_realizefn中调用qemu_init_vcpu对创建的虚拟cpu进行初始化

qemu-6.2.0/softmmu/cpus.c line 611
在cpus.c文件611行qemu_init_vcpu中初始化虚拟cpu,创建执行线程。

虚拟机的运行就是kvm_cpu_exec中的do()while(ret == 0)的循环,该循环体中主要通过KVM_RUN启动虚拟机,进入了kvm的内核处理阶段,并等待返回结果。
当虚拟机退出,会根据返回的原因进行相应处理,最后将处理结果返回。
而kvm_cpu_exec自身也处于vcpu线程函数kvm_vcpu_thread_fn的循环当中,所以虚拟机的运行就是在这两个循环中不断进行。

解析参数,创建虚拟机,创建虚拟cpu,并获取三个最主要的描述符kvmfd、vmfd以及vcpufd。
根据vcpu数量创建具体的执行线程。
在线程中通过KVM_RUN启动虚拟机,进入内核KVM的处理流程。
重复循环KVM_RUN阶段。

在用户层QEMU阶段有提到通过函数kvm_vcpu_ioctl(cpu, KVM_RUN, 0)进入到内核KVM处理阶段。

linux-5.15.39/virt/kvm/kvm_main.c,line 3764
在kvm_main.c文件3764行找到内核中实际的kvm_vcpu_ioctl函数。

那调用流程就是
kvm_vcpu_ioctl --> kvm_arch_vcpu_ioctl_run
--> vcpu_run --> vcpu_enter_guest
--> static_call(kvm_x86_run)(vcpu)

在arch/x86/kvm/vmx/vmx.c line 7584
定义了一系列架构相关的操作函数
关注运行相关的
.run = vmx_vcpu_run,

在arch/x86/kvm/vmx/vmx.c line 7584
定义了一系列架构相关的操作函数
关注退出处理相关的
.handle_exit = vmx_handle_exit,

进入guest世界的准备工作。
正式进入guest执行。
根据guest退出原因进行处理,KVM先自行处理,
若kvm不能完全处理,则返回到用户层由QEMU处理。
QEMU处理后再次通过KVM_RUN进入到内核KVM流程。

.《Intel® Volume 3 System Programming Guide》
.《系统虚拟化:原理与实现》
.《处理器虚拟化技术》
. https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.15.39.tar.xz
. https://download.qemu.org/qemu-6.2.0.tar.xz

$ ./qemu-system-x86_64 \
--enable-kvm \
-machine q35 \
-cpu host,+vmx \
-smp 1 \
-m 2048 \
-name ubuntu \
-hda /opt/vms/ubuntu.qcow2 \
-cdrom /opt/vms/ubuntu.iso
$ ./qemu-system-x86_64 \
--enable-kvm \
-machine q35 \
-cpu host,+vmx \
-smp 1 \
-m 2048 \
-name ubuntu \
-hda /opt/vms/ubuntu.qcow2 \
-cdrom /opt/vms/ubuntu.iso
void qemu_init(int argc, char **argv, char **envp)
{
    //...
    // 对参数进行解析
    for(;;) {
        if (optind >= argc)
            break;
        if (argv[optind][0] != '-') {
            loc_set_cmdline(argv, optind, 1);
            drive_add(IF_DEFAULT, 0, argv[optind++], HD_OPTS);
        } else {
            const QEMUOption *popt;
 
            popt = lookup_opt(argc, argv, &optarg, &optind);
            if (!(popt->arch_mask & arch_type)) {
                error_report("Option not supported for this target");
                exit(1);
            }
            switch(popt->index) {
            case QEMU_OPTION_cpu:
                /* hw initialization will check this */
                cpu_option = optarg;
                break;
            //...
            // 主要关注下面几个参数
            case QEMU_OPTION_m:
                opts = qemu_opts_parse_noisily(qemu_find_opts("memory"),
                                               optarg, true);
                if (!opts) {
                    exit(EXIT_FAILURE);
                }
                break;
            case QEMU_OPTION_enable_kvm:
                qdict_put_str(machine_opts_dict, "accel", "kvm");
                break;
            case QEMU_OPTION_M:
            case QEMU_OPTION_machine:
                {
                    bool help;
 
                    keyval_parse_into(machine_opts_dict, optarg, "type", &help, &error_fatal);
                    if (help) {
                        machine_help_func(machine_opts_dict);
                        exit(EXIT_SUCCESS);
                    }
                    break;
                }
            case QEMU_OPTION_smp:
                machine_parse_property_opt(qemu_find_opts("smp-opts"),
                                           "smp", optarg);
                break;
            }
        }
    }
    //...
    // 根据accel设置accelerators = kvm
    qemu_apply_legacy_machine_options(machine_opts_dict);
    qemu_apply_machine_options(machine_opts_dict);
 
    // 也会根据进程名判断可用的加速类型
    configure_accelerators(argv[0]);
    // 内部调用了do_configure_accelerator --> accel_init_machine
    // accel_init_machine --> kvm_init
    // 初始化具体的accel类(这里是kvm)
    // 在qemu-6.2.0/accel/kvm/kvm-all.c line 3629
    // 函数kvm_accel_class_init内部找到真正的初始化函数
    // ac->init_machine = kvm_init;
    //...
 
    // 在qmp_x_exit_preconfig与虚拟cpu创建有关
    if (!preconfig_requested) {
        qmp_x_exit_preconfig(&error_fatal);
    }
    qemu_init_displays();
    // 设置accel
    accel_setup_post(current_machine);
    os_setup_post();
    resume_mux_open();
}
void qemu_init(int argc, char **argv, char **envp)
{
    //...
    // 对参数进行解析
    for(;;) {
        if (optind >= argc)
            break;
        if (argv[optind][0] != '-') {
            loc_set_cmdline(argv, optind, 1);
            drive_add(IF_DEFAULT, 0, argv[optind++], HD_OPTS);
        } else {
            const QEMUOption *popt;
 
            popt = lookup_opt(argc, argv, &optarg, &optind);
            if (!(popt->arch_mask & arch_type)) {
                error_report("Option not supported for this target");
                exit(1);
            }
            switch(popt->index) {
            case QEMU_OPTION_cpu:
                /* hw initialization will check this */
                cpu_option = optarg;
                break;
            //...
            // 主要关注下面几个参数
            case QEMU_OPTION_m:
                opts = qemu_opts_parse_noisily(qemu_find_opts("memory"),
                                               optarg, true);
                if (!opts) {
                    exit(EXIT_FAILURE);
                }
                break;
            case QEMU_OPTION_enable_kvm:
                qdict_put_str(machine_opts_dict, "accel", "kvm");
                break;
            case QEMU_OPTION_M:
            case QEMU_OPTION_machine:
                {
                    bool help;
 
                    keyval_parse_into(machine_opts_dict, optarg, "type", &help, &error_fatal);
                    if (help) {
                        machine_help_func(machine_opts_dict);
                        exit(EXIT_SUCCESS);
                    }
                    break;
                }
            case QEMU_OPTION_smp:
                machine_parse_property_opt(qemu_find_opts("smp-opts"),
                                           "smp", optarg);
                break;
            }
        }
    }
    //...
    // 根据accel设置accelerators = kvm
    qemu_apply_legacy_machine_options(machine_opts_dict);
    qemu_apply_machine_options(machine_opts_dict);
 
    // 也会根据进程名判断可用的加速类型
    configure_accelerators(argv[0]);
    // 内部调用了do_configure_accelerator --> accel_init_machine
    // accel_init_machine --> kvm_init
    // 初始化具体的accel类(这里是kvm)
    // 在qemu-6.2.0/accel/kvm/kvm-all.c line 3629
    // 函数kvm_accel_class_init内部找到真正的初始化函数
    // ac->init_machine = kvm_init;
    //...
 
    // 在qmp_x_exit_preconfig与虚拟cpu创建有关
    if (!preconfig_requested) {
        qmp_x_exit_preconfig(&error_fatal);
    }
    qemu_init_displays();
    // 设置accel
    accel_setup_post(current_machine);
    os_setup_post();
    resume_mux_open();
}
参数 描述
QEMU_OPTION_m 虚拟机内存大小
QEMU_OPTION_enable_kvm 启用kvm加速
QEMU_OPTION_machine 虚拟机机器类型
QEMU_OPTION_smp 虚拟机cpu数量
kvm_init(MachineState * ms) (qemu-6.2.0\accel\kvm\kvm-all.c:2308)
accel_init_machine(AccelState * accel, MachineState * ms) (qemu-6.2.0\accel\accel-softmmu.c:39)
do_configure_accelerator(void * opaque, QemuOpts * opts, Error ** errp) (qemu-6.2.0\softmmu\vl.c:2348)
qemu_opts_foreach(QemuOptsList * list, qemu_opts_loopfunc func, void * opaque, Error ** errp) (qemu-6.2.0\util\qemu-option.c:1135)
configure_accelerators(const char * progname) (qemu-6.2.0\softmmu\vl.c:2414)
qemu_init(int argc, char ** argv, char ** envp) (qemu-6.2.0\softmmu\vl.c:3724)
main(int argc, char ** argv, char ** envp) (qemu-6.2.0\softmmu\main.c:49)
kvm_init(MachineState * ms) (qemu-6.2.0\accel\kvm\kvm-all.c:2308)
accel_init_machine(AccelState * accel, MachineState * ms) (qemu-6.2.0\accel\accel-softmmu.c:39)
do_configure_accelerator(void * opaque, QemuOpts * opts, Error ** errp) (qemu-6.2.0\softmmu\vl.c:2348)
qemu_opts_foreach(QemuOptsList * list, qemu_opts_loopfunc func, void * opaque, Error ** errp) (qemu-6.2.0\util\qemu-option.c:1135)
configure_accelerators(const char * progname) (qemu-6.2.0\softmmu\vl.c:2414)
qemu_init(int argc, char ** argv, char ** envp) (qemu-6.2.0\softmmu\vl.c:3724)
main(int argc, char ** argv, char ** envp) (qemu-6.2.0\softmmu\main.c:49)
static int kvm_init(MachineState *ms)
{
    MachineClass *mc = MACHINE_GET_CLASS(ms);
    static const char upgrade_note[] =
        "Please upgrade to at least kernel 2.6.29 or recent kvm-kmod\n"
        "(see http://sourceforge.net/projects/kvm).\n";
    //...
    QLIST_INIT(&s->kvm_parked_vcpus);
    // 开始使用kvm之前的标准流程
    // 打开设备/dev/kvm,检查kvm API版本
    // 保存了kvm设备描述符s->fd
    s->fd = qemu_open_old("/dev/kvm", O_RDWR);
    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);
    if (ret < KVM_API_VERSION) {
        if (ret >= 0) {
            ret = -EINVAL;
        }
        fprintf(stderr, "kvm version too old\n");
        goto err;
    }
 
    if (ret > KVM_API_VERSION) {
        ret = -EINVAL;
        fprintf(stderr, "kvm version not supported\n");
        goto err;
    }
 
    //...
    // 创建虚拟机,保存虚拟机描述符s->vmfd
    do {
        ret = kvm_ioctl(s, KVM_CREATE_VM, type);
    } while (ret == -EINTR);
    //...
    s->vmfd = ret;
    //...
}
static int kvm_init(MachineState *ms)
{
    MachineClass *mc = MACHINE_GET_CLASS(ms);
    static const char upgrade_note[] =
        "Please upgrade to at least kernel 2.6.29 or recent kvm-kmod\n"
        "(see http://sourceforge.net/projects/kvm).\n";
    //...
    QLIST_INIT(&s->kvm_parked_vcpus);
    // 开始使用kvm之前的标准流程
    // 打开设备/dev/kvm,检查kvm API版本
    // 保存了kvm设备描述符s->fd
    s->fd = qemu_open_old("/dev/kvm", O_RDWR);
    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);
    if (ret < KVM_API_VERSION) {
        if (ret >= 0) {
            ret = -EINVAL;
        }
        fprintf(stderr, "kvm version too old\n");
        goto err;
    }
 
    if (ret > KVM_API_VERSION) {
        ret = -EINVAL;
        fprintf(stderr, "kvm version not supported\n");
        goto err;
    }
 
    //...
    // 创建虚拟机,保存虚拟机描述符s->vmfd
    do {
        ret = kvm_ioctl(s, KVM_CREATE_VM, type);
    } while (ret == -EINTR);
    //...
    s->vmfd = ret;
    //...
}
 
void x86_cpus_init(X86MachineState *x86ms, int default_cpu_version)
{
    //...
    // 根据参数smp的值,创建对应数量的虚拟cpu
    for (i = 0; i < ms->smp.cpus; i++) {
        x86_cpu_new(x86ms, possible_cpus->cpus[i].arch_id, &error_fatal);
    }
}
void x86_cpus_init(X86MachineState *x86ms, int default_cpu_version)
{
    //...
    // 根据参数smp的值,创建对应数量的虚拟cpu
    for (i = 0; i < ms->smp.cpus; i++) {
        x86_cpu_new(x86ms, possible_cpus->cpus[i].arch_id, &error_fatal);
    }
}
void qemu_init_vcpu(CPUState *cpu)
{
    //...
    // 调用函数kvm_start_vcpu_thread创建虚拟cpu执行线程
    cpus_accel->create_vcpu_thread(cpu);
    //...
}
static void kvm_start_vcpu_thread(CPUState *cpu)
{
    //...
    // 线程函数kvm_vcpu_thread_fn
    qemu_thread_create(cpu->thread, thread_name, kvm_vcpu_thread_fn,
                       cpu, QEMU_THREAD_JOINABLE);
    //...
}
static void *kvm_vcpu_thread_fn(void *arg)
{
    //...
    // kvm_init_vcpu中通过kvm_vm_ioctl(s, KVM_CREATE_VCPU, (void *)vcpu_id)
    // 获取了vcpu描述符 cpu->kvm_fd = ret;
    r = kvm_init_vcpu(cpu, &error_fatal);
    kvm_init_cpu_signals(cpu);
 
    /* signal CPU creation */
    cpu_thread_signal_created(cpu);
    qemu_guest_random_seed_thread_part2(cpu->random_seed);
 
    // do while循环执行kvm_cpu_exec
    do {
        if (cpu_can_run(cpu)) {
            r = kvm_cpu_exec(cpu);
            if (r == EXCP_DEBUG) {
                cpu_handle_guest_debug(cpu);
            }
        }
        qemu_wait_io_event(cpu);
    } while (!cpu->unplug || cpu_can_run(cpu));
 
    kvm_destroy_vcpu(cpu);
    cpu_thread_signal_destroyed(cpu);
    qemu_mutex_unlock_iothread();
    rcu_unregister_thread();
    return NULL;
}
int kvm_cpu_exec(CPUState *cpu)
{
    //...
    do {
        // kvm_vcpu_ioctl(cpu, KVM_RUN, 0)
        // 从这里进入kvm内核阶段,开始运行虚拟机
        run_ret = kvm_vcpu_ioctl(cpu, KVM_RUN, 0);
        //...
        // 根据退出原因,分发处理
        switch (run->exit_reason) {
        case KVM_EXIT_IO:
            DPRINTF("handle_io\n");
            /* Called outside BQL */
            kvm_handle_io(run->io.port, attrs,
                          (uint8_t *)run + run->io.data_offset,
                          run->io.direction,
                          run->io.size,
                          run->io.count);
            ret = 0;
            break;
        default:
            DPRINTF("kvm_arch_handle_exit\n");
            ret = kvm_arch_handle_exit(cpu, run);
            break;
        }
    } while (ret == 0);
    cpu_exec_end(cpu);
    //...
    qatomic_set(&cpu->exit_request, 0);
    return ret;
}
void qemu_init_vcpu(CPUState *cpu)
{
    //...
    // 调用函数kvm_start_vcpu_thread创建虚拟cpu执行线程
    cpus_accel->create_vcpu_thread(cpu);
    //...
}
static void kvm_start_vcpu_thread(CPUState *cpu)
{
    //...
    // 线程函数kvm_vcpu_thread_fn
    qemu_thread_create(cpu->thread, thread_name, kvm_vcpu_thread_fn,
                       cpu, QEMU_THREAD_JOINABLE);
    //...
}
static void *kvm_vcpu_thread_fn(void *arg)
{
    //...
    // kvm_init_vcpu中通过kvm_vm_ioctl(s, KVM_CREATE_VCPU, (void *)vcpu_id)
    // 获取了vcpu描述符 cpu->kvm_fd = ret;
    r = kvm_init_vcpu(cpu, &error_fatal);
    kvm_init_cpu_signals(cpu);
 
    /* signal CPU creation */
    cpu_thread_signal_created(cpu);
    qemu_guest_random_seed_thread_part2(cpu->random_seed);
 
    // do while循环执行kvm_cpu_exec
    do {
        if (cpu_can_run(cpu)) {
            r = kvm_cpu_exec(cpu);
            if (r == EXCP_DEBUG) {
                cpu_handle_guest_debug(cpu);
            }
        }
        qemu_wait_io_event(cpu);
    } while (!cpu->unplug || cpu_can_run(cpu));
 
    kvm_destroy_vcpu(cpu);
    cpu_thread_signal_destroyed(cpu);
    qemu_mutex_unlock_iothread();
    rcu_unregister_thread();
    return NULL;
}
int kvm_cpu_exec(CPUState *cpu)
{
    //...
    do {
        // kvm_vcpu_ioctl(cpu, KVM_RUN, 0)

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 17
支持
分享
最新回复 (9)
雪    币: 24
活跃值: (1353)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
感谢分享
2022-9-9 14:44
0
雪    币: 1907
活跃值: (5999)
能力值: ( LV7,RANK:116 )
在线值:
发帖
回帖
粉丝
3
路总牛逼!
2022-9-9 15:10
1
雪    币: 183
活跃值: (2427)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
4
路总牛逼!
2022-9-9 15:12
1
雪    币: 3475
活跃值: (7764)
能力值: ( LV4,RANK:41 )
在线值:
发帖
回帖
粉丝
5
路总牛逼!
2022-9-9 15:14
1
雪    币: 9
活跃值: (287)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
路总牛逼!
2022-9-10 10:18
1
雪    币: 9934
活跃值: (2554)
能力值: ( LV6,RANK:87 )
在线值:
发帖
回帖
粉丝
7
路总牛逼!
2022-9-11 16:43
1
雪    币: 65
活跃值: (437)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
8
Mark
2022-11-2 10:46
0
雪    币: 3738
活跃值: (3872)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
9
感谢分享!
2022-11-2 11:27
0
雪    币: 42
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
10
牛逼
2023-6-17 16:17
0
游客
登录 | 注册 方可回帖
返回
//