系列文章目录
第二十五章 QEMU系统仿真的机器创建分析实例
文章目录
前言
本文以 QEMU 8.2.2 为例,分析其作为系统仿真工具的工作过程,并为读者展示各种 QEMU 系统仿真的启动配置实例。
本文读者需要具备一定的 QEMU 系统仿真使用经验,并对 C 语言编程有一定了解。
一、QEMU是什么?
QEMU 是一个通用且开源的机器模拟器和虚拟机。
其官方主页是:https://www.qemu.org/
二、QEMU系统仿真的机器创建分析实例
1.系统仿真的命令行参数
QEMU 作为系统仿真工具,其入口代码在 system/main.c 文件中,初始化函数 qemu_init() 的实现在 system/vl.c 文件中。
前文完成创建目标机器的过程分析,本文将继续后续运行过程的分析,读者需要对 QEMU 系统启动过程的程序代码有所了解,相关内容可以参考《QEMU系统分析之启动篇》系列文章。
..\qemu\8.2.2-qkd\qemu-system-x86_64.exe -cpu "Penryn,vendor=GenuineIntel,+ssse3,+sse4.2" -M "q35,accel=whpx,smm=off" -object "memory-backend-ram,id=ram0,size=4G,prealloc=on,share=on,merge=off,dump=off" -object "memory-backend-ram,id=ram1,size=2G,prealloc=on,share=on,merge=off,dump=off" -numa "node,memdev=ram0,cpus=0,nodeid=0" -numa "node,memdev=ram1,cpus=1,nodeid=1" -smp "cpus=2" -m "6G" -audio "sdl,model=hda" -vga "std" -netdev "user,id=mynet0" -device "e1000,id=nic1,netdev=mynet0" -L "data" -qtest "unix:qtest-sock,server,nowait"
2. 将当前机器配置导出到文件
这部分代码在 system/vl.c 文件中,实现如下:
int qemu_init(int argc, char **argv)
{
...
if (!preconfig_requested) {
qmp_x_exit_preconfig(&error_fatal);
}
...
}
前文分析了解析机器的存储设备设置的过程,本文将分析解析 NUMA 结点配置项的过程。
qmp_x_exit_preconfig()
函数 qmp_x_exit_preconfig() 代码如下:
void qmp_x_exit_preconfig(Error **errp)
{
if (phase_check(PHASE_MACHINE_INITIALIZED)) {
error_setg(errp, "The command is permitted only before machine initialization");
return;
}
qemu_init_board();
qemu_create_cli_devices();
qemu_machine_creation_done();
if (loadvm) {
load_snapshot(loadvm, NULL, false, NULL, &error_fatal);
}
if (replay_mode != REPLAY_MODE_NONE) {
replay_vmstate_init();
}
if (incoming) {
Error *local_err = NULL;
if (strcmp(incoming, "defer") != 0) {
qmp_migrate_incoming(incoming, false, NULL, &local_err);
if (local_err) {
error_reportf_err(local_err, "-incoming %s: ", incoming);
exit(1);
}
}
} else if (autostart) {
qmp_cont(NULL);
}
}
首先,调用函数 qemu_init_board() 初始化机器主板,代码如下:
qemu_init_board();
qemu_init_board()
代码如下:
static void qemu_init_board(void)
{
/* process plugin before CPUs are created, but once -smp has been parsed */
qemu_plugin_load_list(&plugin_list, &error_fatal);
/* From here on we enter MACHINE_PHASE_INITIALIZED. */
machine_run_board_init(current_machine, mem_path, &error_fatal);
drive_check_orphaned();
realtime_init();
}
在函数 qemu_init_board() 中,首先运行机器主板的初始化,代码如下:
void machine_run_board_init(MachineState *machine, const char *mem_path, Error **errp)
{
ERRP_GUARD();
MachineClass *machine_class = MACHINE_GET_CLASS(machine);
ObjectClass *oc = object_class_by_name(machine->cpu_type);
CPUClass *cc;
/* This checkpoint is required by replay to separate prior clock
reading from the other reads, because timer polling functions query
clock values from the log. */
replay_checkpoint(CHECKPOINT_INIT);
if (!xen_enabled()) {
/* On 32-bit hosts, QEMU is limited by virtual address space */
if (machine->ram_size > (2047 << 20) && HOST_LONG_BITS == 32) {
error_setg(errp, "at most 2047 MB RAM can be simulated");
return;
}
}
if (machine->memdev) {
ram_addr_t backend_size = object_property_get_uint(OBJECT(machine->memdev),
"size", &error_abort);
if (backend_size != machine->ram_size) {
error_setg(errp, "Machine memory size does not match the size of the memory backend");
return;
}
} else if (machine_class->default_ram_id && machine->ram_size &&
numa_uses_legacy_mem()) {
if (object_property_find(object_get_objects_root(),
machine_class->default_ram_id)) {
error_setg(errp, "object's id '%s' is reserved for the default"
" RAM backend, it can't be used for any other purposes",
machine_class->default_ram_id);
error_append_hint(errp,
"Change the object's 'id' to something else or disable"
" automatic creation of the default RAM backend by setting"
" 'memory-backend=%s' with '-machine'.\n",
machine_class->default_ram_id);
return;
}
if (!create_default_memdev(current_machine, mem_path, errp)) {
return;
}
}
if (machine->numa_state) {
numa_complete_configuration(machine);
if (machine->numa_state->num_nodes) {
machine_numa_finish_cpu_init(machine);
if (machine_class->cpu_cluster_has_numa_boundary) {
validate_cpu_cluster_to_numa_boundary(machine);
}
}
}
if (!machine->ram && machine->memdev) {
machine->ram = machine_consume_memdev(machine, machine->memdev);
}
/* If the machine supports the valid_cpu_types check and the user
* specified a CPU with -cpu check here that the user CPU is supported.
*/
if (machine_class->valid_cpu_types && machine->cpu_type) {
int i;
for (i = 0; machine_class->valid_cpu_types[i]; i++) {
if (object_class_dynamic_cast(oc,
machine_class->valid_cpu_types[i])) {
/* The user specified CPU is in the valid field, we are
* good to go.
*/
break;
}
}
if (!machine_class->valid_cpu_types[i]) {
/* The user specified CPU is not valid */
error_report("Invalid CPU type: %s", machine->cpu_type);
error_printf("The valid types are: %s",
machine_class->valid_cpu_types[0]);
for (i = 1; machine_class->valid_cpu_types[i]; i++) {
error_printf(", %s", machine_class->valid_cpu_types[i]);
}
error_printf("\n");
exit(1);
}
}
/* Check if CPU type is deprecated and warn if so */
cc = CPU_CLASS(oc);
if (cc && cc->deprecation_note) {
warn_report("CPU model %s is deprecated -- %s", machine->cpu_type,
cc->deprecation_note);
}
if (machine->cgs) {
/*
* With confidential guests, the host can't see the real
* contents of RAM, so there's no point in it trying to merge
* areas.
*/
machine_set_mem_merge(OBJECT(machine), false, &error_abort);
/*
* Virtio devices can't count on directly accessing guest
* memory, so they need iommu_platform=on to use normal DMA
* mechanisms. That requires also disabling legacy virtio
* support for those virtio pci devices which allow it.
*/
object_register_sugar_prop(TYPE_VIRTIO_PCI, "disable-legacy",
"on", true);
object_register_sugar_prop(TYPE_VIRTIO_DEVICE, "iommu_platform",
"on", false);
}
accel_init_interfaces(ACCEL_GET_CLASS(machine->accelerator));
machine_class->init(machine);
phase_advance(PHASE_MACHINE_INITIALIZED);
}
跟踪调式进入函数 accel_init_interfaces(),代码如下:
void accel_init_interfaces(AccelClass *ac)
{
#ifndef CONFIG_USER_ONLY
accel_init_ops_interfaces(ac);
#endif /* !CONFIG_USER_ONLY */
accel_init_cpu_interfaces(ac);
}
跟踪调式进入函数 accel_init_ops_interfaces(),代码如下:
/* initialize the arch-independent accel operation interfaces */
void accel_init_ops_interfaces(AccelClass *ac)
{
const char *ac_name;
char *ops_name;
ObjectClass *oc;
AccelOpsClass *ops;
ac_name = object_class_get_name(OBJECT_CLASS(ac));
g_assert(ac_name != NULL);
ops_name = g_strdup_printf("%s" ACCEL_OPS_SUFFIX, ac_name);
ops = ACCEL_OPS_CLASS(module_object_class_by_name(ops_name));
oc = module_object_class_by_name(ops_name);
if (!oc) {
error_report("fatal: could not load module for type '%s'", ops_name);
exit(1);
}
g_free(ops_name);
ops = ACCEL_OPS_CLASS(oc);
/*
* all accelerators need to define ops, providing at least a mandatory
* non-NULL create_vcpu_thread operation.
*/
g_assert(ops != NULL);
if (ops->ops_init) {
ops->ops_init(ops);
}
cpus_register_accel(ops);
}
跟踪调式进入函数 accel_init_cpu_interfaces(),代码如下:
/* initialize the arch-specific accel CpuClass interfaces */
static void accel_init_cpu_interfaces(AccelClass *ac)
{
const char *ac_name; /* AccelClass name */
char *acc_name; /* AccelCPUClass name */
ObjectClass *acc; /* AccelCPUClass */
ac_name = object_class_get_name(OBJECT_CLASS(ac));
g_assert(ac_name != NULL);
acc_name = g_strdup_printf("%s-%s", ac_name, CPU_RESOLVING_TYPE);
acc = object_class_by_name(acc_name);
g_free(acc_name);
if (acc) {
object_class_foreach(accel_init_cpu_int_aux,
CPU_RESOLVING_TYPE, false, acc);
}
}
3.调试输出
首先,添加跟踪调试信息,修改后的代码如下:
void machine_run_board_init(MachineState *machine, const char *mem_path, Error **errp)
{
...
huedbg_flag = 1;
HUEDBG("run\n");
accel_init_interfaces(ACCEL_GET_CLASS(machine->accelerator));
HUEDBG("run\n");
huedbg_flag = 0;
...
}
运行后,输出信息如下:
[17456]../hw/core/machine.c/machine_run_board_init(1532):run
[17456]../qom/object.c/type_table_lookup(103):lookup type(accel) in hash table
[17456]../qom/object.c/type_get_parent(194):parent_type(accel)
[17456]../accel/accel-target.c/accel_init_interfaces(109):enter
[17456]../accel/accel-target.c/accel_init_interfaces(111):run
[17456]../accel/accel-system.c/accel_init_ops_interfaces(79):
[17456]../accel/accel-system.c/accel_init_ops_interfaces(81):ac_name=[whpx-accel]
[17456]../accel/accel-system.c/accel_init_ops_interfaces(83):
[17456]../accel/accel-system.c/accel_init_ops_interfaces(86):ops_name=[whpx-accel-ops]
[17456]../qom/object.c/object_class_by_name(1095):enter
[17456]../qom/object.c/type_table_lookup(103):lookup type(whpx-accel-ops) in hash table
[17456]../qom/object.c/object_class_by_name(1105):class(whpx-accel-ops) return
[17456]../qom/object.c/type_table_lookup(103):lookup type(accel-ops) in hash table
[17456]../qom/object.c/type_get_parent(194):parent_type(accel-ops)
[17456]../accel/accel-system.c/accel_init_ops_interfaces(88):
[17456]../qom/object.c/object_class_by_name(1095):enter
[17456]../qom/object.c/type_table_lookup(103):lookup type(whpx-accel-ops) in hash table
[17456]../qom/object.c/object_class_by_name(1105):class(whpx-accel-ops) return
[17456]../accel/accel-system.c/accel_init_ops_interfaces(90):
[17456]../accel/accel-system.c/accel_init_ops_interfaces(96):
[17456]../accel/accel-system.c/accel_init_ops_interfaces(98):
[17456]../accel/accel-system.c/accel_init_ops_interfaces(104):
[17456]../accel/accel-system.c/accel_init_ops_interfaces(106):
[17456]../accel/accel-system.c/accel_init_ops_interfaces(108):
[17456]../accel/accel-system.c/accel_init_ops_interfaces(110):
[17456]../system/cpus.c/cpus_register_accel(624):cpus_accel=[00000188ee5b0560]
[17456]../util/huedbg-accel-ops.c/huedbg_dump_AccelOpsClass(51):<<<deep>>>=[9] ops=[00000188ee5b0560]
[17456]../util/huedbg-accel-ops.c/huedbg_dump_AccelOpsClass(53):ops_init=[00007ff7c8c758d0]
[17456]../util/huedbg-accel-ops.c/huedbg_dump_AccelOpsClass(55):cpus_are_resettable=[0000000000000000]
[17456]../util/huedbg-accel-ops.c/huedbg_dump_AccelOpsClass(56):cpu_reset_hold=[0000000000000000]
[17456]../util/huedbg-accel-ops.c/huedbg_dump_AccelOpsClass(58):create_vcpu_thread=[00007ff7c8c75a80]
[17456]../util/huedbg-accel-ops.c/huedbg_dump_AccelOpsClass(59):kick_vcpu_thread=[00007ff7c8c75990]
[17456]../util/huedbg-accel-ops.c/huedbg_dump_AccelOpsClass(60):cpu_thread_is_idle=[00007ff7c8c75970]
[17456]../util/huedbg-accel-ops.c/huedbg_dump_AccelOpsClass(62):synchronize_post_reset=[00007ff7c8c729a0]
[17456]../util/huedbg-accel-ops.c/huedbg_dump_AccelOpsClass(63):synchronize_post_init=[00007ff7c8c729b0]
[17456]../util/huedbg-accel-ops.c/huedbg_dump_AccelOpsClass(64):synchronize_state=[00007ff7c8c72980]
[17456]../util/huedbg-accel-ops.c/huedbg_dump_AccelOpsClass(65):synchronize_pre_loadvm=[00007ff7c8c729c0]
[17456]../util/huedbg-accel-ops.c/huedbg_dump_AccelOpsClass(66):synchronize_pre_resume=[00007ff7c8c729d0]
[17456]../util/huedbg-accel-ops.c/huedbg_dump_AccelOpsClass(68):handle_interrupt=[0000000000000000]
[17456]../util/huedbg-accel-ops.c/huedbg_dump_AccelOpsClass(70):get_virtual_clock=[0000000000000000]
[17456]../util/huedbg-accel-ops.c/huedbg_dump_AccelOpsClass(71):get_elapsed_ticks=[0000000000000000]
[17456]../util/huedbg-accel-ops.c/huedbg_dump_AccelOpsClass(73):supports_guest_debug=[0000000000000000]
[17456]../util/huedbg-accel-ops.c/huedbg_dump_AccelOpsClass(74):update_guest_debug=[0000000000000000]
[17456]../util/huedbg-accel-ops.c/huedbg_dump_AccelOpsClass(75):insert_breakpoint=[0000000000000000]
[17456]../util/huedbg-accel-ops.c/huedbg_dump_AccelOpsClass(76):remove_breakpoint=[0000000000000000]
[17456]../util/huedbg-accel-ops.c/huedbg_dump_AccelOpsClass(77):remove_all_breakpoints=[0000000000000000]
[17456]../accel/accel-system.c/accel_init_ops_interfaces(112):
[17456]../util/huedbg-accel.c/huedbg_dump_AccelClass(76):<<<deep>>>=[9] ac=[00000188ee6b38f0]
[17456]../util/huedbg-accel.c/huedbg_dump_AccelClass(79):name=[WHPX]
[17456]../util/huedbg-accel.c/huedbg_dump_AccelClass(80):init_machine=[00007ff7c8c74ce0]
[17456]../util/huedbg-accel.c/huedbg_dump_AccelClass(82):setup_post=[0000000000000000]
[17456]../util/huedbg-accel.c/huedbg_dump_AccelClass(83):has_memory=[0000000000000000]
[17456]../util/huedbg-accel.c/huedbg_dump_AccelClass(85):cpu_common_realize=[0000000000000000]
[17456]../util/huedbg-accel.c/huedbg_dump_AccelClass(86):cpu_common_unrealize=[0000000000000000]
[17456]../util/huedbg-accel.c/huedbg_dump_AccelClass(88):gdbstub_supported_sstep_flags=[0000000000000000]
[17456]../util/huedbg-accel.c/huedbg_dump_AccelClass(89):allowed=[00007ff7c9c8d9e9]
[17456]../util/huedbg-accel.c/huedbg_dump_AccelClass(91):*allowed=[1]
[17456]../util/huedbg-accel.c/huedbg_dump_AccelClass(93):compat_props=[0000000000000000]
[17456]../accel/accel-target.c/accel_init_interfaces(117):return
[17456]../hw/core/machine.c/machine_run_board_init(1534):run
总结
以上分析了系统初始化过程中运行机器主板初始化时对加速器的初始化配置。