系列文章目录
第十四章 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" -M "q35,accel=whpx,smm=off" -m "6G" -display "sdl" -audio "sdl,model=hda" -vga "std" -L "data"
2.完成早期后端驱动的设置工作
这部分代码在 system/vl.c 文件中,实现如下:
int qemu_init(int argc, char **argv)
{
...
qemu_create_early_backends();
...
}
前文分析了创建后端驱动过程中控制台和字符设备的创建过程,本文继续完成块设备和音频设备驱动的创建过程。
qemu_create_early_backends()
函数 qemu_create_early_backends() 代码如下:
static void qemu_create_early_backends(void)
{
...
/*
* Note: we need to create audio and block backends before
* setting machine properties, so they can be referred to.
*/
configure_blockdev(&bdo_queue, machine_class, snapshot);
audio_init_audiodevs();
if (default_audio) {
audio_create_default_audiodevs();
}
}
首先我们对块设备后端驱动进行配置。
configure_blockdev();
代码如下:
static void configure_blockdev(BlockdevOptionsQueue *bdo_queue,
MachineClass *machine_class, int snapshot)
{
/*
* If the currently selected machine wishes to override the
* units-per-bus property of its default HBA interface type, do so
* now.
*/
if (machine_class->units_per_default_bus) {
override_max_devs(machine_class->block_default_type,
machine_class->units_per_default_bus);
}
/* open the virtual block devices */
while (!QSIMPLEQ_EMPTY(bdo_queue)) {
BlockdevOptionsQueueEntry *bdo = QSIMPLEQ_FIRST(bdo_queue);
QSIMPLEQ_REMOVE_HEAD(bdo_queue, entry);
loc_push_restore(&bdo->loc);
qmp_blockdev_add(bdo->bdo, &error_fatal);
loc_pop(&bdo->loc);
qapi_free_BlockdevOptions(bdo->bdo);
g_free(bdo);
}
if (snapshot) {
qemu_opts_foreach(qemu_find_opts("drive"), drive_enable_snapshot,
NULL, NULL);
}
if (qemu_opts_foreach(qemu_find_opts("drive"), drive_init_func,
&machine_class->block_default_type, &error_fatal)) {
/* We printed help */
exit(0);
}
default_drive(default_cdrom, snapshot, machine_class->block_default_type, 2,
CDROM_OPTS);
default_drive(default_floppy, snapshot, IF_FLOPPY, 0, FD_OPTS);
default_drive(default_sdcard, snapshot, IF_SD, 0, SD_OPTS);
}
audio_init_audiodevs()
代码如下:
void audio_init_audiodevs(void)
{
AudiodevListEntry *e;
QSIMPLEQ_FOREACH(e, &audiodevs, next) {
audio_init(e->dev, &error_fatal);
}
}
对 audiodevs 中的每个音频设备调用函数 audio_init() 完成初始化。
函数 audio_init() 代码如下:
/*
* if we have dev, this function was called because of an -audiodev argument =>
* initialize a new state with it
* if dev == NULL => legacy implicit initialization, return the already created
* state or create a new one
*/
static AudioState *audio_init(Audiodev *dev, Error **errp)
{
static bool atexit_registered;
int done = 0;
const char *drvname;
VMChangeStateEntry *vmse;
AudioState *s;
struct audio_driver *driver;
s = g_new0(AudioState, 1);
QLIST_INIT (&s->hw_head_out);
QLIST_INIT (&s->hw_head_in);
QLIST_INIT (&s->cap_head);
if (!atexit_registered) {
atexit(audio_cleanup);
atexit_registered = true;
}
s->ts = timer_new_ns(QEMU_CLOCK_VIRTUAL, audio_timer, s);
if (dev) {
/* -audiodev option */
s->dev = dev;
drvname = AudiodevDriver_str(dev->driver);
driver = audio_driver_lookup(drvname);
if (driver) {
done = !audio_driver_init(s, driver, dev, errp);
} else {
error_setg(errp, "Unknown audio driver `%s'", drvname);
}
if (!done) {
goto out;
}
} else {
assert(!default_audio_state);
for (;;) {
AudiodevListEntry *e = QSIMPLEQ_FIRST(&default_audiodevs);
if (!e) {
error_setg(errp, "no default audio driver available");
goto out;
}
s->dev = dev = e->dev;
QSIMPLEQ_REMOVE_HEAD(&default_audiodevs, next);
g_free(e);
drvname = AudiodevDriver_str(dev->driver);
driver = audio_driver_lookup(drvname);
if (!audio_driver_init(s, driver, dev, NULL)) {
break;
}
qapi_free_Audiodev(dev);
s->dev = NULL;
}
}
if (dev->timer_period <= 0) {
s->period_ticks = 1;
} else {
s->period_ticks = dev->timer_period * (int64_t)SCALE_US;
}
vmse = qemu_add_vm_change_state_handler (audio_vm_change_state_handler, s);
if (!vmse) {
dolog ("warning: Could not register change state handler\n"
"(Audio can continue looping even after stopping the VM)\n");
}
QTAILQ_INSERT_TAIL(&audio_states, s, list);
QLIST_INIT (&s->card_head);
vmstate_register_any(NULL, &vmstate_audio, s);
return s;
out:
free_audio_state(s);
return NULL;
}
audio_create_default_audiodevs()
最后,如果设置了 default_audio,调用函数 audio_create_default_audiodevs() 创建默认音频设备,代码如下:
void audio_create_default_audiodevs(void)
{
for (int i = 0; audio_prio_list[i]; i++) {
if (audio_driver_lookup(audio_prio_list[i])) {
QDict *dict = qdict_new();
Audiodev *dev = NULL;
Visitor *v;
qdict_put_str(dict, "driver", audio_prio_list[i]);
qdict_put_str(dict, "id", "#default");
v = qobject_input_visitor_new_keyval(QOBJECT(dict));
qobject_unref(dict);
visit_type_Audiodev(v, NULL, &dev, &error_fatal);
visit_free(v);
audio_define_default(dev, &error_abort);
}
}
}
3.调试输出
首先,添加跟踪调试信息,修改后的代码如下:
```c
static void qemu_create_early_backends(void)
{
...
huedbg_flag = 1;
HUEDBG("\n");
huedbg_dump_device_configs(2);
HUEDBG("\n");
qemu_create_early_backends();
HUEDBG("\n");
huedbg_dump_device_configs(2);
HUEDBG("\n");
huedbg_flag = 0;
...
}
运行后,输出信息如下:
总结
以上分析了系统初始化过程中创建早期后端驱动的过程。