• 【QEMU系统分析之启动篇(十七)】


    系列文章目录

    第十七章 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();
    ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    数据结构 DisplayState 定义如下:

    struct DisplayState {
        QEMUTimer *gui_timer;
        uint64_t last_update;
        uint64_t update_interval;
        bool refreshing;
    
        QLIST_HEAD(, DisplayChangeListener) listeners;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    其中,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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    os_setup_signal_handling()

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

    static inline void os_setup_signal_handling(void) {}
    
    • 1

    在 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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    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,
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    因此,函数 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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    函数 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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

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


    总结

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

  • 相关阅读:
    微服务架构师封神之路13-RabbitMQ集群与高可用|RabbitMQ clustering and HA
    【spring boot 使用模板引擎】
    Spring Cache和redis结合使用
    手机桌面待办事项APP推荐
    python调用git出错:ImportError: Failed to initialize: Bad git executable.
    数据整理(提取年份)
    MFC:自绘CListBox,GetText返回一个乱码
    本地连接远程mysql服务报错:MySQL is running but PID file could not be found [FAILED]
    2023 版 QQ 机器人运行部署文档
    想找一个英文的二元分类数据集,类似sst2这种
  • 原文地址:https://blog.csdn.net/dvd37784302/article/details/138095117