Qemu 如何模拟 PCIe 设备?

Luc*_*lla 5 c virtualization kvm qemu virtual-machine

我正在写一个关于 qemu 内部结构的开源文档,所以如果你帮助我,你就是在帮助 Qemu 项目的发展

我找到的最接近的答案是:在哪些条件下 ioctl KVM_RUN 返回?

这是在 KVM 上运行的单个 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);
    if (r < 0) {
        error_report("kvm_init_vcpu failed: %s", strerror(-r));
        exit(1);
    }

    kvm_init_cpu_signals(cpu);

    /* signal CPU creation */
    cpu->created = true;
    qemu_cond_signal(&qemu_cpu_cond);
    qemu_guest_random_seed_thread_part2(cpu->random_seed);

    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));

    qemu_kvm_destroy_vcpu(cpu);
    cpu->created = false;
    qemu_cond_signal(&qemu_cpu_cond);
    qemu_mutex_unlock_iothread();
    rcu_unregister_thread();
    return NULL;
}
Run Code Online (Sandbox Code Playgroud)

你可以在这里看到:

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));
Run Code Online (Sandbox Code Playgroud)

每次 KVM 返回时,它都会为 Qemu 提供模拟的机会。我认为当来宾上的内核尝试访问 PCIe 设备时,主机上的 KVM 将返回。我不知道KVM怎么知道如何返回。也许 KVM 维护 PCIe 设备的地址,并告诉 Intel 的 VT-D 或 AMD 的 IOV 哪些地址应该生成异常。有人可以澄清这一点吗?

好吧,从 的外观来看qemu_kvm_cpu_thread_fn,唯一可以模拟 PCIe 访问的地方是qemu_wait_io_event(cpu),它的定义如下: https: //github.com/qemu/qemu/blob/stable-4.2/cpus.c#L1266和此处定义的调用qemu_wait_io_event_common: https: //github.com/qemu/qemu/blob/stable-4.2/cpus.c#L1241process_queued_cpu_work此处定义的调用:https: //github.com/qemu/qemu/blob/stable-4.2 /cpus-common.c#L309

让我们看看执行队列函数的代码:

 while (cpu->queued_work_first != NULL) {
        wi = cpu->queued_work_first;
        cpu->queued_work_first = wi->next;
        if (!cpu->queued_work_first) {
            cpu->queued_work_last = NULL;
        }
        qemu_mutex_unlock(&cpu->work_mutex);
        if (wi->exclusive) {
            /* Running work items outside the BQL avoids the following deadlock:
             * 1) start_exclusive() is called with the BQL taken while another
             * CPU is running; 2) cpu_exec in the other CPU tries to takes the
             * BQL, so it goes to sleep; start_exclusive() is sleeping too, so
             * neither CPU can proceed.
             */
            qemu_mutex_unlock_iothread();
            start_exclusive();
            wi->func(cpu, wi->data);
Run Code Online (Sandbox Code Playgroud)

看起来,qemu_kvm_cpu_thread_fn当 KVM 返回时,VCPU 线程拥有的唯一权力就是执行排队的函数:

wi->func(cpu, wi->data);
Run Code Online (Sandbox Code Playgroud)

这意味着 PCIe 设备必须不断地将自身排队作为 qemu 执行的函数。我不明白它会如何运作。

能够在该 cpu 上排队工作的功能已run_on_cpu在其名称中列出。通过在 VSCode 上搜索,我发现了一些对工作进行排队的函数,但没有一个与 PCIe 甚至仿真相关。我发现的最好的函数是这个显然修补指令的函数:https://github.com/qemu/qemu/blob/stable-4.2/hw/i386/kvmvapic.c#L446。很好,我也想知道。

Pet*_*ell 5

KVM 下的设备模拟(所有设备,不仅仅是 PCI)由“switch (run-> exit_reason)”在 kvm_cpu_exec() 内。qemu_wait_io_event() 不相关。

想知道执行如何“模拟 PCI 设备上的寄存器读取”吗?在 gdb 下运行 QEMU,在您正在使用的以太网 PCI 卡的寄存器读/写函数上设置一个断点,然后当您进入调试器时查看堆栈回溯。(编译 QEMU --enable-debug 以获得此类事情的更好调试信息。)

PS:如果您出于教育目的而检查 QEMU 内部结构,您最好使用当前代码,而不是一年前的版本。