Qemu 如何模拟 PCIe 设备?

How does Qemu emulate PCIe devices?

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

我找到的最接近的答案是:

这是 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;
}

你可以在这里看到:

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 returns,它都为 Qemu 提供了一个模拟事物的机会。我想当客户机上的内核尝试访问 PCIe 设备时,主机上的 KVM returns。我不知道 KVM 怎么知道 return。也许 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 and which calls qemu_wait_io_event_common defined here: https://github.com/qemu/qemu/blob/stable-4.2/cpus.c#L1241 which calls process_queued_cpu_work defined here: 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);

看起来 VCPU 线程 qemu_kvm_cpu_thread_fn 在 KVM returns 时的唯一能力是执行排队的函数:

wi->func(cpu, wi->data);

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

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

KVM 下的设备仿真(所有设备,不仅仅是 PCI)由“case KVM_EXIT_IO”(对于 x86 风格的 IO 端口)和“case KVM_EXIT_MMIO”(对于内存)处理kvm_cpu_exec() 内的“开关 (运行->exit_reason)”中的映射 IO(包括 PCI)。 qemu_wait_io_event() 无关。

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

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