系列文章目录

第十七章 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 虚拟机导出信息的设置,接下来将处理预配置的工作,本篇文章将完成以下代码部分的分析。

2.主循环数据初始化

这部分代码在 system/vl.c 文件中,实现如下:

void qemu_init(int argc, char **argv)
{
...
    qemu_init_displays();
...
}

3. qemu_init_displays()

此函数在 /system/vl.c 文件中,定义如下:

static void qemu_init_displays(void)
{
    DisplayState *ds;

    /* init local displays */
    ds = init_displaystate();
    qemu_display_init(ds, &dpy);

    /* must be after terminal init, SDL library changes signal handlers */
    os_setup_signal_handling();

    /* init remote displays */
#ifdef CONFIG_VNC
    qemu_opts_foreach(qemu_find_opts("vnc"),
                      vnc_init_func, NULL, &error_fatal);
#endif

    if (using_spice) {
        qemu_spice.display_init();
    }
}

init_displaystate()

此函数在 /ui/console.c 文件中,定义如下:

static DisplayState *display_state;

/*
 * Called by main(), after creating QemuConsoles
 * and before initializing ui (sdl/vnc/...).
 */
DisplayState *init_displaystate(void)
{
    gchar *name;
    QemuConsole *con;

    QTAILQ_FOREACH(con, &consoles, next) {
        /* Hook up into the qom tree here (not in object_new()), once
         * all QemuConsoles are created and the order / numbering
         * doesn't change any more */
        name = g_strdup_printf("console[%d]", con->index);
        object_property_add_child(container_get(object_get_root(), "/backend"),
                                  name, OBJECT(con));
        g_free(name);
    }

    return display_state;
}

数据结构 DisplayState 定义如下:

struct DisplayState {
    QEMUTimer *gui_timer;
    uint64_t last_update;
    uint64_t update_interval;
    bool refreshing;

    QLIST_HEAD(, DisplayChangeListener) listeners;
};

qemu_display_init()

此函数在 /ui/console.c 文件中,定义如下:

void qemu_display_init(DisplayState *ds, DisplayOptions *opts)
{
    assert(opts->type < DISPLAY_TYPE__MAX);
    if (opts->type == DISPLAY_TYPE_NONE) {
        return;
    }
    assert(dpys[opts->type] != NULL);
    dpys[opts->type]->init(ds, opts);
}

其中,DISPLAY_TYPE__MAX 定义如下:

typedef enum DisplayType {
    DISPLAY_TYPE_DEFAULT,
    DISPLAY_TYPE_NONE,
#if defined(CONFIG_GTK)
    DISPLAY_TYPE_GTK,
#endif /* defined(CONFIG_GTK) */
#if defined(CONFIG_SDL)
    DISPLAY_TYPE_SDL,
#endif /* defined(CONFIG_SDL) */
#if defined(CONFIG_OPENGL)
    DISPLAY_TYPE_EGL_HEADLESS,
#endif /* defined(CONFIG_OPENGL) */
#if defined(CONFIG_CURSES)
    DISPLAY_TYPE_CURSES,
#endif /* defined(CONFIG_CURSES) */
#if defined(CONFIG_COCOA)
    DISPLAY_TYPE_COCOA,
#endif /* defined(CONFIG_COCOA) */
#if defined(CONFIG_SPICE)
    DISPLAY_TYPE_SPICE_APP,
#endif /* defined(CONFIG_SPICE) */
#if defined(CONFIG_DBUS_DISPLAY)
    DISPLAY_TYPE_DBUS,
#endif /* defined(CONFIG_DBUS_DISPLAY) */
    DISPLAY_TYPE__MAX,
} DisplayType;

os_setup_signal_handling()

函数 os_setup_signal_handling() 在 Windows 系统中的定义如下:

static inline void os_setup_signal_handling(void) {}

在 POSIX 系统中定义如下:

void os_setup_signal_handling(void)
{
    struct sigaction act;

    memset(&act, 0, sizeof(act));
    act.sa_sigaction = termsig_handler;
    act.sa_flags = SA_SIGINFO;
    sigaction(SIGINT,  &act, NULL);
    sigaction(SIGHUP,  &act, NULL);
    sigaction(SIGTERM, &act, NULL);
}

qemu_spice.display_init()

变量 qemu_spice 定义如下:

struct QemuSpiceOps qemu_spice = {
    .init         = qemu_spice_init_stub,
    .display_init = qemu_spice_display_init_stub,
    .migrate_info = qemu_spice_migrate_info_stub,
    .set_passwd   = qemu_spice_set_passwd_stub,
    .set_pw_expire = qemu_spice_set_pw_expire_stub,
    .display_add_client = qemu_spice_display_add_client_stub,
};

因此,函数 qemu_spice.display_init() 实际调用 qemu_spice_display_init_stub(),如果定义了 CONFIG_SPICE,则调用 /ui/spice-display.c 文件中的函数 qemu_spice_display_init(),定义如下:

