QEMUのなかみ(QEMU internals) 5
QEMU Guest Agent
QEMUはゲストOS内にqemu-gaをインストールする事で、ホストからゲストの情報を取得できる。 qemu-gaはvirtio-serialを通して、ホストとUNIXソケットで双方向通信を行う。ゲスト側は/dev/virtio-ports/org.qemu.guest_agen.0などのデバイスファイルを通し、またホスト側は/tmp/qga.agentなどを通して通信する。(これらはqemu起動時にオプションで指定する)
|sh| ./qemu-ga -m virtio-serial -p /dev/virtio-ports/org.qemu.guest_agent.0 ||<
-mは通信経路(メソッド)で-pはパス。
ゲスト-ホスト間はQMP(json)でやりとりをする。
(qemu-ga.c)
|c| struct GAState { JSONMessageParser parser; GMainLoop *main_loop; GIOChannel *conn_channel; GIOChannel *listen_channel; const char *path; const char method; bool virtio; / fastpath to check for virtio to deal with poll() quirks */ GACommandState *command_state; GLogLevelFlags log_level; FILE *log_file; bool logging_enabled; };
static struct GAState *ga_state;
int main(int argc, char **argv) { ... s = g_malloc0(sizeof(GAState)); s->conn_channel = NULL; s->path = path; s->method = method; s->log_file = log_file; s->log_level = log_level; g_log_set_default_handler(ga_log, s); g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR); s->logging_enabled = true; s->command_state = ga_command_state_new(); ga_command_state_init(s, s->command_state); ga_command_state_init_all(s->command_state); ga_state = s;
module_call_init(MODULE_INIT_QAPI);
init_guest_agent(ga_state); [*]
register_signal_handlers();
g_main_loop_run(ga_state->main_loop);
ga_command_state_cleanup_all(ga_state->command_state);
unlink(pidfile);
return 0;
} ||<
[*]のinit_guest_agentでエージェントを初期化する。
(qemu-ga.c)
|c| static void init_guest_agent(GAState *s) { struct termios tio; int ret, fd;
if (strcmp(s->method, "virtio-serial") == 0) {
s->virtio = true;
fd = qemu_open(s->path, O_RDWR | O_NONBLOCK | O_ASYNC);
ret = conn_channel_add(s, fd); [*]
}
...
json_message_parser_init(&s->parser, process_event); [*]
s->main_loop = g_main_loop_new(NULL, false);
}
||< virtio-serialの場合、[*]でデバイスファイルを開いた後、glibを使ってIOチャンネルを作成する。
(qemu-ga.c)
|c| static gboolean conn_channel_read(GIOChannel *channel, GIOCondition condition, gpointer data) { GAState *s = data; gchar buf[1024]; gsize count; GError err = NULL; memset(buf, 0, 1024); GIOStatus status = g_io_channel_read_chars(channel, buf, 1024, &count, &err); [] switch (status) { case G_IO_STATUS_NORMAL: g_debug("read data, count: %d, data: %s", (int)count, buf); json_message_parser_feed(&s->parser, (char )buf, (int)count); [] ... } return true; }
static int conn_channel_add(GAState *s, int fd) { GIOChannel *conn_channel; GError *err = NULL;
g_assert(s && !s->conn_channel);
conn_channel = g_io_channel_unix_new(fd);
g_assert(conn_channel);
g_io_channel_set_encoding(conn_channel, NULL, &err);
g_io_add_watch(conn_channel, G_IO_IN | G_IO_HUP,
conn_channel_read, s); [*]
s->conn_channel = conn_channel;
return 0;
} ||<
[]でゲストがデバイスファイルからreadした際のコールバックをconn_channel_readに設定する。 conn_channel_readでは[]で1024バイト読み、json_message_parser_feed(json-streamer.c)に投げる。この関数はJSONが文法的に正しいか確認するだけ。 また[*]でjson_message_parser_init(json-streamer.c)でJSONパーサーを初期化している。この時JSONパース時に呼ばれるコールバック関数をprocess_eventに設定している。
(qemu-ga.c)
|c| /* handle requests/control events coming in over the channel */ static void process_event(JSONMessageParser *parser, QList *tokens) { GAState *s = container_of(parser, GAState, parser); QObject *obj; QDict *qdict; Error *err = NULL; int ret;
g_assert(s && parser);
g_debug("process_event: called");
obj = json_parser_parse_err(tokens, NULL, &err);
if (err || !obj || qobject_type(obj) != QTYPE_QDICT) {
qobject_decref(obj);
qdict = qdict_new();
if (!err) {
g_warning("failed to parse event: unknown error");
error_set(&err, QERR_JSON_PARSING);
} else {
g_warning("failed to parse event: %s", error_get_pretty(err));
}
qdict_put_obj(qdict, "error", error_get_qobject(err));
error_free(err);
} else {
qdict = qobject_to_qdict(obj);
}
g_assert(qdict);
/* handle host->guest commands */
if (qdict_haskey(qdict, "execute")) {
process_command(s, qdict); [*]
} else {
ret = conn_channel_send_payload(s->conn_channel, QOBJECT(qdict));
}
QDECREF(qdict);
} ||< [*]のprocess_commandで{"execute": "hoge"} のhogeを実行する。
(qemu-ga.c)
|c| static void process_command(GAState *s, QDict *req) { QObject *rsp = NULL; int ret;
g_assert(req);
g_debug("processing command");
rsp = qmp_dispatch(QOBJECT(req)); [*]
if (rsp) {
ret = conn_channel_send_payload(s->conn_channel, rsp);
if (ret) {
g_warning("error sending payload: %s", strerror(ret));
}
qobject_decref(rsp);
} else {
g_warning("error getting response");
}
} ||< qmp_dispatch(qapi/qmp-dispatch.c)で指定されたコマンドを実行する。
(qapi/qmp-dispatch.c)
|c| static QObject *do_qmp_dispatch(QObject *request, Error **errp) { const char *command; QDict *args, *dict; QmpCommand *cmd; QObject *ret = NULL;
dict = qmp_dispatch_check_obj(request, errp);
if (!dict || error_is_set(errp)) {
return NULL;
}
command = qdict_get_str(dict, "execute");
cmd = qmp_find_command(command);
if (cmd == NULL) {
error_set(errp, QERR_COMMAND_NOT_FOUND, command);
return NULL;
}
if (!qdict_haskey(dict, "arguments")) {
args = qdict_new();
} else {
args = qdict_get_qdict(dict, "arguments");
QINCREF(args);
}
switch (cmd->type) {
case QCT_NORMAL:
cmd->fn(args, &ret, errp); [*]
if (!error_is_set(errp) && ret == NULL) {
ret = QOBJECT(qdict_new());
}
break;
}
QDECREF(args);
return ret;
}
QObject *qmp_dispatch(QObject *request) { ret = do_qmp_dispatch(request, &err);
return QOBJECT(rsp);
} ||< [*]で検索したQmpCommandのコールバック関数を呼ぶ。これがコマンドの本体。