本文以 QEMU 8.2.2 为例,分析其作为系统仿真工具的启动过程,并为读者展示各种 QEMU 系统仿真的启动配置实例。
本文读者需要具备一定的 QEMU 系统仿真使用经验,并对 C 语言编程有一定了解。
QEMU 是一个通用且开源的机器模拟器和虚拟机。
其官方主页是:https://www.qemu.org/
QEMU 作为系统仿真工具,其入口代码在 system/main.c 文件中,初始化函数 qemu_init() 的实现在 system/vl.c 文件中,在完成 QEMU 虚拟机导出信息的设置,接下来将处理预配置的工作,本篇文章将完成以下代码部分的分析。
这部分代码在 system/vl.c 文件中,实现如下:
void qemu_init(int argc, char **argv)
{
...
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();
}
}
此函数在 /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;
};
此函数在 /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() 在 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 定义如下:
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();
}
此函数在 /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() 定义如下:
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;
}
此函数在 /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;
}
此函数在 /ui/console.c 文件中,定义如下:
bool qemu_console_is_graphic(QemuConsole *con)
{
if (con == NULL) {
con = active_console;
}
return con && QEMU_IS_GRAPHIC_CONSOLE(con);
}
此函数在 /ui/spice-core.c 文件中,定义如下:
bool qemu_spice_have_display_interface(QemuConsole *con)
{
if (g_slist_find(spice_consoles, con)) {
return true;
}
return false;
}
此函数在 /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);
}
此函数在 /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);
}
此函数在 /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() 定义如下:
void qemu_spice_display_start(void)
{
if (spice_display_is_running) {
return;
}
spice_display_is_running = true;
spice_server_vm_start(spice_server);
}
此函数在 /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);
}
此函数在 /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系统仿真完成显示初始化的代码。