void qemu_spice_display_init(void)
{
    QemuOptsList *olist = qemu_find_opts("spice");
    QemuOpts *opts = QTAILQ_FIRST(&olist->head);
    QemuConsole *spice_con, *con;
    const char *str;
    int i;

    str = qemu_opt_get(opts, "display");
    if (str) {
        int head = qemu_opt_get_number(opts, "head", 0);
        Error *err = NULL;

        spice_con = qemu_console_lookup_by_device_name(str, head, &err);
        if (err) {
            error_report("Failed to lookup display/head");
            exit(1);
        }
    } else {
        spice_con = NULL;
    }

    for (i = 0;; i++) {
        con = qemu_console_lookup_by_index(i);
        if (!con || !qemu_console_is_graphic(con)) {
            break;
        }
        if (qemu_spice_have_display_interface(con)) {
            continue;
        }
        if (spice_con != NULL && spice_con != con) {
            continue;
        }
        qemu_spice_display_init_one(con);
    }

    qemu_spice_display_init_done();
}

qemu_console_lookup_by_device_name()

此函数在 /ui/console.c 文件中,定义如下:

QemuConsole *qemu_console_lookup_by_device_name(const char *device_id,
                                                uint32_t head, Error **errp)
{
    DeviceState *dev;
    QemuConsole *con;

    dev = qdev_find_recursive(sysbus_get_default(), device_id);
    if (dev == NULL) {
        error_set(errp, ERROR_CLASS_DEVICE_NOT_FOUND,
                  "Device '%s' not found", device_id);
        return NULL;
    }

    con = qemu_console_lookup_by_device(dev, head);
    if (con == NULL) {
        error_setg(errp, "Device %s (head %d) is not bound to a QemuConsole",
                   device_id, head);
        return NULL;
    }

    return con;
}
qemu_console_lookup_by_device()

函数 qemu_console_lookup_by_device() 定义如下:

QemuConsole *qemu_console_lookup_by_device(DeviceState *dev, uint32_t head)
{
    QemuConsole *con;
    Object *obj;
    uint32_t h;

    QTAILQ_FOREACH(con, &consoles, next) {
        obj = object_property_get_link(OBJECT(con),
                                       "device", &error_abort);
        if (DEVICE(obj) != dev) {
            continue;
        }
        h = object_property_get_uint(OBJECT(con),
                                     "head", &error_abort);
        if (h != head) {
            continue;
        }
        return con;
    }
    return NULL;
}

qemu_console_lookup_by_index()

此函数在 /ui/console.c 文件中,定义如下:

QemuConsole *qemu_console_lookup_by_index(unsigned int index)
{
    QemuConsole *con;

    QTAILQ_FOREACH(con, &consoles, next) {
        if (con->index == index) {
            return con;
        }
    }
    return NULL;
}

qemu_console_is_graphic()

此函数在 /ui/console.c 文件中,定义如下:

bool qemu_console_is_graphic(QemuConsole *con)
{
    if (con == NULL) {
        con = active_console;
    }
    return con && QEMU_IS_GRAPHIC_CONSOLE(con);
}

qemu_spice_have_display_interface()

此函数在 /ui/spice-core.c 文件中,定义如下:

bool qemu_spice_have_display_interface(QemuConsole *con)
{
    if (g_slist_find(spice_consoles, con)) {
        return true;
    }
    return false;
}

qemu_spice_display_init_one()

此函数在 /ui/spice-display.c 文件中,定义如下:

static void qemu_spice_display_init_one(QemuConsole *con)
{
    SimpleSpiceDisplay *ssd = g_new0(SimpleSpiceDisplay, 1);

    qemu_spice_display_init_common(ssd);

    ssd->dcl.ops = &display_listener_ops;
#ifdef HAVE_SPICE_GL
    if (spice_opengl) {
        ssd->dcl.ops = &display_listener_gl_ops;
        ssd->dgc.ops = &gl_ctx_ops;
        ssd->gl_unblock_bh = qemu_bh_new(qemu_spice_gl_unblock_bh, ssd);
        ssd->gl_unblock_timer = timer_new_ms(QEMU_CLOCK_REALTIME,
                                             qemu_spice_gl_block_timer, ssd);
        ssd->gls = qemu_gl_init_shader();
        ssd->have_surface = false;
        ssd->have_scanout = false;
    }
#endif
    ssd->dcl.con = con;

    ssd->qxl.base.sif = &dpy_interface.base;
    qemu_spice_add_display_interface(&ssd->qxl, con);

#if SPICE_SERVER_VERSION >= 0x000e02 /* release 0.14.2 */
    Error *err = NULL;
    char device_address[256] = "";
    if (qemu_console_fill_device_address(con, device_address, 256, &err)) {
        spice_qxl_set_device_info(&ssd->qxl,
                                  device_address,
                                  qemu_console_get_head(con),
                                  1);
    } else {
        error_report_err(err);
    }
#endif

    qemu_spice_create_host_memslot(ssd);

    if (spice_opengl) {
        qemu_console_set_display_gl_ctx(con, &ssd->dgc);
    }
    register_displaychangelistener(&ssd->dcl);
}
qemu_spice_display_init_common()
qemu_bh_new()
timer_new_ms()
qemu_gl_init_shader()
qemu_spice_add_display_interface()
qemu_console_fill_device_address()
spice_qxl_set_device_info()
qemu_console_get_head()
qemu_spice_create_host_memslot()
qemu_console_set_display_gl_ctx()
register_displaychangelistener()

