系列文章目录

第十四章 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;
    ...
}

运行后,输出信息如下:



总结

以上分析了系统初始化过程中创建早期后端驱动的过程。

05-08 23:08