系列文章目录
第十七章 QEMU系统仿真的显示初始化分析
文章目录
- 系列文章目录
- 
- [第十七章 QEMU系统仿真的显示初始化分析](#第十七章 QEMU系统仿真的显示初始化分析)
 
- 前言
- 一、QEMU是什么?
- 二、QEMU系统仿真的启动分析
- 
- 1.系统仿真的初始化代码
- 2.主循环数据初始化
- [3. qemu_init_displays()](#3. qemu_init_displays())
- 
- init_displaystate()
- qemu_display_init()
- os_setup_signal_handling()
- qemu_spice.display_init()
- 
- qemu_console_lookup_by_device_name()
- qemu_console_lookup_by_index()
- qemu_console_is_graphic()
- qemu_spice_have_display_interface()
- qemu_spice_display_init_one()
- 
- 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()
 
 
 
- 总结
前言
本文以 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 文件中,实现如下:
            
            
              c
              
              
            
          
          void qemu_init(int argc, char **argv)
{
...
    qemu_init_displays();
...
}3. qemu_init_displays()
此函数在 /system/vl.c 文件中,定义如下:
            
            
              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 文件中,定义如下:
            
            
              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 定义如下:
            
            
              c
              
              
            
          
          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 文件中,定义如下:
            
            
              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 定义如下:
            
            
              c
              
              
            
          
          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 系统中的定义如下:
            
            
              c
              
              
            
          
          static inline void os_setup_signal_handling(void) {}在 POSIX 系统中定义如下:
            
            
              c
              
              
            
          
          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 定义如下:
            
            
              c
              
              
            
          
          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(),定义如下:
            
            
              c
              
              
            
          
          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 文件中,定义如下:
            
            
              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() 定义如下:
            
            
              c
              
              
            
          
          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 文件中,定义如下:
            
            
              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 文件中,定义如下:
            
            
              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 文件中,定义如下:
            
            
              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 文件中,定义如下:
            
            
              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 文件中,定义如下:
            
            
              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 文件中,定义如下:
            
            
              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() 定义如下:
            
            
              c
              
              
            
          
          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 文件中,定义如下:
            
            
              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 文件中,定义如下:
            
            
              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() 定义如下:
            
            
              c
              
              
            
          
          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系统仿真完成显示初始化的代码。