qemu_spice_display_init_done()

此函数在 /ui/spice-display.c 文件中,定义如下:

void qemu_spice_display_init_done(void)
{
    if (runstate_is_running()) {
        qemu_spice_display_start();
    }
    qemu_add_vm_change_state_handler(vm_change_state_handler, NULL);
}
runstate_is_running()

此函数在 /system/runstate.c 文件中,定义如下:

bool runstate_is_running(void)
{
    return runstate_check(RUN_STATE_RUNNING);
}

bool runstate_check(RunState state)
{
    return current_run_state == state;
}
qemu_spice_display_start()

函数 qemu_spice_display_start() 定义如下:

void  qemu_spice_display_start(void)
{
    if (spice_display_is_running) {
        return;
    }

    spice_display_is_running = true;
    spice_server_vm_start(spice_server);
}
qemu_add_vm_change_state_handler()

此函数在 /ui/spice-core.c 文件中,定义如下:

/**
 * qemu_add_vm_change_state_handler_prio:
 * @cb: the callback to invoke
 * @opaque: user data passed to the callback
 * @priority: low priorities execute first when the vm runs and the reverse is
 *            true when the vm stops
 *
 * Register a callback function that is invoked when the vm starts or stops
 * running.
 *
 * Returns: an entry to be freed using qemu_del_vm_change_state_handler()
 */
VMChangeStateEntry *qemu_add_vm_change_state_handler_prio(
        VMChangeStateHandler *cb, void *opaque, int priority)
{
    return qemu_add_vm_change_state_handler_prio_full(cb, NULL, opaque,
                                                      priority);
}

/**
 * qemu_add_vm_change_state_handler_prio_full:
 * @cb: the main callback to invoke
 * @prepare_cb: a callback to invoke before the main callback
 * @opaque: user data passed to the callbacks
 * @priority: low priorities execute first when the vm runs and the reverse is
 *            true when the vm stops
 *
 * Register a main callback function and an optional prepare callback function
 * that are invoked when the vm starts or stops running. The main callback and
 * the prepare callback are called in two separate phases: First all prepare
 * callbacks are called and only then all main callbacks are called. As its
 * name suggests, the prepare callback can be used to do some preparatory work
 * before invoking the main callback.
 *
 * Returns: an entry to be freed using qemu_del_vm_change_state_handler()
 */
VMChangeStateEntry *
qemu_add_vm_change_state_handler_prio_full(VMChangeStateHandler *cb,
                                           VMChangeStateHandler *prepare_cb,
                                           void *opaque, int priority)
{
    VMChangeStateEntry *e;
    VMChangeStateEntry *other;

    e = g_malloc0(sizeof(*e));
    e->cb = cb;
    e->prepare_cb = prepare_cb;
    e->opaque = opaque;
    e->priority = priority;

    /* Keep list sorted in ascending priority order */
    QTAILQ_FOREACH(other, &vm_change_state_head, entries) {
        if (priority < other->priority) {
            QTAILQ_INSERT_BEFORE(other, e, entries);
            return e;
        }
    }

    QTAILQ_INSERT_TAIL(&vm_change_state_head, e, entries);
    return e;
}

VMChangeStateEntry *qemu_add_vm_change_state_handler(VMChangeStateHandler *cb,
                                                     void *opaque)
{
    return qemu_add_vm_change_state_handler_prio(cb, opaque, 0);
}
vm_change_state_handler()

此函数在 /ui/spice-core.c 文件中,定义如下:

static void vm_change_state_handler(void *opaque, bool running,
                                    RunState state)
{
    if (running) {
        qemu_spice_display_start();
    } else if (state != RUN_STATE_PAUSED) {
        qemu_spice_display_stop();
    }
}

函数 qemu_spice_display_start() 和 qemu_spice_display_stop() 定义如下:

void  qemu_spice_display_start(void)
{
    if (spice_display_is_running) {
        return;
    }

    spice_display_is_running = true;
    spice_server_vm_start(spice_server);
}

void qemu_spice_display_stop(void)
{
    if (!spice_display_is_running) {
        return;
    }

    spice_server_vm_stop(spice_server);
    spice_display_is_running = false;
}

至此,函数 qemu_spice.display_init() 执行完毕,同时主程序的函数 qemu_init_displays() 也执行完毕。


总结

以上分析了 QEMU 系统仿真在启动过程中,QEMU系统仿真完成显示初始化的代码。

04-30 03:48