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 内部结构,最好使用当前代码,而不是一年前的版本。
我正在写一份关于 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 内部结构,最好使用当前代码,而不是一年前的版本。